import Link from "next/link"; import { Code } from "@/components/code"; export const metadata = { title: "Registry | json-render", }; export default function RegistryPage() { return (

Registry

Register React components and action handlers to bring your catalog to life.

{/* defineRegistry */}

defineRegistry

Use defineRegistry to create a type-safe registry from your catalog. Pass your components, actions, or both in a single call:

{`import { defineRegistry } from '@json-render/react'; import { myCatalog } from './catalog'; export const { registry, handlers, executeAction } = defineRegistry(myCatalog, { components: { Card: ({ props, children }) => (

{props.title}

{props.description &&

{props.description}

} {children}
), Button: ({ props, onAction }) => ( ), }, actions: { submit_form: async (params, setData) => { const res = await fetch('/api/submit', { method: 'POST', body: JSON.stringify(params), }); const result = await res.json(); setData((prev) => ({ ...prev, formResult: result })); }, export_data: async (params) => { const blob = await generateExport(params.format); downloadBlob(blob, \`export.\${params.format}\`); }, }, });`}

The returned object contains:

{/* Component Props */}

Component Props

Each component in the registry receives a ComponentContext{" "} object:

{`interface ComponentContext { props: T; // Type-safe props from your catalog children?: React.ReactNode; // Rendered children (for slot components) onAction?: (action: ActionTrigger) => void; // Dispatch an action loading?: boolean; // Whether the renderer is in a loading state }`}

Props are automatically inferred from your catalog, so{" "} props.title is typed as string if your catalog defines it that way.

{/* Action Handlers */}

Action Handlers

Instead of AI generating arbitrary code, it declares intent by name. Your application provides the implementation. This is a core guardrail.

Defining Actions

Define available actions in your catalog:

{`import { defineCatalog } from '@json-render/core'; import { schema } from '@json-render/react'; import { z } from 'zod'; const catalog = defineCatalog(schema, { components: { /* ... */ }, actions: { submit_form: { params: z.object({ formId: z.string(), }), description: 'Submit a form', }, export_data: { params: z.object({ format: z.enum(['csv', 'pdf', 'json']), }), }, navigate: { params: z.object({ url: z.string(), }), }, }, });`}

Implementing Action Handlers

Action handlers receive (params, setData, data) and are defined inside defineRegistry:

{`export const { handlers, executeAction } = defineRegistry(catalog, { actions: { submit_form: async (params, setData) => { const response = await fetch('/api/submit', { method: 'POST', body: JSON.stringify({ formId: params.formId }), }); const result = await response.json(); setData((prev) => ({ ...prev, formResult: result })); }, export_data: async (params) => { const blob = await generateExport(params.format); downloadBlob(blob, \`export.\${params.format}\`); }, navigate: (params) => { window.location.href = params.url; }, }, });`} {/* Using Data Binding */}

Using Data Binding

Use hooks inside your registry components to read and write data:

{`import { useData } from '@json-render/react'; import { getByPath } from '@json-render/core'; // Inside defineRegistry components: Metric: ({ props }) => { const { data } = useData(); const value = getByPath(data, props.valuePath); return (
{props.label} {formatValue(value)}
); }, TextField: ({ props }) => { const { data, set } = useData(); const value = getByPath(data, props.valuePath) as string; return ( set(props.valuePath, e.target.value)} placeholder={props.placeholder} /> ); },`}
{/* Renderer Section */}

Using the Renderer

Wire everything together with providers and the{" "} {""} component:

{`import { useMemo, useRef } from 'react'; import { Renderer, DataProvider, VisibilityProvider, ActionProvider, } from '@json-render/react'; import { registry, handlers } from './registry'; function App({ spec, data, setData }) { const dataRef = useRef(data); const setDataRef = useRef(setData); dataRef.current = data; setDataRef.current = setData; const actionHandlers = useMemo( () => handlers(() => setDataRef.current, () => dataRef.current), [], ); return ( ); }`}

Next

Learn about{" "} data binding {" "} for dynamic values.

); }