page.mdx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. export const metadata = { title: "Migration Guide" }
  2. # Migration Guide
  3. This guide covers breaking changes introduced in v0.5.0 and how to update your code.
  4. ## State Provider
  5. `DataProvider` has been renamed to `StateProvider`, and its props have changed.
  6. **Before:**
  7. ```tsx
  8. import { DataProvider } from "@json-render/react";
  9. <DataProvider data={myData} getValue={getter} setValue={setter}>
  10. {children}
  11. </DataProvider>
  12. ```
  13. **After:**
  14. ```tsx
  15. import { StateProvider } from "@json-render/react";
  16. <StateProvider initialState={myData} onStateChange={(path, value) => console.log(path, value)}>
  17. {children}
  18. </StateProvider>
  19. ```
  20. `StateProvider` now manages state internally. Use `useStateStore()` to access `get`, `set`, and `update`.
  21. | Before | After |
  22. |--------|-------|
  23. | `DataProvider` | `StateProvider` |
  24. | `data` prop | `initialState` prop |
  25. | `getValue` / `setValue` props | Removed (use `useStateStore()` hook for `get` / `set`) |
  26. | `useData` | `useStateStore` |
  27. | `useDataValue` | `useStateValue` |
  28. | `useDataBinding` | `useStateBinding` (deprecated, use `useBoundProp` instead) |
  29. | `DataModel` type | `StateModel` type |
  30. ## Dynamic Expressions
  31. All dynamic value expressions have been renamed to use `$state`, `$item`, and `$index`.
  32. **Before:**
  33. ```json
  34. {
  35. "type": "Text",
  36. "props": {
  37. "label": { "$path": "/user/name" },
  38. "count": { "$data": "/items/length" }
  39. }
  40. }
  41. ```
  42. **After:**
  43. ```json
  44. {
  45. "type": "Text",
  46. "props": {
  47. "label": { "$state": "/user/name" },
  48. "count": { "$state": "/items/length" }
  49. }
  50. }
  51. ```
  52. Inside repeat scopes, use `$item` and `$index`:
  53. ```json
  54. {
  55. "type": "Card",
  56. "props": {
  57. "title": { "$item": "name" },
  58. "subtitle": { "$index": true }
  59. }
  60. }
  61. ```
  62. | Before | After |
  63. |--------|-------|
  64. | `{ "$path": "/..." }` | `{ "$state": "/..." }` |
  65. | `{ "$data": "/..." }` | `{ "$state": "/..." }` |
  66. ## Two-Way Binding
  67. Form components no longer use `valuePath` / `statePath` props. Instead, use `$bindState` expressions on the value prop, and `useBoundProp` in your registry.
  68. **Before (catalog):**
  69. ```typescript
  70. Input: {
  71. props: z.object({
  72. label: z.string(),
  73. valuePath: z.string(),
  74. placeholder: z.string().optional(),
  75. }),
  76. }
  77. ```
  78. **Before (spec):**
  79. ```json
  80. {
  81. "type": "Input",
  82. "props": { "label": "Email", "valuePath": "/form/email" }
  83. }
  84. ```
  85. **Before (registry):**
  86. ```tsx
  87. Input: ({ props }) => {
  88. const [value, setValue] = useStateBinding(props.valuePath);
  89. return <input value={value ?? ""} onChange={(e) => setValue(e.target.value)} />;
  90. }
  91. ```
  92. **After (catalog):**
  93. ```typescript
  94. Input: {
  95. props: z.object({
  96. label: z.string(),
  97. value: z.string().optional(),
  98. placeholder: z.string().optional(),
  99. }),
  100. }
  101. ```
  102. **After (spec):**
  103. ```json
  104. {
  105. "type": "Input",
  106. "props": { "label": "Email", "value": { "$bindState": "/form/email" } }
  107. }
  108. ```
  109. **After (registry):**
  110. ```tsx
  111. Input: ({ props, bindings }) => {
  112. const [value, setValue] = useBoundProp<string>(props.value, bindings?.value);
  113. return <input value={value ?? ""} onChange={(e) => setValue(e.target.value)} />;
  114. }
  115. ```
  116. `$bindState` reads from and writes to the given state path. Inside repeat scopes, use `$bindItem` to bind to a field on the current item:
  117. ```json
  118. {
  119. "type": "Checkbox",
  120. "props": { "checked": { "$bindItem": "completed" } }
  121. }
  122. ```
  123. ## Visibility Conditions
  124. Visibility conditions have been renamed to use `$state`, `$and`, and `$or`.
  125. **Before:**
  126. ```json
  127. { "path": "/isAdmin" }
  128. { "eq": [{ "path": "/role" }, "admin"] }
  129. { "and": [{ "path": "/isAdmin" }, { "path": "/feature" }] }
  130. { "or": [{ "path": "/roleA" }, { "path": "/roleB" }] }
  131. ```
  132. **After:**
  133. ```json
  134. { "$state": "/isAdmin" }
  135. { "$state": "/role", "eq": "admin" }
  136. { "$and": [{ "$state": "/isAdmin" }, { "$state": "/feature" }] }
  137. { "$or": [{ "$state": "/roleA" }, { "$state": "/roleB" }] }
  138. ```
  139. You can also use an array as shorthand for `$and`:
  140. ```json
  141. [{ "$state": "/isAdmin" }, { "$state": "/feature" }]
  142. ```
  143. Inside repeat scopes, use `$item` and `$index`:
  144. ```json
  145. { "$item": "isActive" }
  146. { "$index": true, "eq": 0 }
  147. ```
  148. ## Event System
  149. Components now use `emit` to fire named events. `onAction` has been removed.
  150. **Before:**
  151. ```tsx
  152. Button: ({ props, onAction }) => (
  153. <button onClick={() => onAction?.("press")}>{props.label}</button>
  154. )
  155. ```
  156. **After:**
  157. ```tsx
  158. Button: ({ props, emit }) => (
  159. <button onClick={() => emit("press")}>{props.label}</button>
  160. )
  161. ```
  162. `emit` is always defined (never `undefined`), so optional chaining is not needed.
  163. ## Actions Context
  164. `dispatch` has been renamed to `execute`, and the provider prop has been renamed from `actionHandlers` to `handlers`.
  165. **Before:**
  166. ```tsx
  167. const { dispatch } = useActions();
  168. dispatch({ action: "submit", params: {} });
  169. <ActionProvider actionHandlers={myHandlers}>
  170. ```
  171. **After:**
  172. ```tsx
  173. const { execute } = useActions();
  174. execute({ action: "submit", params: {} });
  175. <ActionProvider handlers={myHandlers}>
  176. ```
  177. ## Repeat / List Rendering
  178. The `repeat` field now uses `statePath` instead of `path`.
  179. **Before:**
  180. ```json
  181. {
  182. "type": "Column",
  183. "repeat": { "path": "/todos", "key": "id" },
  184. "children": ["todo-item"]
  185. }
  186. ```
  187. **After:**
  188. ```json
  189. {
  190. "type": "Column",
  191. "repeat": { "statePath": "/todos", "key": "id" },
  192. "children": ["todo-item"]
  193. }
  194. ```
  195. ## Catalog Creation
  196. `createCatalog` and `generateSystemPrompt` have been replaced by `defineSchema` + `defineCatalog`.
  197. **Before:**
  198. ```typescript
  199. import { createCatalog, generateSystemPrompt } from "@json-render/core";
  200. const catalog = createCatalog({
  201. name: "my-app",
  202. components: { /* ... */ },
  203. actions: { /* ... */ },
  204. });
  205. const prompt = generateSystemPrompt(catalog);
  206. ```
  207. **After:**
  208. ```typescript
  209. import { defineCatalog } from "@json-render/core";
  210. import { schema } from "@json-render/react/schema";
  211. const catalog = defineCatalog(schema, {
  212. components: { /* ... */ },
  213. actions: { /* ... */ },
  214. });
  215. const prompt = catalog.prompt();
  216. // Chat mode prompt
  217. const chatPrompt = catalog.prompt({ mode: "chat" });
  218. ```
  219. ## Validation
  220. `ValidationCheck` now uses `type` instead of `fn`, `ValidationProvider` uses `customFunctions` instead of `functions`, and `useFieldValidation` takes a config object instead of a checks array.
  221. **Before:**
  222. ```json
  223. { "fn": "required", "message": "Required" }
  224. { "fn": "minLength", "args": { "length": 8 }, "message": "Too short" }
  225. ```
  226. **After:**
  227. ```json
  228. { "type": "required", "message": "Required" }
  229. { "type": "minLength", "args": { "min": 8 }, "message": "Too short" }
  230. ```
  231. | Before | After |
  232. |--------|-------|
  233. | `{ fn: "required" }` | `{ type: "required" }` |
  234. | `ValidationProvider functions={...}` | `ValidationProvider customFunctions={...}` |
  235. | `useFieldValidation(path, checks)` | `useFieldValidation(path, config)` where config is `{ checks, validateOn? }` |
  236. ## Visibility Provider
  237. The `auth` prop has been removed from `VisibilityProvider`. Auth state should be modeled as regular state.
  238. **Before:**
  239. ```tsx
  240. <VisibilityProvider auth={{ isSignedIn: true, role: "admin" }}>
  241. ```
  242. ```json
  243. { "auth": "signedIn" }
  244. ```
  245. **After:**
  246. ```tsx
  247. <StateProvider initialState={{ auth: { isSignedIn: true, role: "admin" } }}>
  248. <VisibilityProvider>
  249. ```
  250. ```json
  251. { "$state": "/auth/isSignedIn" }
  252. ```
  253. ## Codegen
  254. `traverseTree` has been renamed to `traverseSpec`, `SpecVisitor` to `TreeVisitor`, and the visitor callback now receives a `key` parameter.
  255. **Before:**
  256. ```typescript
  257. import { traverseTree } from "@json-render/codegen";
  258. traverseTree(tree, (element) => {
  259. // ...
  260. });
  261. ```
  262. **After:**
  263. ```typescript
  264. import { traverseSpec } from "@json-render/codegen";
  265. traverseSpec(spec, (element, key) => {
  266. // ...
  267. });
  268. ```
  269. ## Action Params
  270. Action params in specs now use `statePath` instead of `path`.
  271. **Before:**
  272. ```json
  273. {
  274. "on": {
  275. "press": { "action": "setState", "params": { "path": "/count", "value": 0 } }
  276. }
  277. }
  278. ```
  279. **After:**
  280. ```json
  281. {
  282. "on": {
  283. "press": { "action": "setState", "params": { "statePath": "/count", "value": 0 } }
  284. }
  285. }
  286. ```
  287. ## Removed Exports
  288. The following exports have been removed from `@json-render/core`:
  289. | Removed | Replacement |
  290. |---------|-------------|
  291. | `createCatalog` | `defineCatalog(schema, config)` |
  292. | `generateCatalogPrompt` | `catalog.prompt()` |
  293. | `generateSystemPrompt` | `catalog.prompt()` |
  294. | `ComponentDefinition` | Use catalog component config directly |
  295. | `CatalogConfig` | Use `defineCatalog` parameters |
  296. | `SystemPromptOptions` | Use `PromptOptions` |
  297. | `LogicExpression` | Use `VisibilityCondition` |
  298. | `AuthState` | Model auth as regular state (e.g. `/auth/isSignedIn`) |
  299. | `evaluateLogicExpression` | Use `evaluateVisibility` |
  300. | `createRendererFromCatalog` | Use `defineRegistry` |
  301. | `traverseTree` (codegen) | Use `traverseSpec` |