Callback when state changes (uncontrolled mode). Called once per set or update with all changed entries.
#### External Store (Controlled Mode)
Pass a `StateStore` to bypass the internal state and wire json-render to any state management library:
```tsx
import { createStateStore, type StateStore } from "@json-render/react";
const store = createStateStore({ count: 0 });
{children}
// Mutate from anywhere — React re-renders automatically:
store.set("/count", 1);
```
The `store` prop is also available on `JSONUIProvider` and `createRenderer`.
### ActionProvider
```tsx
}>
{children}
type ActionHandler = (params: Record) => void | Promise;
```
### VisibilityProvider
```tsx
{children}
```
`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](/docs/visibility) for the full syntax.
### ValidationProvider
```tsx
}>
{children}
type ValidationFunction = (value: unknown, args?: object) => boolean | Promise;
```
## 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.
```tsx
import { defineRegistry } from '@json-render/react';
const { registry } = defineRegistry(catalog, {
components: {
Card: ({ props, children }) =>
{props.title}{children}
,
Button: ({ props, emit }) => (
),
},
});
// Pass to
```
## Components
### Renderer
```tsx
type Registry = Record>;
```
### JSONUIProvider
Convenience wrapper that combines `StateProvider`, `VisibilityProvider`, `ValidationProvider`, and `ActionProvider`. Accepts all their props plus:
Prop
Type
Description
functions
Record<string, ComputedFunction>
Named functions for $computed expressions in props
```tsx
{ /* ... */ } }}
functions={{ fullName: (args) => `${args.first} ${args.last}` }}
>
```
The `functions` prop is also available on `createRenderer`.
### Component Props (via defineRegistry)
```tsx
interface ComponentContext
{
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; // 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`:
```tsx
Link: ({ props, on }) => {
const click = on("click");
return (
{
if (click.shouldPreventDefault) e.preventDefault();
click.emit();
}}
>
{props.label}
);
},
```
### BaseComponentProps
Catalog-agnostic base type for building reusable component libraries (e.g. `@json-render/shadcn`) that are not tied to a specific catalog:
```typescript
import type { BaseComponentProps } from "@json-render/react";
const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
{props.title}{children}
);
```
## Hooks
### useUIStream
```typescript
const {
spec, // Spec | null - current UI state
isStreaming, // boolean - true while streaming
error, // Error | null
send, // (prompt: string, context?: Record) => Promise
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
```typescript
const {
state, // StateModel (Record)
get, // (path: string) => unknown
set, // (path: string, value: unknown) => void
update, // (updates: Record) => void
} = useStateStore();
```
### useStateValue
```typescript
const value = useStateValue(path: string);
```
### useStateBinding (deprecated)
> **Deprecated.** Use `useBoundProp` with `$bindState` expressions instead.
```typescript
const [value, setValue] = useStateBinding(path: string);
```
### useActions
```typescript
const { execute } = useActions();
// execute(binding: ActionBinding) => Promise
```
### useAction
```typescript
const { execute, isLoading } = useAction(binding: ActionBinding);
// execute() => Promise
```
### useIsVisible
```typescript
const isVisible = useIsVisible(condition?: VisibilityCondition);
```
### useFieldValidation
```typescript
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' }`.
### useOptionalValidation
Non-throwing variant of `useValidation()`. Returns `null` when no `ValidationProvider` is present, instead of throwing. Useful in components that may or may not be rendered inside a validation context.
```typescript
const validation = useOptionalValidation();
// ValidationContextValue | null
```
### useBoundProp
Two-way binding helper for `$bindState` / `$bindItem` expressions. Returns `[value, setValue]` where `setValue` writes back to the bound state path.
```typescript
const [value, setValue] = useBoundProp(
propValue: T | undefined, // The already-resolved prop value
bindingPath: string | undefined // From bindings?.value
);
```
Use inside registry components:
```tsx
const Input: ComponentRenderer = ({ props, bindings }) => {
const [value, setValue] = useBoundProp(props.value, bindings?.value);
return 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 SDK `UIMessage.parts` array. Use with the Vercel AI SDK's `useChat` for 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.
```typescript
const {
messages, // ChatMessage[] - all messages in the conversation
isStreaming, // boolean - true while streaming
error, // Error | null
send, // (text: string) => Promise
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`.
```typescript
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-null
```
### buildSpecFromParts / getTextFromParts
Standalone utilities for extracting spec and text from AI SDK message parts (non-hook versions):
```typescript
import { buildSpecFromParts, getTextFromParts } from '@json-render/react';
const spec = buildSpecFromParts(message.parts); // Spec | null
const text = getTextFromParts(message.parts); // string
```