---
id: sm-json-render-react
name: "json-render-react"
url: https://skills.yangsir.net/skill/sm-json-render-react
author: vercel-labs
domain: ai-frontend-engineering
tags: ["json-rendering", "react", "ui-components", "data-visualization", "frontend-development"]
install_count: 412
rating: 4.20 (51 reviews)
github: https://github.com/vercel-labs/json-render
---

# json-render-react

> 一个React渲染器，能将JSON规范数据转换为可交互的React组件树，实现动态界面渲染。

**Stats**: 412 installs · 4.2/5 (51 reviews)

## Before / After 对比

### 动态渲染React界面

## Readme

# json-render-react

# @json-render/react

React renderer that converts JSON specs into React component trees.

## Quick Start

```
import { defineRegistry, Renderer } from "@json-render/react";
import { catalog } from "./catalog";

const { registry } = defineRegistry(catalog, {
  components: {
    Card: ({ props, children }) => <div>{props.title}{children}</div>,
  },
});

function App({ spec }) {
  return <Renderer spec={spec} registry={registry} />;
}

```

## Creating a Catalog

```
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react/schema";
import { defineRegistry } from "@json-render/react";
import { z } from "zod";

// Create catalog with props schemas
export const catalog = defineCatalog(schema, {
  components: {
    Button: {
      props: z.object({
        label: z.string(),
        variant: z.enum(["primary", "secondary"]).nullable(),
      }),
      description: "Clickable button",
    },
    Card: {
      props: z.object({ title: z.string() }),
      description: "Card container with title",
    },
  },
});

// Define component implementations with type-safe props
const { registry } = defineRegistry(catalog, {
  components: {
    Button: ({ props }) => (
      <button className={props.variant}>{props.label}</button>
    ),
    Card: ({ props, children }) => (
      <div className="card">
        <h2>{props.title}</h2>
        {children}
      </div>
    ),
  },
});

```

## Spec Structure (Element Tree)

The React schema uses an element tree format:

```
{
  "root": {
    "type": "Card",
    "props": { "title": "Hello" },
    "children": [
      { "type": "Button", "props": { "label": "Click me" } }
    ]
  }
}

```

## Visibility Conditions

Use `visible` on elements to show/hide based on state. New syntax: `{ "$state": "/path" }`, `{ "$state": "/path", "eq": value }`, `{ "$state": "/path", "not": true }`, `{ "$and": [cond1, cond2] }` for AND, `{ "$or": [cond1, cond2] }` for OR. Helpers: `visibility.when("/path")`, `visibility.unless("/path")`, `visibility.eq("/path", val)`, `visibility.and(cond1, cond2)`, `visibility.or(cond1, cond2)`.

## Providers

Provider
Purpose

`StateProvider`
Share state across components (JSON Pointer paths). Accepts optional `store` prop for controlled mode.

`ActionProvider`
Handle actions dispatched via the event system

`VisibilityProvider`
Enable conditional rendering based on state

`ValidationProvider`
Form field validation

### External Store (Controlled Mode)

Pass a `StateStore` to `StateProvider` (or `JSONUIProvider` / `createRenderer`) to use external state management (Redux, Zustand, XState, etc.):

```
import { createStateStore, type StateStore } from "@json-render/react";

const store = createStateStore({ count: 0 });

<StateProvider store={store}>{children}</StateProvider>

// Mutate from anywhere — React re-renders automatically:
store.set("/count", 1);

```

When `store` is provided, `initialState` and `onStateChange` are ignored.

## Dynamic Prop Expressions

Any prop value can be a data-driven expression resolved by the renderer before components receive props:

- **`{ "$state": "/state/key" }`** - reads from state model (one-way read)

- **`{ "$bindState": "/path" }`** - two-way binding: reads from state and enables write-back. Use on the natural value prop (value, checked, pressed, etc.) of form components.

- **`{ "$bindItem": "field" }`** - two-way binding to a repeat item field. Use inside repeat scopes.

- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - conditional value

- **`{ "$template": "Hello, ${/name}!" }`** - interpolates state values into strings

- **`{ "$computed": "fn", "args": { ... } }`** - calls registered functions with resolved args

```
{
  "type": "Input",
  "props": {
    "value": { "$bindState": "/form/email" },
    "placeholder": "Email"
  }
}

```

Components do not use a `statePath` prop for two-way binding. Use `{ "$bindState": "/path" }` on the natural value prop instead.

Components receive already-resolved props. For two-way bound props, use the `useBoundProp` hook with the `bindings` map the renderer provides.

Register `$computed` functions via the `functions` prop on `JSONUIProvider` or `createRenderer`:

```
<JSONUIProvider
  functions={{ fullName: (args) => `${args.first} ${args.last}` }}
>

```

