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.

1import { useGenerativeUI } from '@genuikit/react';

Parameters

ParamTypeDescription
registryComponentRegistryThe registry containing your component schemas and implementations.
outputLLMOutputRaw LLM output object with type and props fields.
options?UseGenerativeUIOptionsOptional config: strict (fail on extra props), fallback (element to render on failure).

Return Value

FieldTypeDescription
elementReact.ReactElement | nullThe rendered component, or null if validation failed.
okbooleanWhether the output passed schema validation and rendered successfully.
correctionPromptstring | nullA prompt describing validation errors. Send this back to the LLM for self-correction.
errorsZodError[] | nullRaw Zod validation errors when validation fails, null otherwise.

Example

A chat message component that renders LLM output or retries on validation failure:

chat-message.tsxtsx
1import { useGenerativeUI } from '@genuikit/react';
2import { registry } from '../registry';
3 
4interface ChatMessageProps {
5 output: { type: string; props: Record<string, unknown> };
6 onRetry: (correctionPrompt: string) => void;
7}
8 
9export 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.

1import { GenerativeUI } from '@genuikit/react';

Props

PropTypeDefaultDescription
registryComponentRegistry--Component registry with schemas and implementations.
outputLLMOutput--Raw output from the LLM to validate and render.
fallback?React.ReactNodenullRendered 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?booleanfalseWhen true, extra props not in the schema cause a validation error.

Declarative Usage

message-list.tsxtsx
1import { GenerativeUI } from '@genuikit/react';
2import { registry } from '../registry';
3 
4function 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.

1import { DevGenerativeUI } from '@genuikit/react';

Props

PropTypeDefaultDescription
registryComponentRegistry--Component registry for validation.
outputLLMOutput--Raw LLM output to validate and render.
showOverlay?booleantrueToggle 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

dev-chat.tsxtsx
1import { DevGenerativeUI } from '@genuikit/react';
2import { registry } from '../registry';
3 
4function 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.

1import { ErrorOverlay } from '@genuikit/react';

Props

PropTypeDefaultDescription
errorsZodError[]--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

custom-error-ui.tsxtsx
1import { useGenerativeUI, ErrorOverlay } from '@genuikit/react';
2import { registry } from '../registry';
3import { useState } from 'react';
4 
5function 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.

1import { useStreamingUI } from '@genuikit/react';

Options

OptionTypeDefaultDescription
registryComponentRegistry--Component registry for validation and rendering.
urlstring--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.ReactNodenullRendered 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

FieldTypeDescription
elementReact.ReactElement | nullThe progressively rendered component. Updates as new chunks arrive.
status'idle' | 'streaming' | 'complete' | 'error'Current state of the stream.
start() => voidManually start or restart the stream.
abort() => voidCancel the in-progress stream.
errorError | nullThe error object if the stream failed.

Streaming Example

streaming-chat.tsxtsx
1import { useStreamingUI } from '@genuikit/react';
2import { registry } from '../registry';
3 
4function 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.

1import { useCoAgent } from '@genuikit/react';

Parameters

ParamTypeDescription
agentIdstringUnique identifier for the agent session.
initialStateTThe initial shared state object.
options?CoAgentOptions<T>Configuration: onStateChange, onToolCall, optimisticUpdates.

Return Value

FieldTypeDescription
stateTThe current shared state, updated by both the user and the agent.
dispatch(action: CoAction) => voidSend an action to the agent. The agent receives it as a tool call result.
historyCoAction[]Full action history from both the user and the agent.
lastToolCallToolCall | nullThe most recent tool call from the agent, useful for showing agent activity.
isAgentActivebooleanWhether 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:

booking-form.tsxtsx
1import { useCoAgent } from '@genuikit/react';
2 
3interface BookingState {
4 destination: string;
5 checkIn: string;
6 checkOut: string;
7 guests: number;
8 notes: string;
9}
10 
11const initialState: BookingState = {
12 destination: '',
13 checkIn: '',
14 checkOut: '',
15 guests: 1,
16 notes: '',
17};
18 
19export 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.

1import { CoAgentProvider } from '@genuikit/react';

Props

PropTypeDefaultDescription
endpointstring--WebSocket or HTTP endpoint for the agent backend.
registryActionRegistry--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.
childrenReact.ReactNode--Child components that can use useCoAgent.

Provider Setup

app-layout.tsxtsx
1import { CoAgentProvider, ActionRegistry } from '@genuikit/react';
2import { BookingForm } from './booking-form';
3 
4// Define the actions the agent can perform
5const actionRegistry = new ActionRegistry();
6 
7actionRegistry.register('UPDATE_FIELD', {
8 description: 'Update a form field value',
9 handler: (state, payload) => ({
10 ...state,
11 [payload.field]: payload.value,
12 }),
13});
14 
15actionRegistry.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 
24actionRegistry.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 
34export 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}