Streaming
Progressively render UI as AI generates it.
How Streaming Works
json-render uses JSONL (JSON Lines) streaming. As AI generates, each line represents a patch operation:
{"op":"set","path":"/root","value":{"key":"root","type":"Card","props":{"title":"Dashboard"}}}
{"op":"add","path":"/root/children","value":{"key":"metric-1","type":"Metric","props":{"label":"Revenue"}}}
{"op":"add","path":"/root/children","value":{"key":"metric-2","type":"Metric","props":{"label":"Users"}}}useUIStream Hook
The hook handles parsing and state management:
import { useUIStream } from '@json-render/react';
function App() {
const {
tree, // Current UI tree state
isLoading, // True while streaming
error, // Any error that occurred
generate, // Function to start generation
abort, // Function to cancel streaming
} = useUIStream({
endpoint: '/api/generate',
});
}Patch Operations
Supported operations:
set— Set the value at a path (creates if needed)add— Add to an array at a pathreplace— Replace value at a pathremove— Remove value at a path
Path Format
Paths use a key-based format for elements:
/root -> Root element
/root/children -> Children of root
/elements/card-1 -> Element with key "card-1"
/elements/card-1/children -> Children of card-1Server-Side Setup
Ensure your API route streams properly:
export async function POST(req: Request) {
const { prompt } = await req.json();
const result = streamText({
model: 'anthropic/claude-opus-4.5',
system: generateCatalogPrompt(catalog),
prompt,
});
// Return as a streaming response
return new Response(result.textStream, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Transfer-Encoding': 'chunked',
'Cache-Control': 'no-cache',
},
});
}Progressive Rendering
The Renderer automatically updates as the tree changes:
function App() {
const { tree, isLoading } = useUIStream({ endpoint: '/api/generate' });
return (
<div>
{isLoading && <LoadingIndicator />}
<Renderer tree={tree} registry={registry} />
</div>
);
}Aborting Streams
function App() {
const { isLoading, generate, abort } = useUIStream({
endpoint: '/api/generate',
});
return (
<div>
<button onClick={() => generate('Create dashboard')}>
Generate
</button>
{isLoading && (
<button onClick={abort}>Cancel</button>
)}
</div>
);
}