## Event System

Components use `emit` to fire named events, or `on()` to get an event handle with metadata. The element's `on` field maps events to action bindings:

```
// Simple event firing
Button: ({ props, emit }) => (
  <button onClick={() => emit("press")}>{props.label}</button>
),

// Event handle with metadata (e.g. preventDefault)
Link: ({ props, on }) => {
  const click = on("click");
  return (
    <a href={props.href} onClick={(e) => {
      if (click.shouldPreventDefault) e.preventDefault();
      click.emit();
    }}>{props.label}</a>
  );
},

```

```
{
  "type": "Button",
  "props": { "label": "Submit" },
  "on": { "press": { "action": "submit" } }
}

```

The `EventHandle` returned by `on()` has: `emit()`, `shouldPreventDefault` (boolean), and `bound` (boolean).

## State Watchers

Elements can declare a `watch` field (top-level, sibling of type/props/children) to trigger actions when state values change:

```
{
  "type": "Select",
  "props": { "value": { "$bindState": "/form/country" }, "options": ["US", "Canada"] },
  "watch": { "/form/country": { "action": "loadCities" } },
  "children": []
}

```

## Built-in Actions

The `setState`, `pushState`, `removeState`, and `validateForm` actions are built into the React schema and handled automatically by `ActionProvider`. They are injected into AI prompts without needing to be declared in catalog `actions`:

```
{ "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } }
{ "action": "pushState", "params": { "statePath": "/items", "value": { "text": "New" } } }
{ "action": "removeState", "params": { "statePath": "/items", "index": 0 } }
{ "action": "validateForm", "params": { "statePath": "/formResult" } }

```

`validateForm` validates all registered fields and writes `{ valid, errors }` to state.

Note: `statePath` in action params (e.g. `setState.statePath`) targets the mutation path. Two-way binding in component props uses `{ "$bindState": "/path" }` on the value prop, not `statePath`.

## useBoundProp

For form components that need two-way binding, use `useBoundProp` with the `bindings` map the renderer provides when a prop uses `{ "$bindState": "/path" }` or `{ "$bindItem": "field" }`:

```
import { useBoundProp } from "@json-render/react";

Input: ({ element, bindings }) => {
  const [value, setValue] = useBoundProp<string>(
    element.props.value,
    bindings?.value
  );
  return (
    <input
      value={value ?? ""}
      onChange={(e) => setValue(e.target.value)}
    />
  );
},

```

`useBoundProp(propValue, bindingPath)` returns `[value, setValue]`. The `value` is the resolved prop; `setValue` writes back to the bound state path (no-op if not bound).

## BaseComponentProps

For building reusable component libraries not tied to a specific catalog (e.g. `@json-render/shadcn`):

```
import type { BaseComponentProps } from "@json-render/react";

const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
  <div>{props.title}{children}</div>
);

```

## defineRegistry

`defineRegistry` conditionally requires the `actions` field only when the catalog declares actions. Catalogs with `actions: {}` can omit it.

## Key Exports

Export
Purpose

`defineRegistry`
Create a type-safe component registry from a catalog

`Renderer`
Render a spec using a registry

`schema`
Element tree schema (includes built-in state actions: setState, pushState, removeState, validateForm)

`useStateStore`
Access state context

`useStateValue`
Get single value from state

`useBoundProp`
Two-way binding for `$bindState`/`$bindItem` expressions

`useActions`
Access actions context

`useAction`
Get a single action dispatch function

`useOptionalValidation`
Non-throwing variant of useValidation (returns null if no provider)

`useUIStream`
Stream specs from an API endpoint

`createStateStore`
Create a framework-agnostic in-memory `StateStore`

`StateStore`
Interface for plugging in external state management

`BaseComponentProps`
Catalog-agnostic base type for reusable component libraries

`EventHandle`
Event handle type (`emit`, `shouldPreventDefault`, `bound`)

`ComponentContext`
Typed component context (catalog-aware)

Weekly Installs391Repository[vercel-labs/json-render](https://github.com/vercel-labs/json-render)GitHub Stars12.7KFirst SeenFeb 5, 2026Security Audits[Gen Agent Trust HubPass](/vercel-labs/json-render/json-render-react/security/agent-trust-hub)[SocketPass](/vercel-labs/json-render/json-render-react/security/socket)[SnykPass](/vercel-labs/json-render/json-render-react/security/snyk)Installed onopencode366codex362gemini-cli360github-copilot356kimi-cli334amp334

---
*Source: https://skills.yangsir.net/skill/sm-json-render-react*
*Markdown mirror: https://skills.yangsir.net/api/skill/sm-json-render-react/markdown*