json-render-react について
@json-render/reactは、データ駆動型UIを構築する開発者向けに、JSON仕様をReactコンポーネントツリーとして描画します。状態バインディング、条件表示、イベント処理、フォーム検証を備えています。
主な機能
- 01仕様をReactコンポーネントツリーに描画
- 02型安全なコンポーネントレジストリ
- 03状態の読み取りと双方向バインディング
- 04条件付き描画と状態ウォッチャー
- 05イベント処理とフォーム検証
活用シーン
- 01JSON仕様から動的にUIを構築
- 02外部状態に連動するフォームの作成
- 03状態に応じた表示制御とアクション実行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vercel-labs/ json-render --skill json-render-reactインストール後、Claude Code で/json-render-reactを入力して読み込まれたか確認します。
詳細
@json-render/react
JSON 仕様を React コンポーネントツリーに変換する React レンダラーです。
クイックスタート
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} />;
}
カタログの作成
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>
),
},
});
仕様の構造(要素ツリー)
React スキーマでは、要素ツリー形式を使用します。
{
"root": {
"type": "Card",
"props": { "title": "Hello" },
"children": [
{ "type": "Button", "props": { "label": "Click me" } }
]
}
}
表示条件
要素に visible を指定すると、状態に基づいて表示/非表示を切り替えられます。新しい構文:{ "$state": "/path" }、{ "$state": "/path", "eq": value }、{ "$state": "/path", "not": true }、AND 条件には { "$and": [cond1, cond2] }、OR 条件には { "$or": [cond1, cond2] } を使用します。ヘルパー:visibility.when("/path")、visibility.unless("/path")、visibility.eq("/path", val)、visibility.and(cond1, cond2)、visibility.or(cond1, cond2)。
プロバイダー
| プロバイダー | 目的 |
|---|---|
StateProvider | コンポーネント間で状態を共有します(JSON Pointer パス)。制御モード用の store プロパティを任意で指定できます。 |
ActionProvider | イベントシステムを介してディスパッチされるアクションを処理します |
VisibilityProvider | 状態に基づく条件付きレンダリングを有効にします |
ValidationProvider | フォームフィールドの検証を行います |
外部ストア(制御モード)
外部状態管理(Redux、Zustand、XState など)を使用するには、StateProvider(または JSONUIProvider / createRenderer)に StateStore を渡します。
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);
store が指定されている場合、initialState と onStateChange は無視されます。
動的なプロパティ式
任意のプロパティ値にデータ駆動型の式を指定できます。コンポーネントにプロパティが渡される前に、レンダラーによって評価されます。
{ "$state": "/state/key" }- 状態モデルから値を読み取ります(一方向の読み取り){ "$bindState": "/path" }- 双方向バインディングです。状態から値を読み取り、書き戻しを有効にします。フォームコンポーネントの通常の値プロパティ(value、checked、pressed など)に使用します。{ "$bindItem": "field" }- 繰り返し項目のフィールドに双方向バインディングします。繰り返しスコープ内で使用します。{ "$cond": <condition>, "$then": <value>, "$else": <value> }- 条件付きの値です{ "$template": "Hello, ${/name}!" }- 状態値を文字列に補間します{ "$computed": "fn", "args": { ... } }- 解決済みの引数を使って登録済み関数を呼び出します
{
"type": "Input",
"props": {
"value": { "$bindState": "/form/email" },
"placeholder": "Email"
}
}
コンポーネントでは、双方向バインディングに statePath プロパティを使用しません。代わりに、通常の値プロパティに { "$bindState": "/path" } を指定します。
コンポーネントには、解決済みのプロパティが渡されます。双方向バインドされたプロパティには、レンダラーが提供する bindings マップと useBoundProp フックを使用します。
JSONUIProvider または createRenderer の functions プロパティを介して、$computed 関数を登録します。
<JSONUIProvider
functions={{ fullName: (args) => `${args.first} ${args.last}` }}
>
イベントシステム
コンポーネントでは emit を使って名前付きイベントを発火するか、on() を使ってメタデータ付きのイベントハンドルを取得します。要素の on フィールドで、イベントとアクションのバインディングを対応付けます。
// 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" } }
}
on() が返す EventHandle には、emit()、shouldPreventDefault(ブール値)、bound(ブール値)があります。
状態ウォッチャー
要素では watch フィールド(type/props/children と同階層のトップレベル)を宣言して、状態値が変化したときにアクションを実行できます。
{
"type": "Select",
"props": { "value": { "$bindState": "/form/country" }, "options": ["US", "Canada"] },
"watch": { "/form/country": { "action": "loadCities" } },
"children": []
}
組み込みアクション
setState、pushState、removeState、validateForm アクションは React スキーマに組み込まれており、ActionProvider によって自動的に処理されます。カタログの actions に宣言しなくても、AI プロンプトに挿入されます。
{ "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 は登録済みのすべてのフィールドを検証し、{ valid, errors } を状態に書き込みます。
注:アクションパラメーター内の statePath(例:setState.statePath)は、変更対象のパスを指定します。コンポーネントのプロパティで双方向バインディングを行う場合は、statePath ではなく、値のプロパティに { "$bindState": "/path" } を指定します。
useBoundProp
双方向バインディングが必要なフォームコンポーネントでは、プロパティに { "$bindState": "/path" } または { "$bindItem": "field" } を使用する際に、レンダラーが提供する bindings マップと useBoundProp を使用します。
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) は [value, setValue] を返します。value は解決済みのプロパティで、setValue はバインド先の状態パスに値を書き戻します(バインドされていない場合は何もしません)。
BaseComponentProps
特定のカタログに依存しない再利用可能なコンポーネントライブラリ(例:@json-render/shadcn)を構築する場合:
import type { BaseComponentProps } from "@json-render/react";
const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
<div>{props.title}{children}</div>
);
defineRegistry
defineRegistry は、カタログでアクションが宣言されている場合にのみ、actions フィールドを必須とします。actions: {} を指定したカタログでは、このフィールドを省略できます。
主なエクスポート
| エクスポート | 用途 |
|---|---|
defineRegistry | カタログから型安全なコンポーネントレジストリを作成します |
Renderer | レジストリを使用して仕様をレンダリングします |
schema | 要素ツリーのスキーマ(組み込みの状態アクション:setState、pushState、removeState、validateForm を含みます) |
useStateStore | 状態コンテキストにアクセスします |
useStateValue | 状態から単一の値を取得します |
useBoundProp | $bindState/$bindItem 式の双方向バインディング |
useActions | アクションコンテキストにアクセスします |
useAction | 単一のアクションディスパッチ関数を取得します |
useOptionalValidation | useValidation の例外をスローしないバリアント(プロバイダーがない場合は null を返します) |
useUIStream | API エンドポイントから仕様をストリーミングします |
createStateStore | フレームワークに依存しないインメモリの StateStore を作成します |
StateStore | 外部の状態管理を接続するためのインターフェース |
BaseComponentProps | 再利用可能なコンポーネントライブラリ向けの、カタログに依存しない基本型 |
EventHandle | イベントハンドル型(emit、shouldPreventDefault、bound) |
ComponentContext | 型付きコンポーネントコンテキスト(カタログ対応) |