| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- export const metadata = { title: "Migration Guide" }
- # Migration Guide
- This guide covers breaking changes introduced in v0.6.0 and how to update your code.
- ## State Provider
- `DataProvider` has been renamed to `StateProvider`, and its props have changed.
- **Before:**
- ```tsx
- import { DataProvider } from "@json-render/react";
- <DataProvider data={myData} getValue={getter} setValue={setter}>
- {children}
- </DataProvider>
- ```
- **After:**
- ```tsx
- import { StateProvider } from "@json-render/react";
- <StateProvider initialState={myData} onStateChange={(path, value) => console.log(path, value)}>
- {children}
- </StateProvider>
- ```
- `StateProvider` now manages state internally. Use `useStateStore()` to access `get`, `set`, and `update`.
- | Before | After |
- |--------|-------|
- | `DataProvider` | `StateProvider` |
- | `data` prop | `initialState` prop |
- | `getValue` / `setValue` props | Removed (use `useStateStore()` hook for `get` / `set`) |
- | `useData` | `useStateStore` |
- | `useDataValue` | `useStateValue` |
- | `useDataBinding` | `useStateBinding` (deprecated, use `useBoundProp` instead) |
- | `DataModel` type | `StateModel` type |
- ## Dynamic Expressions
- All dynamic value expressions have been renamed to use `$state`, `$item`, and `$index`.
- **Before:**
- ```json
- {
- "type": "Text",
- "props": {
- "label": { "$path": "/user/name" },
- "count": { "$data": "/items/length" }
- }
- }
- ```
- **After:**
- ```json
- {
- "type": "Text",
- "props": {
- "label": { "$state": "/user/name" },
- "count": { "$state": "/items/length" }
- }
- }
- ```
- Inside repeat scopes, use `$item` and `$index`:
- ```json
- {
- "type": "Card",
- "props": {
- "title": { "$item": "name" },
- "subtitle": { "$index": true }
- }
- }
- ```
- | Before | After |
- |--------|-------|
- | `{ "$path": "/..." }` | `{ "$state": "/..." }` |
- | `{ "$data": "/..." }` | `{ "$state": "/..." }` |
- ## Two-Way Binding
- Form components no longer use `valuePath` / `statePath` props. Instead, use `$bindState` expressions on the value prop, and `useBoundProp` in your registry.
- **Before (catalog):**
- ```typescript
- Input: {
- props: z.object({
- label: z.string(),
- valuePath: z.string(),
- placeholder: z.string().optional(),
- }),
- }
- ```
- **Before (spec):**
- ```json
- {
- "type": "Input",
- "props": { "label": "Email", "valuePath": "/form/email" }
- }
- ```
- **Before (registry):**
- ```tsx
- Input: ({ props }) => {
- const [value, setValue] = useStateBinding(props.valuePath);
- return <input value={value ?? ""} onChange={(e) => setValue(e.target.value)} />;
- }
- ```
- **After (catalog):**
- ```typescript
- Input: {
- props: z.object({
- label: z.string(),
- value: z.string().optional(),
- placeholder: z.string().optional(),
- }),
- }
- ```
- **After (spec):**
- ```json
- {
- "type": "Input",
- "props": { "label": "Email", "value": { "$bindState": "/form/email" } }
- }
- ```
- **After (registry):**
- ```tsx
- Input: ({ props, bindings }) => {
- const [value, setValue] = useBoundProp<string>(props.value, bindings?.value);
- return <input value={value ?? ""} onChange={(e) => setValue(e.target.value)} />;
- }
- ```
- `$bindState` reads from and writes to the given state path. Inside repeat scopes, use `$bindItem` to bind to a field on the current item:
- ```json
- {
- "type": "Checkbox",
- "props": { "checked": { "$bindItem": "completed" } }
- }
- ```
- ## Visibility Conditions
- Visibility conditions have been renamed to use `$state`, `$and`, and `$or`.
- **Before:**
- ```json
- { "path": "/isAdmin" }
- { "eq": [{ "path": "/role" }, "admin"] }
- { "and": [{ "path": "/isAdmin" }, { "path": "/feature" }] }
- { "or": [{ "path": "/roleA" }, { "path": "/roleB" }] }
- ```
- **After:**
- ```json
- { "$state": "/isAdmin" }
- { "$state": "/role", "eq": "admin" }
- { "$and": [{ "$state": "/isAdmin" }, { "$state": "/feature" }] }
- { "$or": [{ "$state": "/roleA" }, { "$state": "/roleB" }] }
- ```
- You can also use an array as shorthand for `$and`:
- ```json
- [{ "$state": "/isAdmin" }, { "$state": "/feature" }]
- ```
- Inside repeat scopes, use `$item` and `$index`:
- ```json
- { "$item": "isActive" }
- { "$index": true, "eq": 0 }
- ```
- ## Event System
- Components now use `emit` to fire named events. `onAction` has been removed.
- **Before:**
- ```tsx
- Button: ({ props, onAction }) => (
- <button onClick={() => onAction?.("press")}>{props.label}</button>
- )
- ```
- **After:**
- ```tsx
- Button: ({ props, emit }) => (
- <button onClick={() => emit("press")}>{props.label}</button>
- )
- ```
- `emit` is always defined (never `undefined`), so optional chaining is not needed.
- ## Actions Context
- `dispatch` has been renamed to `execute`, and the provider prop has been renamed from `actionHandlers` to `handlers`.
- **Before:**
- ```tsx
- const { dispatch } = useActions();
- dispatch({ action: "submit", params: {} });
- <ActionProvider actionHandlers={myHandlers}>
- ```
- **After:**
- ```tsx
- const { execute } = useActions();
- execute({ action: "submit", params: {} });
- <ActionProvider handlers={myHandlers}>
- ```
- ## Repeat / List Rendering
- The `repeat` field now uses `statePath` instead of `path`.
- **Before:**
- ```json
- {
- "type": "Column",
- "repeat": { "path": "/todos", "key": "id" },
- "children": ["todo-item"]
- }
- ```
- **After:**
- ```json
- {
- "type": "Column",
- "repeat": { "statePath": "/todos", "key": "id" },
- "children": ["todo-item"]
- }
- ```
- ## Catalog Creation
- `createCatalog` and `generateSystemPrompt` have been replaced by `defineSchema` + `defineCatalog`.
- **Before:**
- ```typescript
- import { createCatalog, generateSystemPrompt } from "@json-render/core";
- const catalog = createCatalog({
- name: "my-app",
- components: { /* ... */ },
- actions: { /* ... */ },
- });
- const prompt = generateSystemPrompt(catalog);
- ```
- **After:**
- ```typescript
- import { defineCatalog } from "@json-render/core";
- import { schema } from "@json-render/react/schema";
- const catalog = defineCatalog(schema, {
- components: { /* ... */ },
- actions: { /* ... */ },
- });
- const prompt = catalog.prompt();
- // Chat mode prompt
- const chatPrompt = catalog.prompt({ mode: "chat" });
- ```
- ## Validation
- `ValidationCheck` now uses `type` instead of `fn`, `ValidationProvider` uses `customFunctions` instead of `functions`, and `useFieldValidation` takes a config object instead of a checks array.
- **Before:**
- ```json
- { "fn": "required", "message": "Required" }
- { "fn": "minLength", "args": { "length": 8 }, "message": "Too short" }
- ```
- **After:**
- ```json
- { "type": "required", "message": "Required" }
- { "type": "minLength", "args": { "min": 8 }, "message": "Too short" }
- ```
- | Before | After |
- |--------|-------|
- | `{ fn: "required" }` | `{ type: "required" }` |
- | `ValidationProvider functions={...}` | `ValidationProvider customFunctions={...}` |
- | `useFieldValidation(path, checks)` | `useFieldValidation(path, config)` where config is `{ checks, validateOn? }` |
- ## Visibility Provider
- The `auth` prop has been removed from `VisibilityProvider`. Auth state should be modeled as regular state.
- **Before:**
- ```tsx
- <VisibilityProvider auth={{ isSignedIn: true, role: "admin" }}>
- ```
- ```json
- { "auth": "signedIn" }
- ```
- **After:**
- ```tsx
- <StateProvider initialState={{ auth: { isSignedIn: true, role: "admin" } }}>
- <VisibilityProvider>
- ```
- ```json
- { "$state": "/auth/isSignedIn" }
- ```
- ## Codegen
- `traverseTree` has been renamed to `traverseSpec`, `SpecVisitor` to `TreeVisitor`, and the visitor callback now receives a `key` parameter.
- **Before:**
- ```typescript
- import { traverseTree } from "@json-render/codegen";
- traverseTree(tree, (element) => {
- // ...
- });
- ```
- **After:**
- ```typescript
- import { traverseSpec } from "@json-render/codegen";
- traverseSpec(spec, (element, key) => {
- // ...
- });
- ```
- ## Action Params
- Action params in specs now use `statePath` instead of `path`.
- **Before:**
- ```json
- {
- "on": {
- "press": { "action": "setState", "params": { "path": "/count", "value": 0 } }
- }
- }
- ```
- **After:**
- ```json
- {
- "on": {
- "press": { "action": "setState", "params": { "statePath": "/count", "value": 0 } }
- }
- }
- ```
- ## Removed Exports
- The following exports have been removed from `@json-render/core`:
- | Removed | Replacement |
- |---------|-------------|
- | `createCatalog` | `defineCatalog(schema, config)` |
- | `generateCatalogPrompt` | `catalog.prompt()` |
- | `generateSystemPrompt` | `catalog.prompt()` |
- | `ComponentDefinition` | Use catalog component config directly |
- | `CatalogConfig` | Use `defineCatalog` parameters |
- | `SystemPromptOptions` | Use `PromptOptions` |
- | `LogicExpression` | Use `VisibilityCondition` |
- | `AuthState` | Model auth as regular state (e.g. `/auth/isSignedIn`) |
- | `evaluateLogicExpression` | Use `evaluateVisibility` |
- | `createRendererFromCatalog` | Use `defineRegistry` |
- | `traverseTree` (codegen) | Use `traverseSpec` |
|