@json-render/react
v0.2.0
Published
React renderer for @json-render/core. JSON becomes React components.
Maintainers
Readme
@json-render/react
Predictable. Guardrailed. Fast. React renderer for user-prompted dashboards, widgets, apps, and data visualizations.
Features
- Visibility Filtering: Components automatically show/hide based on visibility conditions
- Action Handling: Built-in action execution with confirmation dialogs
- Validation: Field validation with error display
- Data Binding: Two-way data binding between UI and data model
- Streaming: Progressive rendering from streamed UI trees
Installation
npm install @json-render/react @json-render/core
# or
pnpm add @json-render/react @json-render/coreQuick Start
Basic Setup
import { JSONUIProvider, Renderer, useUIStream } from '@json-render/react';
// Define your component registry
const registry = {
Card: ({ element, children }) => (
<div className="card">
<h3>{element.props.title}</h3>
{children}
</div>
),
Button: ({ element, onAction }) => (
<button onClick={() => onAction?.(element.props.action)}>
{element.props.label}
</button>
),
};
// Action handlers
const actionHandlers = {
submit: async (params) => {
await api.submit(params);
},
export: (params) => {
download(params.format);
},
};
function App() {
const { tree, isStreaming, send, clear } = useUIStream({
api: '/api/generate',
});
return (
<JSONUIProvider
registry={registry}
initialData={{ user: { name: 'John' } }}
authState={{ isSignedIn: true }}
actionHandlers={actionHandlers}
>
<input
placeholder="Describe the UI..."
onKeyDown={(e) => e.key === 'Enter' && send(e.target.value)}
/>
<Renderer tree={tree} registry={registry} loading={isStreaming} />
</JSONUIProvider>
);
}Using Contexts Directly
import {
DataProvider,
VisibilityProvider,
ActionProvider,
ValidationProvider,
useData,
useVisibility,
useActions,
useFieldValidation,
} from '@json-render/react';
// Data context
function MyComponent() {
const { data, get, set } = useData();
const value = get('/user/name');
return (
<input
value={value}
onChange={(e) => set('/user/name', e.target.value)}
/>
);
}
// Visibility context
function ConditionalComponent({ visible }) {
const { isVisible } = useVisibility();
if (!isVisible(visible)) {
return null;
}
return <div>Visible content</div>;
}
// Action context
function ActionButton({ action }) {
const { execute, loadingActions } = useActions();
return (
<button
onClick={() => execute(action)}
disabled={loadingActions.has(action.name)}
>
{action.name}
</button>
);
}
// Validation context
function ValidatedInput({ path, checks }) {
const { errors, validate, touch } = useFieldValidation(path, { checks });
const [value, setValue] = useDataBinding(path);
return (
<div>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={() => { touch(); validate(); }}
/>
{errors.map((err) => <span key={err}>{err}</span>)}
</div>
);
}Streaming UI
import { useUIStream } from '@json-render/react';
function StreamingDemo() {
const {
tree, // Current UI tree
isStreaming, // Whether currently streaming
error, // Error if any
send, // Send a prompt
clear, // Clear the tree
} = useUIStream({
api: '/api/generate',
onComplete: (tree) => console.log('Done:', tree),
onError: (err) => console.error('Error:', err),
});
return (
<div>
<button onClick={() => send('Create a dashboard')}>
Generate
</button>
{isStreaming && <span>Generating...</span>}
{tree && <Renderer tree={tree} registry={registry} />}
</div>
);
}API Reference
Providers
JSONUIProvider- Combined provider for all contextsDataProvider- Data model contextVisibilityProvider- Visibility evaluation contextActionProvider- Action execution contextValidationProvider- Validation context
Hooks
useData()- Access data modeluseDataValue(path)- Get a single valueuseDataBinding(path)- Two-way binding like useStateuseVisibility()- Access visibility evaluationuseIsVisible(condition)- Check if condition is visibleuseActions()- Access action executionuseAction(action)- Execute a specific actionuseValidation()- Access validation contextuseFieldValidation(path, config)- Field-level validation
Components
Renderer- Render a UI treeConfirmDialog- Default confirmation dialog
Utilities
useUIStream(options)- Hook for streaming UI generationflatToTree(elements)- Convert flat list to tree
Component Props
Components in your registry receive these props:
interface ComponentRenderProps<P = Record<string, unknown>> {
element: UIElement<string, P>; // The element definition
children?: ReactNode; // Rendered children
onAction?: (action: Action) => void; // Action callback
loading?: boolean; // Streaming in progress
}Example Component
function MetricComponent({ element }: ComponentRenderProps) {
const { label, valuePath, format } = element.props;
const value = useDataValue(valuePath);
const formatted = format === 'currency'
? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(value)
: String(value);
return (
<div className="metric">
<span className="label">{label}</span>
<span className="value">{formatted}</span>
</div>
);
}