export const metadata = { title: "Migration Guide" }
# Migration Guide
This guide covers breaking changes introduced in v0.5.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";
{children}
```
**After:**
```tsx
import { StateProvider } from "@json-render/react";
console.log(path, value)}>
{children}
```
`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 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(props.value, bindings?.value);
return 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 }) => (
)
```
**After:**
```tsx
Button: ({ props, emit }) => (
)
```
`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: {} });
```
**After:**
```tsx
const { execute } = useActions();
execute({ action: "submit", params: {} });
```
## 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
```
```json
{ "auth": "signedIn" }
```
**After:**
```tsx
```
```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` |