@json-render/react
React components, providers, and hooks.
Providers
StateProvider
<StateProvider initialState={object}>
{children}
</StateProvider>ActionProvider
<ActionProvider handlers={Record<string, ActionHandler>}>
{children}
</ActionProvider>
type ActionHandler = (params: Record<string, unknown>) => void | Promise<void>;VisibilityProvider
<VisibilityProvider>
{children}
</VisibilityProvider>VisibilityProvider reads state from the parent StateProvider automatically. Conditions in specs use the VisibilityCondition format with $state paths (e.g. { "$state": "/path" }, { "$state": "/path", "eq": value }). See visibility for the full syntax.
ValidationProvider
<ValidationProvider customFunctions={Record<string, ValidationFunction>}>
{children}
</ValidationProvider>
type ValidationFunction = (value: unknown, args?: object) => boolean | Promise<boolean>;defineRegistry
Create a type-safe component registry from a catalog. Components receive props, children, emit, on, and loading with catalog-inferred types.
When the catalog declares actions, the actions field is required. When the catalog has no actions (e.g. actions: {}), the field is optional.
import { defineRegistry } from '@json-render/react';
const { registry } = defineRegistry(catalog, {
components: {
Card: ({ props, children }) => <div>{props.title}{children}</div>,
Button: ({ props, emit }) => (
<button onClick={() => emit("press")}>
{props.label}
</button>
),
},
});
// Pass to <Renderer>
<Renderer spec={spec} registry={registry} />Components
Renderer
<Renderer
spec={Spec} // The UI spec to render
registry={Registry} // Component registry (from defineRegistry)
loading={boolean} // Optional loading state
fallback={Component} // Optional fallback for unknown types
/>
type Registry = Record<string, React.ComponentType<ComponentRenderProps>>;Component Props (via defineRegistry)
interface ComponentContext<P> {
props: P; // Typed props from catalog
children?: React.ReactNode; // Rendered children (for slot components)
emit: (event: string) => void; // Emit a named event (always defined)
on: (event: string) => EventHandle; // Get event handle with metadata
loading?: boolean;
bindings?: Record<string, string>; // State paths from $bindState/$bindItem expressions
}
interface EventHandle {
emit: () => void; // Fire the event
shouldPreventDefault: boolean; // Whether any binding requested preventDefault
bound: boolean; // Whether any handler is bound
}Use emit("press") for simple event firing. Use on("click") when you need to check metadata like shouldPreventDefault:
Link: ({ props, on }) => {
const click = on("click");
return (
<a
href={props.href}
onClick={(e) => {
if (click.shouldPreventDefault) e.preventDefault();
click.emit();
}}
>
{props.label}
</a>
);
},BaseComponentProps
Catalog-agnostic base type for building reusable component libraries (e.g. @json-render/shadcn) that are not tied to a specific catalog:
import type { BaseComponentProps } from "@json-render/react";
const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
<div>{props.title}{children}</div>
);Hooks
useUIStream
const {
spec, // Spec | null - current UI state
isStreaming, // boolean - true while streaming
error, // Error | null
send, // (prompt: string, context?: Record<string, unknown>) => Promise<void>
clear, // () => void - reset spec and error
} = useUIStream({
api: string, // API endpoint URL
onComplete?: (spec: Spec) => void, // Called when streaming completes
onError?: (error: Error) => void, // Called when an error occurs
});useStateStore
const {
state, // StateModel (Record<string, unknown>)
get, // (path: string) => unknown
set, // (path: string, value: unknown) => void
update, // (updates: Record<string, unknown>) => void
} = useStateStore();useStateValue
const value = useStateValue(path: string);useStateBinding (deprecated)
Deprecated. Use
useBoundPropwith$bindStateexpressions instead.
const [value, setValue] = useStateBinding(path: string);useActions
const { execute } = useActions();
// execute(binding: ActionBinding) => Promise<void>useAction
const { execute, isLoading } = useAction(binding: ActionBinding);
// execute() => Promise<void>useIsVisible
const isVisible = useIsVisible(condition?: VisibilityCondition);useFieldValidation
const {
state, // FieldValidationState
validate, // () => ValidationResult
touch, // () => void
clear, // () => void
errors, // string[]
isValid, // boolean
} = useFieldValidation(path: string, config?: ValidationConfig);ValidationConfig is { checks?: ValidationCheck[], validateOn?: 'change' | 'blur' | 'submit' }.
useBoundProp
Two-way binding helper for $bindState / $bindItem expressions. Returns [value, setValue] where setValue writes back to the bound state path.
const [value, setValue] = useBoundProp<T>(
propValue: T | undefined, // The already-resolved prop value
bindingPath: string | undefined // From bindings?.value
);Use inside registry components:
const Input: ComponentRenderer = ({ props, bindings }) => {
const [value, setValue] = useBoundProp<string>(props.value, bindings?.value);
return <input value={value ?? ""} onChange={(e) => setValue(e.target.value)} />;
};Chat Hooks
Two hooks are available for chat + GenUI, depending on your setup:
useChatUI-- Self-contained chat hook with its own message state, fetch logic, and mixed stream parsing. Use when you want a standalone chat experience without the Vercel AI SDK.useJsonRenderMessage-- Extracts spec + text from an AI SDKUIMessage.partsarray. Use with the Vercel AI SDK'suseChatfor full AI SDK integration.
useChatUI
Hook for chat + GenUI experiences. Manages a multi-turn conversation where each assistant message can contain both text and a json-render UI spec.
const {
messages, // ChatMessage[] - all messages in the conversation
isStreaming, // boolean - true while streaming
error, // Error | null
send, // (text: string) => Promise<void>
clear, // () => void - reset conversation
} = useChatUI({
api: string, // API endpoint
onComplete?: (message: ChatMessage) => void, // Called when streaming completes
onError?: (error: Error) => void, // Called on error
});
interface ChatMessage {
id: string;
role: "user" | "assistant";
text: string;
spec: Spec | null;
}useJsonRenderMessage
Extract a spec and text content from an AI SDK message's parts array. Designed for integration with Vercel AI SDK's useChat.
const { spec, text, hasSpec } = useJsonRenderMessage(parts: DataPart[]);
// spec: Spec | null - compiled from JSONL patches in data parts
// text: string - concatenated text parts
// hasSpec: boolean - true when spec is non-nullbuildSpecFromParts / getTextFromParts
Standalone utilities for extracting spec and text from AI SDK message parts (non-hook versions):
import { buildSpecFromParts, getTextFromParts } from '@json-render/react';
const spec = buildSpecFromParts(message.parts); // Spec | null
const text = getTextFromParts(message.parts); // string