page.mdx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. export const metadata = { title: "Registry" }
  2. # Registry
  3. Register React components and action handlers to bring your catalog to life.
  4. ## defineRegistry
  5. Use `defineRegistry` to create a type-safe registry from your catalog. Pass your components, actions, or both in a single call:
  6. ```tsx
  7. import { defineRegistry } from '@json-render/react';
  8. import { myCatalog } from './catalog';
  9. export const { registry, handlers, executeAction } = defineRegistry(myCatalog, {
  10. components: {
  11. Card: ({ props, children }) => (
  12. <div className="card">
  13. <h2>{props.title}</h2>
  14. {props.description && <p>{props.description}</p>}
  15. {children}
  16. </div>
  17. ),
  18. Button: ({ props, onAction }) => (
  19. <button onClick={() => onAction?.({ name: props.action })}>
  20. {props.label}
  21. </button>
  22. ),
  23. },
  24. actions: {
  25. submit_form: async (params, setState) => {
  26. const res = await fetch('/api/submit', {
  27. method: 'POST',
  28. body: JSON.stringify(params),
  29. });
  30. const result = await res.json();
  31. setState((prev) => ({ ...prev, formResult: result }));
  32. },
  33. export_data: async (params) => {
  34. const blob = await generateExport(params.format);
  35. downloadBlob(blob, `export.${params.format}`);
  36. },
  37. },
  38. });
  39. ```
  40. The returned object contains:
  41. - `registry` - component registry for `<Renderer />`
  42. - `handlers` - factory for ActionProvider-compatible handlers
  43. - `executeAction` - imperative action dispatch (for use outside the React tree)
  44. ## Component Props
  45. Each component in the registry receives a `ComponentContext` object:
  46. ```typescript
  47. interface ComponentContext {
  48. props: T; // Type-safe props from your catalog
  49. children?: React.ReactNode; // Rendered children (for slot components)
  50. onAction?: (action: ActionTrigger) => void; // Dispatch an action
  51. loading?: boolean; // Whether the renderer is in a loading state
  52. }
  53. ```
  54. Props are automatically inferred from your catalog, so `props.title` is typed as `string` if your catalog defines it that way.
  55. ## Action Handlers
  56. Instead of AI generating arbitrary code, it declares *intent* by name. Your application provides the implementation. This is a core guardrail.
  57. ### Defining Actions
  58. Define available actions in your catalog:
  59. ```typescript
  60. import { defineCatalog } from '@json-render/core';
  61. import { schema } from '@json-render/react';
  62. import { z } from 'zod';
  63. const catalog = defineCatalog(schema, {
  64. components: { /* ... */ },
  65. actions: {
  66. submit_form: {
  67. params: z.object({
  68. formId: z.string(),
  69. }),
  70. description: 'Submit a form',
  71. },
  72. export_data: {
  73. params: z.object({
  74. format: z.enum(['csv', 'pdf', 'json']),
  75. }),
  76. },
  77. navigate: {
  78. params: z.object({
  79. url: z.string(),
  80. }),
  81. },
  82. },
  83. });
  84. ```
  85. ### Implementing Action Handlers
  86. Action handlers receive `(params, setState, data)` and are defined inside `defineRegistry`:
  87. ```tsx
  88. export const { handlers, executeAction } = defineRegistry(catalog, {
  89. actions: {
  90. submit_form: async (params, setState) => {
  91. const response = await fetch('/api/submit', {
  92. method: 'POST',
  93. body: JSON.stringify({ formId: params.formId }),
  94. });
  95. const result = await response.json();
  96. setState((prev) => ({ ...prev, formResult: result }));
  97. },
  98. export_data: async (params) => {
  99. const blob = await generateExport(params.format);
  100. downloadBlob(blob, `export.${params.format}`);
  101. },
  102. navigate: (params) => {
  103. window.location.href = params.url;
  104. },
  105. },
  106. });
  107. ```
  108. ## Using Data Binding
  109. Use hooks inside your registry components to read and write data:
  110. ```tsx
  111. import { useStateStore } from '@json-render/react';
  112. import { getByPath } from '@json-render/core';
  113. // Inside defineRegistry components:
  114. Metric: ({ props }) => {
  115. const { data } = useStateStore();
  116. const value = getByPath(data, props.valuePath);
  117. return (
  118. <div className="metric">
  119. <span className="label">{props.label}</span>
  120. <span className="value">{formatValue(value)}</span>
  121. </div>
  122. );
  123. },
  124. TextField: ({ props }) => {
  125. const { data, set } = useStateStore();
  126. const value = getByPath(data, props.valuePath) as string;
  127. return (
  128. <input
  129. value={value || ''}
  130. onChange={(e) => set(props.valuePath, e.target.value)}
  131. placeholder={props.placeholder}
  132. />
  133. );
  134. },
  135. ```
  136. ## Using the Renderer
  137. Wire everything together with providers and the `<Renderer />` component:
  138. ```tsx
  139. import { useMemo, useRef } from 'react';
  140. import {
  141. Renderer,
  142. StateProvider,
  143. VisibilityProvider,
  144. ActionProvider,
  145. } from '@json-render/react';
  146. import { registry, handlers } from './registry';
  147. function App({ spec, data, setState }) {
  148. const dataRef = useRef(data);
  149. const setStateRef = useRef(setState);
  150. dataRef.current = data;
  151. setStateRef.current = setState;
  152. const actionHandlers = useMemo(
  153. () => handlers(() => setStateRef.current, () => dataRef.current),
  154. [],
  155. );
  156. return (
  157. <StateProvider initialState={data}>
  158. <VisibilityProvider>
  159. <ActionProvider handlers={actionHandlers}>
  160. <Renderer spec={spec} registry={registry} />
  161. </ActionProvider>
  162. </VisibilityProvider>
  163. </StateProvider>
  164. );
  165. }
  166. ```
  167. ## Next
  168. Learn about [data binding](/docs/data-binding) for dynamic values.