React Hooks
Hooks and components for connecting LLM output to your React UI. All exports come from @genuikit/react unless noted otherwise.
useGenerativeUI
The primary hook for resolving raw LLM output into a validated, rendered React element. It validates the output against the registry, renders the matching component, and produces a correction prompt when validation fails so you can feed it back to the model for a retry.
| 1 | import { useGenerativeUI } from '@genuikit/react'; |
Parameters
| Param | Type | Description |
|---|---|---|
| registry | ComponentRegistry | The registry containing your component schemas and implementations. |
| output | LLMOutput | Raw LLM output object with type and props fields. |
| options? | UseGenerativeUIOptions | Optional config: strict (fail on extra props), fallback (element to render on failure). |
Return Value
| Field | Type | Description |
|---|---|---|
| element | React.ReactElement | null | The rendered component, or null if validation failed. |
| ok | boolean | Whether the output passed schema validation and rendered successfully. |
| correctionPrompt | string | null | A prompt describing validation errors. Send this back to the LLM for self-correction. |
| errors | ZodError[] | null | Raw Zod validation errors when validation fails, null otherwise. |
Example
A chat message component that renders LLM output or retries on validation failure:
| 1 | import { useGenerativeUI } from '@genuikit/react'; |
| 2 | import { registry } from '../registry'; |
| 3 | |
| 4 | interface ChatMessageProps { |
| 5 | output: { type: string; props: Record<string, unknown> }; |
| 6 | onRetry: (correctionPrompt: string) => void; |
| 7 | } |
| 8 | |
| 9 | export function ChatMessage({ output, onRetry }: ChatMessageProps) { |
| 10 | const { element, ok, correctionPrompt, errors } = useGenerativeUI( |
| 11 | registry, |
| 12 | output, |
| 13 | { strict: true } |
| 14 | ); |
| 15 | |
| 16 | if (!ok) { |
| 17 | // Automatically send correction prompt back to the LLM |
| 18 | if (correctionPrompt) { |
| 19 | onRetry(correctionPrompt); |
| 20 | } |
| 21 | return ( |
| 22 | <div className="rounded-lg border border-red-500/20 bg-red-500/5 p-4"> |
| 23 | <p className="text-sm text-red-400"> |
| 24 | Component failed to render. Retrying... |
| 25 | </p> |
| 26 | {errors && ( |
| 27 | <pre className="mt-2 text-xs text-red-300/70"> |
| 28 | {errors.map((e) => e.message).join('\n')} |
| 29 | </pre> |
| 30 | )} |
| 31 | </div> |
| 32 | ); |
| 33 | } |
| 34 | |
| 35 | return ( |
| 36 | <div className="rounded-lg border p-4"> |
| 37 | {element} |
| 38 | </div> |
| 39 | ); |
| 40 | } |
GenerativeUI Component
A declarative component wrapper around useGenerativeUI. Use it when you prefer JSX over hooks, or when you want to handle errors via callbacks instead of conditional rendering.
| 1 | import { GenerativeUI } from '@genuikit/react'; |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| registry | ComponentRegistry | -- | Component registry with schemas and implementations. |
| output | LLMOutput | -- | Raw output from the LLM to validate and render. |
| fallback? | React.ReactNode | null | Rendered when validation fails and no onError handler returns JSX. |
| onError? | (prompt: string, errors: ZodError[]) => void | -- | Called when validation fails. Receives the correction prompt and raw errors. |
| strict? | boolean | false | When true, extra props not in the schema cause a validation error. |
Declarative Usage
| 1 | import { GenerativeUI } from '@genuikit/react'; |
| 2 | import { registry } from '../registry'; |
| 3 | |
| 4 | function MessageList({ messages }) { |
| 5 | return ( |
| 6 | <div className="space-y-4"> |
| 7 | {messages.map((msg) => ( |
| 8 | <GenerativeUI |
| 9 | key={msg.id} |
| 10 | registry={registry} |
| 11 | output={msg.output} |
| 12 | fallback={<p className="text-gray-400">Could not render component.</p>} |
| 13 | onError={(prompt, errors) => { |
| 14 | console.error('Validation failed:', errors); |
| 15 | // Send correction prompt back to LLM |
| 16 | retryMessage(msg.id, prompt); |
| 17 | }} |
| 18 | /> |
| 19 | ))} |
| 20 | </div> |
| 21 | ); |
| 22 | } |
Hook vs. Component
Use useGenerativeUI when you need fine-grained control over the render cycle (conditional retries, custom state). Use GenerativeUI for straightforward rendering where callbacks are sufficient for error handling.
DevGenerativeUI
A development-only wrapper that adds an error overlay when LLM output fails validation. It renders a visual diff of expected vs. received props, making it easy to debug schema mismatches during development. In production builds, it tree-shakes to zero -- the component simply renders its children with no overhead.
| 1 | import { DevGenerativeUI } from '@genuikit/react'; |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| registry | ComponentRegistry | -- | Component registry for validation. |
| output | LLMOutput | -- | Raw LLM output to validate and render. |
| showOverlay? | boolean | true | Toggle the error overlay. Set to false to disable the visual overlay while still logging errors to console. |
| children? | React.ReactNode | -- | Fallback content rendered when validation fails (behind the overlay). |
Usage
| 1 | import { DevGenerativeUI } from '@genuikit/react'; |
| 2 | import { registry } from '../registry'; |
| 3 | |
| 4 | function DevChatMessage({ output }) { |
| 5 | return ( |
| 6 | <DevGenerativeUI |
| 7 | registry={registry} |
| 8 | output={output} |
| 9 | showOverlay={true} |
| 10 | > |
| 11 | <p className="text-gray-400">Fallback content</p> |
| 12 | </DevGenerativeUI> |
| 13 | ); |
| 14 | } |
| 15 | |
| 16 | // The overlay shows: |
| 17 | // - Component type received vs. registered types |
| 18 | // - Props diff (expected schema vs. received JSON) |
| 19 | // - Zod validation error paths |
| 20 | // - Copy-friendly correction prompt |
Production Behavior
In production (process.env.NODE_ENV === 'production'), DevGenerativeUI tree-shakes to a pass-through component that adds zero bytes to your bundle. You can safely leave it in your code without conditional imports.
ErrorOverlay
A standalone error overlay component for building custom error UIs. Unlike DevGenerativeUI which handles the full validation lifecycle, ErrorOverlay is a presentational component you feed error data to directly.
| 1 | import { ErrorOverlay } from '@genuikit/react'; |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| errors | ZodError[] | -- | Array of Zod validation errors to display. |
| correctionPrompt? | string | -- | The generated correction prompt, displayed in a copyable block. |
| componentType? | string | -- | The component type that failed, shown in the header. |
| onDismiss? | () => void | -- | Called when the user dismisses the overlay. |
| className? | string | -- | Additional CSS classes for the overlay container. |
Custom Error Handling
| 1 | import { useGenerativeUI, ErrorOverlay } from '@genuikit/react'; |
| 2 | import { registry } from '../registry'; |
| 3 | import { useState } from 'react'; |
| 4 | |
| 5 | function ChatMessageWithOverlay({ output }) { |
| 6 | const [dismissed, setDismissed] = useState(false); |
| 7 | const { element, ok, errors, correctionPrompt } = useGenerativeUI( |
| 8 | registry, |
| 9 | output |
| 10 | ); |
| 11 | |
| 12 | if (!ok && !dismissed) { |
| 13 | return ( |
| 14 | <ErrorOverlay |
| 15 | errors={errors!} |
| 16 | correctionPrompt={correctionPrompt ?? undefined} |
| 17 | componentType={output.type} |
| 18 | onDismiss={() => setDismissed(true)} |
| 19 | /> |
| 20 | ); |
| 21 | } |
| 22 | |
| 23 | if (!ok) { |
| 24 | return <p className="text-gray-500">Component could not be rendered.</p>; |
| 25 | } |
| 26 | |
| 27 | return <div className="message">{element}</div>; |
| 28 | } |
useStreamingUI
A hook for progressive rendering of LLM output as it streams in. It incrementally parses partial JSON from a streaming endpoint, validates each chunk against the registry, and re-renders the component as more data arrives. Ideal for real-time chat interfaces where you want to show content before the response is complete.
| 1 | import { useStreamingUI } from '@genuikit/react'; |
Options
| Option | Type | Default | Description |
|---|---|---|---|
| registry | ComponentRegistry | -- | Component registry for validation and rendering. |
| url | string | -- | The streaming API endpoint URL. |
| body? | Record<string, unknown> | -- | Request body sent as JSON POST to the streaming endpoint. |
| skeleton? | React.ReactNode | -- | Shown while waiting for the first parseable chunk. |
| fallback? | React.ReactNode | null | Rendered if the stream completes but validation fails. |
| retry? | { maxAttempts: number; delay: number } | { maxAttempts: 0, delay: 1000 } | Auto-retry configuration. Set maxAttempts > 0 to enable retries on stream failure. |
| onComplete? | (result: StreamResult) => void | -- | Called when the stream finishes. Receives the final validated output and any errors. |
Return Value
| Field | Type | Description |
|---|---|---|
| element | React.ReactElement | null | The progressively rendered component. Updates as new chunks arrive. |
| status | 'idle' | 'streaming' | 'complete' | 'error' | Current state of the stream. |
| start | () => void | Manually start or restart the stream. |
| abort | () => void | Cancel the in-progress stream. |
| error | Error | null | The error object if the stream failed. |
Streaming Example
| 1 | import { useStreamingUI } from '@genuikit/react'; |
| 2 | import { registry } from '../registry'; |
| 3 | |
| 4 | function StreamingChat({ question }: { question: string }) { |
| 5 | const { element, status, start, abort, error } = useStreamingUI({ |
| 6 | registry, |
| 7 | url: '/api/chat/stream', |
| 8 | body: { |
| 9 | question, |
| 10 | tools: registry.toToolDefinition(), |
| 11 | }, |
| 12 | skeleton: ( |
| 13 | <div className="animate-pulse space-y-2"> |
| 14 | <div className="h-4 bg-gray-700 rounded w-3/4" /> |
| 15 | <div className="h-4 bg-gray-700 rounded w-1/2" /> |
| 16 | </div> |
| 17 | ), |
| 18 | fallback: <p className="text-red-400">Failed to render streamed output.</p>, |
| 19 | retry: { maxAttempts: 2, delay: 1500 }, |
| 20 | onComplete: (result) => { |
| 21 | console.log('Stream finished:', result.ok ? 'success' : 'failed'); |
| 22 | if (result.correctionPrompt) { |
| 23 | // Optionally send correction back to the model |
| 24 | console.log('Correction:', result.correctionPrompt); |
| 25 | } |
| 26 | }, |
| 27 | }); |
| 28 | |
| 29 | return ( |
| 30 | <div> |
| 31 | <div className="flex items-center gap-2 mb-4"> |
| 32 | {status === 'idle' && ( |
| 33 | <button onClick={start} className="px-3 py-1 rounded bg-blue-600"> |
| 34 | Send |
| 35 | </button> |
| 36 | )} |
| 37 | {status === 'streaming' && ( |
| 38 | <button onClick={abort} className="px-3 py-1 rounded bg-red-600"> |
| 39 | Stop |
| 40 | </button> |
| 41 | )} |
| 42 | {status === 'streaming' && ( |
| 43 | <span className="text-sm text-gray-400 animate-pulse"> |
| 44 | Streaming... |
| 45 | </span> |
| 46 | )} |
| 47 | </div> |
| 48 | |
| 49 | {element} |
| 50 | |
| 51 | {error && ( |
| 52 | <p className="text-sm text-red-400 mt-2"> |
| 53 | Stream error: {error.message} |
| 54 | </p> |
| 55 | )} |
| 56 | </div> |
| 57 | ); |
| 58 | } |
useCoAgent
A bidirectional sync hook that keeps your React state in sync with an LLM agent. The agent can read and write state through tool calls, and your UI can dispatch actions back to the agent. This enables collaborative workflows where both the user and the agent contribute to a shared state.
| 1 | import { useCoAgent } from '@genuikit/react'; |
Parameters
| Param | Type | Description |
|---|---|---|
| agentId | string | Unique identifier for the agent session. |
| initialState | T | The initial shared state object. |
| options? | CoAgentOptions<T> | Configuration: onStateChange, onToolCall, optimisticUpdates. |
Return Value
| Field | Type | Description |
|---|---|---|
| state | T | The current shared state, updated by both the user and the agent. |
| dispatch | (action: CoAction) => void | Send an action to the agent. The agent receives it as a tool call result. |
| history | CoAction[] | Full action history from both the user and the agent. |
| lastToolCall | ToolCall | null | The most recent tool call from the agent, useful for showing agent activity. |
| isAgentActive | boolean | Whether the agent is currently processing a response. |
Form Example
A booking form where the user fills in fields and the agent suggests or auto-completes values based on context:
| 1 | import { useCoAgent } from '@genuikit/react'; |
| 2 | |
| 3 | interface BookingState { |
| 4 | destination: string; |
| 5 | checkIn: string; |
| 6 | checkOut: string; |
| 7 | guests: number; |
| 8 | notes: string; |
| 9 | } |
| 10 | |
| 11 | const initialState: BookingState = { |
| 12 | destination: '', |
| 13 | checkIn: '', |
| 14 | checkOut: '', |
| 15 | guests: 1, |
| 16 | notes: '', |
| 17 | }; |
| 18 | |
| 19 | export function BookingForm({ agentId }: { agentId: string }) { |
| 20 | const { state, dispatch, lastToolCall, isAgentActive } = useCoAgent<BookingState>({ |
| 21 | agentId, |
| 22 | initialState, |
| 23 | options: { |
| 24 | onStateChange: (prev, next) => { |
| 25 | console.log('State changed:', { prev, next }); |
| 26 | }, |
| 27 | onToolCall: (toolCall) => { |
| 28 | console.log('Agent called tool:', toolCall.name); |
| 29 | }, |
| 30 | optimisticUpdates: true, |
| 31 | }, |
| 32 | }); |
| 33 | |
| 34 | const handleFieldChange = (field: keyof BookingState, value: string | number) => { |
| 35 | dispatch({ |
| 36 | type: 'UPDATE_FIELD', |
| 37 | payload: { field, value }, |
| 38 | }); |
| 39 | }; |
| 40 | |
| 41 | return ( |
| 42 | <form className="space-y-4 max-w-md"> |
| 43 | {isAgentActive && ( |
| 44 | <div className="text-sm text-blue-400 animate-pulse"> |
| 45 | Agent is working... |
| 46 | {lastToolCall && <span> ({lastToolCall.name})</span>} |
| 47 | </div> |
| 48 | )} |
| 49 | |
| 50 | <div> |
| 51 | <label className="block text-sm font-medium mb-1">Destination</label> |
| 52 | <input |
| 53 | type="text" |
| 54 | value={state.destination} |
| 55 | onChange={(e) => handleFieldChange('destination', e.target.value)} |
| 56 | className="w-full rounded border px-3 py-2" |
| 57 | placeholder="Where are you going?" |
| 58 | /> |
| 59 | </div> |
| 60 | |
| 61 | <div className="grid grid-cols-2 gap-3"> |
| 62 | <div> |
| 63 | <label className="block text-sm font-medium mb-1">Check-in</label> |
| 64 | <input |
| 65 | type="date" |
| 66 | value={state.checkIn} |
| 67 | onChange={(e) => handleFieldChange('checkIn', e.target.value)} |
| 68 | className="w-full rounded border px-3 py-2" |
| 69 | /> |
| 70 | </div> |
| 71 | <div> |
| 72 | <label className="block text-sm font-medium mb-1">Check-out</label> |
| 73 | <input |
| 74 | type="date" |
| 75 | value={state.checkOut} |
| 76 | onChange={(e) => handleFieldChange('checkOut', e.target.value)} |
| 77 | className="w-full rounded border px-3 py-2" |
| 78 | /> |
| 79 | </div> |
| 80 | </div> |
| 81 | |
| 82 | <div> |
| 83 | <label className="block text-sm font-medium mb-1">Guests</label> |
| 84 | <input |
| 85 | type="number" |
| 86 | min={1} |
| 87 | value={state.guests} |
| 88 | onChange={(e) => handleFieldChange('guests', parseInt(e.target.value))} |
| 89 | className="w-full rounded border px-3 py-2" |
| 90 | /> |
| 91 | </div> |
| 92 | |
| 93 | <div> |
| 94 | <label className="block text-sm font-medium mb-1">Notes from agent</label> |
| 95 | <textarea |
| 96 | value={state.notes} |
| 97 | readOnly |
| 98 | className="w-full rounded border px-3 py-2 bg-gray-50 text-gray-600" |
| 99 | rows={3} |
| 100 | /> |
| 101 | </div> |
| 102 | |
| 103 | <button |
| 104 | type="submit" |
| 105 | className="w-full rounded bg-blue-600 py-2 text-white font-medium" |
| 106 | > |
| 107 | Book Now |
| 108 | </button> |
| 109 | </form> |
| 110 | ); |
| 111 | } |
CoAgentProvider
A provider component that sets up the CoAgent context for a subtree. It initializes the bidirectional connection, manages the action registry, and provides dispatch capabilities to all descendant useCoAgent hooks. Wrap your app (or a section of it) with this provider before using the hook.
| 1 | import { CoAgentProvider } from '@genuikit/react'; |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| endpoint | string | -- | WebSocket or HTTP endpoint for the agent backend. |
| registry | ActionRegistry | -- | Registry of actions the agent can dispatch. Maps action types to handlers. |
| dispatchOptions? | DispatchOptions | -- | Config for dispatching: throttleMs (debounce user actions), queueSize (max pending actions). |
| onConnect? | () => void | -- | Called when the connection to the agent backend is established. |
| onDisconnect? | (reason: string) => void | -- | Called when the connection drops. Receives a reason string. |
| onError? | (error: Error) => void | -- | Global error handler for agent communication failures. |
| children | React.ReactNode | -- | Child components that can use useCoAgent. |
Provider Setup
| 1 | import { CoAgentProvider, ActionRegistry } from '@genuikit/react'; |
| 2 | import { BookingForm } from './booking-form'; |
| 3 | |
| 4 | // Define the actions the agent can perform |
| 5 | const actionRegistry = new ActionRegistry(); |
| 6 | |
| 7 | actionRegistry.register('UPDATE_FIELD', { |
| 8 | description: 'Update a form field value', |
| 9 | handler: (state, payload) => ({ |
| 10 | ...state, |
| 11 | [payload.field]: payload.value, |
| 12 | }), |
| 13 | }); |
| 14 | |
| 15 | actionRegistry.register('SUGGEST_DATES', { |
| 16 | description: 'Suggest check-in and check-out dates', |
| 17 | handler: (state, payload) => ({ |
| 18 | ...state, |
| 19 | checkIn: payload.checkIn, |
| 20 | checkOut: payload.checkOut, |
| 21 | }), |
| 22 | }); |
| 23 | |
| 24 | actionRegistry.register('ADD_NOTE', { |
| 25 | description: 'Add a note or suggestion', |
| 26 | handler: (state, payload) => ({ |
| 27 | ...state, |
| 28 | notes: state.notes |
| 29 | ? state.notes + '\n' + payload.note |
| 30 | : payload.note, |
| 31 | }), |
| 32 | }); |
| 33 | |
| 34 | export function AppLayout() { |
| 35 | return ( |
| 36 | <CoAgentProvider |
| 37 | endpoint="/api/agent/ws" |
| 38 | registry={actionRegistry} |
| 39 | dispatchOptions={{ |
| 40 | throttleMs: 300, |
| 41 | queueSize: 50, |
| 42 | }} |
| 43 | onConnect={() => console.log('Agent connected')} |
| 44 | onDisconnect={(reason) => console.warn('Disconnected:', reason)} |
| 45 | onError={(error) => console.error('Agent error:', error)} |
| 46 | > |
| 47 | <div className="max-w-4xl mx-auto p-8"> |
| 48 | <h1 className="text-2xl font-bold mb-6">AI Travel Booking</h1> |
| 49 | <BookingForm agentId="booking-agent-1" /> |
| 50 | </div> |
| 51 | </CoAgentProvider> |
| 52 | ); |
| 53 | } |