page.mdx 8.5 KB

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