# @json-render/react

React components, providers, and hooks.

## Providers

### StateProvider

```tsx
<StateProvider initialState={object} onStateChange={fn}>
  {children}
</StateProvider>
```

<table>
  <thead>
    <tr>
      <th>Prop</th>
      <th>Type</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>store</code></td>
      <td><code>StateStore</code></td>
      <td>External store (controlled mode). When provided, <code>initialState</code> and <code>onStateChange</code> are ignored.</td>
    </tr>
    <tr>
      <td><code>initialState</code></td>
      <td><code>Record&lt;string, unknown&gt;</code></td>
      <td>Initial state model (uncontrolled mode).</td>
    </tr>
    <tr>
      <td><code>onStateChange</code></td>
      <td><code>{'(changes: Array<{ path: string; value: unknown }>) => void'}</code></td>
      <td>Callback when state changes (uncontrolled mode). Called once per <code>set</code> or <code>update</code> with all changed entries.</td>
    </tr>
  </tbody>
</table>

#### External Store (Controlled Mode)

Pass a `StateStore` to bypass the internal state and wire json-render to any state management library:

```tsx

const store = createStateStore({ count: 0 });

<StateProvider store={store}>
  {children}
</StateProvider>

// Mutate from anywhere — React re-renders automatically:
store.set("/count", 1);
```

The `store` prop is also available on `JSONUIProvider` and `createRenderer`.

### ActionProvider

```tsx
<ActionProvider handlers={Record<string, ActionHandler>}>
  {children}
</ActionProvider>

type ActionHandler = (params: Record<string, unknown>) => void | Promise<void>;
```

### VisibilityProvider

```tsx
<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](/docs/visibility) for the full syntax.

### ValidationProvider

```tsx
<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.

```tsx

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

```tsx
<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>>;
```

### JSONUIProvider

Convenience wrapper that combines `StateProvider`, `VisibilityProvider`, `ValidationProvider`, and `ActionProvider`. Accepts all their props plus:

<table>
  <thead>
    <tr>
      <th>Prop</th>
      <th>Type</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>functions</code></td>
      <td><code>Record&lt;string, ComputedFunction&gt;</code></td>
      <td>Named functions for <code>$computed</code> expressions in props</td>
    </tr>
  </tbody>
</table>

```tsx
<JSONUIProvider
  spec={spec}
  catalog={catalog}
  handlers={{ submit: async () => { /* ... */ } }}
  functions={{ fullName: (args) => `${args.first} ${args.last}` }}
>
  <Renderer spec={spec} registry={registry} />
</JSONUIProvider>
```

The `functions` prop is also available on `createRenderer`.

### Component Props (via defineRegistry)

```tsx
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`:

```tsx
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:

```typescript

const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
  <div>{props.title}{children}</div>
);
```

## Hooks

### useUIStream

```typescript
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

```typescript
const {
  state,   // StateModel (Record<string, unknown>)
  get,     // (path: string) => unknown
  set,     // (path: string, value: unknown) => void
  update,  // (updates: Record<string, unknown>) => 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<void>
```

### useAction

```typescript
const { execute, isLoading } = useAction(binding: ActionBinding);
// execute() => Promise<void>
```

### 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<T>(
  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<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 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<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`.

```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

const spec = buildSpecFromParts(message.parts);   // Spec | null
const text = getTextFromParts(message.parts);      // string
```