Skip to main content

react

React renderer for json-render that turns JSON specs into React components. Use when working with @json-render/react, building React UIs from JSON, creating component catalogs, or rendering AI-generated specs.

소스 정보

저장소
vercel-labs/json-render
최근 소스 활동
2026년 8월 15일 01:22
감지된 SKILL.md 언어
영어
스타
18,686
포크
992

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
react
description
React renderer for json-render that turns JSON specs into React components. Use when working with @json-render/react, building React UIs from JSON, creating component catalogs, or rendering AI-generated specs.
# @json-render/react React renderer that converts JSON specs into React component trees. ## Quick Start ```typescript 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 ```typescript 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() }), slots: ["default"], description: "Card container with title", }, Layout: { props: z.object({}), slots: ["default", "header", "footer"], description: "Layout with named content regions", }, }, }); // 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> ), Layout: ({ children, slots }) => ( <div> <header>{slots?.header}</header> <main>{children}</main> <footer>{slots?.footer}</footer> </div> ), }, }); ``` ## Spec Structure (Element Tree) The React schema uses an element tree format: ```json { "root": { "type": "Card", "props": { "title": "Hello" }, "children": [{ "type": "Button", "props": { "label": "Click me" } }] } } ``` ## Named Slots Use `children` for the `"default"` slot. Use the element's top-level `slots` object for other slot names declared by the catalog: ```json { "type": "Layout", "props": {}, "children": ["main"], "slots": { "header": ["heading"], "footer": ["actions"] } } ``` Registry components receive named content as `slots?.header`, `slots?.footer`, and so on. Do not use `slots.default`. ## 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.): ```tsx 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. - **Filtered lists**: `repeat` plus an `$item` visible condition on the same container renders only matching items: `{ "repeat": { "statePath": "/tasks", "key": "id" }, "visible": { "$item": "status", "eq": "todo" }, "children": ["task-card"] }`. AND-composed `$state` conjuncts gate the container shell; `$item`/`$index` conjuncts filter items. - **Nested lists**: inside a repeat, use `{ "repeat": { "statePath": { "$item": "comments" }, "key": "id" } }` to iterate an array on the enclosing item. - **`{ "$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 ```json { "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`: ```tsx <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: ```tsx // 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> ); }, ``` ```json { "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: ```json { "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`: ```json { "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" }`: ```tsx 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`): ```typescript import type { BaseComponentProps } from "@json-render/react"; const Card = ({ props, children }: BaseComponentProps<{ title?: string }>) => (
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기