A production-focused playground for live editing React code with real-time preview capabilities
—
React Context and Higher-Order Component utilities for integrating with the live editing system. These tools allow you to build custom components that interact with the live editing state and create advanced integrations.
React Context that provides access to the live editing state and control functions. This context is used internally by all live components and can be consumed by custom components to access editing state.
/**
* React Context providing live editing state and controls
*/
const LiveContext: React.Context<{
/** Current error message from compilation or execution */
error?: string;
/** Currently rendered React component from code execution */
element?: ComponentType | null;
/** Original code string passed to LiveProvider */
code: string;
/** Most recent code change (may differ from 'code' during editing) */
newCode?: string;
/** Whether editing is currently disabled */
disabled: boolean;
/** Language identifier for syntax highlighting */
language: string;
/** Prism theme for syntax highlighting */
theme?: typeof themes.nightOwl;
/** Function to handle and display errors */
onError(error: Error): void;
/** Function called when code content changes */
onChange(value: string): void;
}>;Usage Examples:
import React, { useContext } from "react";
import { LiveProvider, LiveContext } from "react-live";
// Custom component consuming LiveContext
function CodeStats() {
const { code, error, element } = useContext(LiveContext);
return (
<div className="code-stats">
<p>Lines: {code.split('\n').length}</p>
<p>Characters: {code.length}</p>
<p>Status: {error ? 'Error' : element ? 'Valid' : 'Empty'}</p>
{error && <p className="error">Error: {error}</p>}
</div>
);
}
// Usage within LiveProvider
function PlaygroundWithStats() {
return (
<LiveProvider code="<h1>Hello World</h1>">
<div className="editor-panel">
<LiveEditor />
<CodeStats />
</div>
<LivePreview />
</LiveProvider>
);
}
// Custom reset button
function ResetButton({ defaultCode }: { defaultCode: string }) {
const { onChange } = useContext(LiveContext);
return (
<button onClick={() => onChange(defaultCode)}>
Reset Code
</button>
);
}
// Code formatter component
function FormatButton() {
const { code, onChange } = useContext(LiveContext);
const formatCode = () => {
try {
// Simple formatter (in real app, use prettier or similar)
const formatted = code
.split('\n')
.map(line => line.trim())
.join('\n');
onChange(formatted);
} catch (error) {
console.error('Format failed:', error);
}
};
return (
<button onClick={formatCode}>
Format Code
</button>
);
}
// Custom error display with details
function DetailedError() {
const { error, code } = useContext(LiveContext);
if (!error) return null;
const getErrorDetails = (error: string) => {
if (error.includes('SyntaxError')) {
return 'Check for missing brackets, quotes, or semicolons';
}
if (error.includes('ReferenceError')) {
return 'Variable or component not found in scope';
}
return 'Unexpected error occurred';
};
return (
<div className="detailed-error">
<h4>Error Details</h4>
<pre className="error-message">{error}</pre>
<p className="error-hint">{getErrorDetails(error)}</p>
<details>
<summary>Code Context</summary>
<pre>{code}</pre>
</details>
</div>
);
}
// Complete custom playground
function CustomPlayground() {
const initialCode = `
function Welcome({ name }) {
return <h1>Hello, {name}!</h1>;
}
<Welcome name="React Live" />
`;
return (
<LiveProvider code={initialCode} noInline>
<div className="playground-layout">
<div className="editor-section">
<div className="editor-header">
<CodeStats />
<ResetButton defaultCode={initialCode} />
<FormatButton />
</div>
<LiveEditor />
</div>
<div className="preview-section">
<LivePreview />
<DetailedError />
</div>
</div>
</LiveProvider>
);
}Higher-Order Component that injects the entire LiveContext as props into a wrapped component. Useful for class components or when you need to pass live state as props.
/**
* Higher-Order Component that injects live context as props
* @param WrappedComponent - Component to receive live context as props
* @returns Enhanced component with live context injected as 'live' prop
*/
function withLive<T>(
WrappedComponent: ComponentType<T & { live: Record<string, unknown> }>
): ComponentType<T>;Usage Examples:
import React, { Component } from "react";
import { LiveProvider, withLive } from "react-live";
// Class component with live context
class CodeAnalyzer extends Component<{ live: any }> {
analyzeProp() {
const { live } = this.props;
return {
hasError: !!live.error,
codeLength: live.code.length,
lineCount: live.code.split('\n').length,
isDisabled: live.disabled
};
}
render() {
const analysis = this.analyzeProp();
const { live } = this.props;
return (
<div className="code-analyzer">
<h3>Code Analysis</h3>
<ul>
<li>Status: {analysis.hasError ? 'Error' : 'Valid'}</li>
<li>Length: {analysis.codeLength} characters</li>
<li>Lines: {analysis.lineCount}</li>
<li>Editable: {analysis.isDisabled ? 'No' : 'Yes'}</li>
</ul>
{live.error && (
<div className="error-section">
<strong>Error:</strong> {live.error}
</div>
)}
</div>
);
}
}
// Enhance with live context
const EnhancedCodeAnalyzer = withLive(CodeAnalyzer);
// Function component with live context as props
function CodeToolbar({ live, onSave }: { live: any; onSave: (code: string) => void }) {
const handleSave = () => {
if (!live.error && live.code.trim()) {
onSave(live.code);
}
};
const handleClear = () => {
live.onChange('');
};
return (
<div className="code-toolbar">
<button
onClick={handleSave}
disabled={!!live.error || !live.code.trim()}
>
Save Code
</button>
<button onClick={handleClear}>
Clear
</button>
<span className="status">
{live.error ? '❌ Error' : '✅ Valid'}
</span>
</div>
);
}
const EnhancedCodeToolbar = withLive(CodeToolbar);
// Usage in application
function AppWithLiveComponents() {
const [savedCodes, setSavedCodes] = useState<string[]>([]);
const handleSave = (code: string) => {
setSavedCodes(prev => [...prev, code]);
};
return (
<div className="app">
<LiveProvider code="<h1>Start coding...</h1>">
<div className="main-editor">
<EnhancedCodeToolbar onSave={handleSave} />
<LiveEditor />
<EnhancedCodeAnalyzer />
</div>
<div className="preview-area">
<LivePreview />
<LiveError />
</div>
</LiveProvider>
<div className="saved-codes">
<h3>Saved Codes ({savedCodes.length})</h3>
{savedCodes.map((code, index) => (
<details key={index}>
<summary>Code #{index + 1}</summary>
<pre>{code}</pre>
</details>
))}
</div>
</div>
);
}
// Custom hook alternative to withLive HOC
function useLiveContext() {
const context = useContext(LiveContext);
if (!context) {
throw new Error('useLiveContext must be used within a LiveProvider');
}
return context;
}
// Usage with custom hook
function ModernCodeAnalyzer() {
const live = useLiveContext();
const stats = useMemo(() => ({
words: live.code.split(/\s+/).filter(Boolean).length,
jsx: live.code.includes('<') && live.code.includes('>'),
typescript: live.code.includes(':') && live.enableTypeScript,
hasHooks: /use[A-Z]/.test(live.code)
}), [live.code]);
return (
<div className="modern-analyzer">
<h4>Code Statistics</h4>
<div className="stats-grid">
<div>Words: {stats.words}</div>
<div>JSX: {stats.jsx ? '✓' : '✗'}</div>
<div>TypeScript: {stats.typescript ? '✓' : '✗'}</div>
<div>Hooks: {stats.hasHooks ? '✓' : '✗'}</div>
</div>
</div>
);
}// Synchronize external state with live context
function useSyncedLiveCode(externalCode: string) {
const { code, onChange } = useContext(LiveContext);
useEffect(() => {
if (code !== externalCode) {
onChange(externalCode);
}
}, [externalCode, code, onChange]);
return { code, onChange };
}
// Bidirectional sync
function useBidirectionalSync(
externalCode: string,
onExternalChange: (code: string) => void
) {
const { code, onChange } = useContext(LiveContext);
// Sync external -> live
useEffect(() => {
if (code !== externalCode) {
onChange(externalCode);
}
}, [externalCode]);
// Sync live -> external (debounced)
useEffect(() => {
const timer = setTimeout(() => {
if (code !== externalCode) {
onExternalChange(code);
}
}, 1000);
return () => clearTimeout(timer);
}, [code, externalCode, onExternalChange]);
}function AutoRecoveringEditor() {
const { code, error, onChange } = useContext(LiveContext);
const [lastValidCode, setLastValidCode] = useState(code);
useEffect(() => {
if (!error && code.trim()) {
setLastValidCode(code);
}
}, [code, error]);
const recoverToLastValid = () => {
onChange(lastValidCode);
};
return (
<div className="recovering-editor">
<LiveEditor />
{error && (
<div className="error-recovery">
<p>Error detected: {error}</p>
<button onClick={recoverToLastValid}>
Recover to Last Valid Code
</button>
</div>
)}
</div>
);
}// Memoized context consumer
const MemoizedCodeDisplay = React.memo(() => {
const { code, error } = useContext(LiveContext);
return (
<div className="code-display">
<pre>{code}</pre>
{error && <div className="error">{error}</div>}
</div>
);
});
// Debounced change handler
function useDebouncedLiveChange(delay = 300) {
const { onChange } = useContext(LiveContext);
const debouncedOnChange = useMemo(
() => debounce(onChange, delay),
[onChange, delay]
);
return debouncedOnChange;
}Install with Tessl CLI
npx tessl i tessl/npm-react-live