@@ -14,82 +14,86 @@ export default function RegistryPage() {
1414 life.
1515 </ p >
1616
17- { /* Components Section */ }
18- < h2 className = "text-xl font-semibold mt-12 mb-4" > Component Registry </ h2 >
17+ { /* defineRegistry */ }
18+ < h2 className = "text-xl font-semibold mt-12 mb-4" > defineRegistry </ h2 >
1919 < p className = "text-sm text-muted-foreground mb-4" >
20- Create a registry that maps catalog component types to React components:
20+ Use < code > defineRegistry</ code > to create a type-safe registry from your
21+ catalog. Pass your components, actions, or both in a single call:
2122 </ p >
22- < Code lang = "tsx" > { `import { useAction } from '@json-render/react';
23-
24- const registry = {
25- Card: ({ props, children }) => (
26- <div className="card">
27- <h2>{ props.title}</h2>
28- {props.description && <p>{props.description}</p>}
29- {children}
30- </div>
31- ),
32-
33- Button: ({ props }) => {
34- const executeAction = useAction(props.action);
35- return (
36- <button onClick={() => executeAction({ })}>
23+ < Code lang = "tsx" > { `import { defineRegistry } from '@json-render/react';
24+ import { myCatalog } from './catalog';
25+
26+ export const { registry, handlers, executeAction } = defineRegistry(myCatalog, {
27+ components: {
28+ Card: ({ props, children }) => (
29+ <div className="card">
30+ <h2>{props.title}</h2>
31+ {props.description && <p>{props.description}</p>}
32+ {children}
33+ </div>
34+ ),
35+
36+ Button: ({ props, onAction }) => (
37+ <button onClick={() => onAction?.({ name: props.action })}>
3738 {props.label}
3839 </button>
39- );
40+ ),
4041 },
41- };` } </ Code >
4242
43+ actions: {
44+ submit_form: async (params, setData) => {
45+ const res = await fetch('/api/submit', {
46+ method: 'POST',
47+ body: JSON.stringify(params),
48+ });
49+ const result = await res.json();
50+ setData((prev) => ({ ...prev, formResult: result }));
51+ },
52+
53+ export_data: async (params) => {
54+ const blob = await generateExport(params.format);
55+ downloadBlob(blob, \`export.\${params.format}\`);
56+ },
57+ },
58+ });` } </ Code >
59+
60+ < p className = "text-sm text-muted-foreground mt-4 mb-4" >
61+ The returned object contains:
62+ </ p >
63+ < ul className = "list-disc list-inside text-sm text-muted-foreground mb-4 space-y-1" >
64+ < li >
65+ < code > registry</ code > - component registry for{ " " }
66+ < code > { "<Renderer />" } </ code >
67+ </ li >
68+ < li >
69+ < code > handlers</ code > - factory for ActionProvider-compatible handlers
70+ </ li >
71+ < li >
72+ < code > executeAction</ code > - imperative action dispatch (for use
73+ outside the React tree)
74+ </ li >
75+ </ ul >
76+
77+ { /* Component Props */ }
4378 < h2 className = "text-xl font-semibold mt-12 mb-4" > Component Props</ h2 >
4479 < p className = "text-sm text-muted-foreground mb-4" >
45- Each component receives these props:
80+ Each component in the registry receives a < code > ComponentContext</ code > { " " }
81+ object:
4682 </ p >
47- < Code lang = "typescript" > { `interface ComponentProps<T = Record<string, unknown>> {
48- props: T; // Component props from the spec
83+ < Code lang = "typescript" > { `interface ComponentContext {
84+ props: T; // Type-safe props from your catalog
4985 children?: React.ReactNode; // Rendered children (for slot components)
50- }
51-
52- // Type-safe props by extracting from your catalog
53- type CardProps = ComponentProps<{
54- title: string;
55- description: string | null;
56- }>;
57-
58- // Use hooks for actions and data within components
59- import { useAction, useDataValue } from '@json-render/react';` } </ Code >
86+ onAction?: (action: ActionTrigger) => void; // Dispatch an action
87+ loading?: boolean; // Whether the renderer is in a loading state
88+ }` } </ Code >
6089
61- < h2 className = "text-xl font-semibold mt-12 mb-4" > Using Data Binding</ h2 >
62- < p className = "text-sm text-muted-foreground mb-4" >
63- Use hooks to read and write data:
90+ < p className = "text-sm text-muted-foreground mt-4 mb-4" >
91+ Props are automatically inferred from your catalog, so{ " " }
92+ < code > props.title</ code > is typed as < code > string</ code > if your catalog
93+ defines it that way.
6494 </ p >
65- < Code lang = "tsx" > { `import { useDataValue, useDataBinding } from '@json-render/react';
66-
67- const Metric = ({ props }) => {
68- // Read-only value from data context
69- const value = useDataValue(props.valuePath);
70-
71- return (
72- <div className="metric">
73- <span className="label">{props.label}</span>
74- <span className="value">{formatValue(value)}</span>
75- </div>
76- );
77- };
7895
79- const TextField = ({ props }) => {
80- // Two-way binding to data context
81- const [value, setValue] = useDataBinding(props.valuePath);
82-
83- return (
84- <input
85- value={value || ''}
86- onChange={(e) => setValue(e.target.value)}
87- placeholder={props.placeholder}
88- />
89- );
90- };` } </ Code >
91-
92- { /* Actions Section */ }
96+ { /* Action Handlers */ }
9397 < h2 className = "text-xl font-semibold mt-12 mb-4" > Action Handlers</ h2 >
9498 < p className = "text-sm text-muted-foreground mb-4" >
9599 Instead of AI generating arbitrary code, it declares < em > intent</ em > by
@@ -117,9 +121,6 @@ const catalog = defineCatalog(schema, {
117121 export_data: {
118122 params: z.object({
119123 format: z.enum(['csv', 'pdf', 'json']),
120- filters: z.object({
121- dateRange: z.string().nullable(),
122- }).nullable(),
123124 }),
124125 },
125126 navigate: {
@@ -130,78 +131,104 @@ const catalog = defineCatalog(schema, {
130131 },
131132});` } </ Code >
132133
133- < h3 className = "text-lg font-medium mt-8 mb-3" > ActionProvider</ h3 >
134+ < h3 className = "text-lg font-medium mt-8 mb-3" >
135+ Implementing Action Handlers
136+ </ h3 >
134137 < p className = "text-sm text-muted-foreground mb-4" >
135- Provide action handlers to your app:
138+ Action handlers receive < code > (params, setData, data)</ code > and are
139+ defined inside < code > defineRegistry</ code > :
136140 </ p >
137- < Code lang = "tsx" > { `import { ActionProvider } from '@json-render/react';
138-
139- function App() {
140- const handlers = {
141- submit_form: async (params) => {
141+ < Code lang = "tsx" > { `export const { handlers, executeAction } = defineRegistry(catalog, {
142+ actions: {
143+ submit_form: async (params, setData) => {
142144 const response = await fetch('/api/submit', {
143145 method: 'POST',
144146 body: JSON.stringify({ formId: params.formId }),
145147 });
146- return response.json();
148+ const result = await response.json();
149+ setData((prev) => ({ ...prev, formResult: result }));
147150 },
148-
151+
149152 export_data: async (params) => {
150- const blob = await generateExport(params.format, params.filters );
153+ const blob = await generateExport(params.format);
151154 downloadBlob(blob, \`export.\${params.format}\`);
152155 },
153-
156+
154157 navigate: (params) => {
155158 window.location.href = params.url;
156159 },
157- };
160+ },
161+ });` } </ Code >
158162
159- return (
160- <ActionProvider handlers={handlers}>
161- {/* Your UI */}
162- </ActionProvider>
163- );
164- }` } </ Code >
163+ { /* Using Data Binding */ }
164+ < h2 className = "text-xl font-semibold mt-12 mb-4" > Using Data Binding</ h2 >
165+ < p className = "text-sm text-muted-foreground mb-4" >
166+ Use hooks inside your registry components to read and write data:
167+ </ p >
168+ < Code lang = "tsx" > { `import { useData } from '@json-render/react';
169+ import { getByPath } from '@json-render/core';
165170
166- < h3 className = "text-lg font-medium mt-8 mb-3" >
167- Using Actions in Components
168- </ h3 >
169- < Code lang = "tsx" > { `import { useAction } from '@json-render/react';
171+ // Inside defineRegistry components:
172+
173+ Metric: ({ props }) => {
174+ const { data } = useData();
175+ const value = getByPath(data, props.valuePath);
170176
171- // Using the useAction hook (recommended)
172- const Button = ({ props }) => {
173- const executeAction = useAction(props.action);
174-
175177 return (
176- <button onClick={() => executeAction({})}>
177- {props.label}
178- </button>
178+ <div className="metric">
179+ <span className="label">{props.label}</span>
180+ <span className="value">{formatValue(value)}</span>
181+ </div>
179182 );
180- };
183+ },
184+
185+ TextField: ({ props }) => {
186+ const { data, set } = useData();
187+ const value = getByPath(data, props.valuePath) as string;
181188
182- // Or for standalone use
183- function SubmitButton() {
184- const submitForm = useAction('submit_form');
185-
186189 return (
187- <button onClick={() => submitForm({ formId: 'contact' })}>
188- Submit
189- </button>
190+ <input
191+ value={value || ''}
192+ onChange={(e) => set(props.valuePath, e.target.value)}
193+ placeholder={props.placeholder}
194+ />
190195 );
191- }` } </ Code >
196+ }, ` } </ Code >
192197
193198 { /* Renderer Section */ }
194199 < h2 className = "text-xl font-semibold mt-12 mb-4" > Using the Renderer</ h2 >
195- < Code lang = "tsx" > { `import { Renderer, ActionProvider } from '@json-render/react';
200+ < p className = "text-sm text-muted-foreground mb-4" >
201+ Wire everything together with providers and the{ " " }
202+ < code > { "<Renderer />" } </ code > component:
203+ </ p >
204+ < Code lang = "tsx" > { `import { useMemo, useRef } from 'react';
205+ import {
206+ Renderer,
207+ DataProvider,
208+ VisibilityProvider,
209+ ActionProvider,
210+ } from '@json-render/react';
211+ import { registry, handlers } from './registry';
212+
213+ function App({ spec, data, setData }) {
214+ const dataRef = useRef(data);
215+ const setDataRef = useRef(setData);
216+ dataRef.current = data;
217+ setDataRef.current = setData;
218+
219+ const actionHandlers = useMemo(
220+ () => handlers(() => setDataRef.current, () => dataRef.current),
221+ [],
222+ );
196223
197- function App() {
198224 return (
199- <ActionProvider handlers={actionHandlers}>
200- <Renderer
201- spec={uiSpec}
202- registry={registry}
203- />
204- </ActionProvider>
225+ <DataProvider initialData={data}>
226+ <VisibilityProvider>
227+ <ActionProvider handlers={actionHandlers}>
228+ <Renderer spec={spec} registry={registry} />
229+ </ActionProvider>
230+ </VisibilityProvider>
231+ </DataProvider>
205232 );
206233}` } </ Code >
207234
0 commit comments