@json-render/react-native
v0.21.0
Published
React Native renderer for @json-render/core. JSON becomes React Native components.
Readme
@json-render/react-native
React Native renderer for json-render. Turn JSON specs into native mobile UIs with standard components, data binding, visibility, actions, and dynamic props.
Installation
npm install @json-render/react-native @json-render/core zodQuick Start
1. Create a Catalog
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react-native/schema";
import {
standardComponentDefinitions,
standardActionDefinitions,
} from "@json-render/react-native/catalog";
import { z } from "zod";
export const catalog = defineCatalog(schema, {
components: {
...standardComponentDefinitions,
// Add custom components
Icon: {
props: z.object({
name: z.string(),
size: z.number().nullable(),
color: z.string().nullable(),
}),
slots: [],
description: "Icon display using Ionicons",
},
},
actions: standardActionDefinitions,
});2. Define Custom Component Implementations
import { defineRegistry, type Components } from "@json-render/react-native";
import Ionicons from "@expo/vector-icons/Ionicons";
import { catalog } from "./catalog";
export const { registry } = defineRegistry(catalog, {
components: {
Icon: ({ props }) => (
<Ionicons
name={props.name as keyof typeof Ionicons.glyphMap}
size={props.size ?? 24}
color={props.color ?? "#111827"}
/>
),
} as Components<typeof catalog>,
});Standard components (Container, Row, Column, Button, TextInput, etc.) are included by default. You only need to register custom ones.
3. Render Specs
import {
Renderer,
StateProvider,
VisibilityProvider,
ActionProvider,
ValidationProvider,
} from "@json-render/react-native";
import { registry } from "./registry";
function App({ spec }) {
return (
<StateProvider initialState={{}}>
<VisibilityProvider>
<ActionProvider handlers={{}}>
<ValidationProvider>
<Renderer spec={spec} registry={registry} />
</ValidationProvider>
</ActionProvider>
</VisibilityProvider>
</StateProvider>
);
}Standard Components
Layout
| Component | Description |
|-----------|-------------|
| Container | Basic wrapper with padding, background, border radius |
| Row | Horizontal flex layout with gap, alignment, flex |
| Column | Vertical flex layout with gap, alignment, flex |
| ScrollContainer | Scrollable area (vertical or horizontal) |
| SafeArea | Safe area insets for notch/home indicator |
| Pressable | Touchable wrapper that triggers actions on press |
| Spacer | Fixed or flexible spacing between elements |
| Divider | Thin line separator |
Content
| Component | Description |
|-----------|-------------|
| Heading | Heading text (levels 1-6) |
| Paragraph | Body text |
| Label | Small label text |
| Image | Image display with sizing modes |
| Avatar | Circular avatar image |
| Badge | Small status badge |
| Chip | Tag/chip for categories |
Input
| Component | Description |
|-----------|-------------|
| Button | Pressable button with variants |
| TextInput | Text input field |
| Switch | Toggle switch |
| Checkbox | Checkbox with label |
| Slider | Range slider |
| SearchBar | Search input |
Feedback
| Component | Description |
|-----------|-------------|
| Spinner | Loading indicator |
| ProgressBar | Progress indicator |
Composite
| Component | Description |
|-----------|-------------|
| Card | Card container with optional header |
| ListItem | List row with title, subtitle, accessory |
| Modal | Bottom sheet modal |
Visibility Conditions
Elements can use visible to show/hide based on state. Same syntax as @json-render/react: { "$state": "/path" }, { "$state": "/path", "eq": value }, { "$state": "/path", "not": true }, or [ cond1, cond2 ] for AND.
Pressable Component
The Pressable component wraps children and triggers an action on press. It's essential for building interactive UIs like tab bars:
{
"type": "Pressable",
"props": {
"action": "setState",
"actionParams": { "statePath": "/activeTab", "value": "home" }
},
"children": ["home-tab-icon", "home-tab-label"]
}Built-in Actions
The setState action is handled automatically by ActionProvider. It updates the state model, which triggers re-evaluation of visibility conditions and dynamic prop expressions:
{
"action": "setState",
"actionParams": { "statePath": "/activeTab", "value": "home" }
}Dynamic Prop Expressions
Any prop value can be a dynamic expression resolved at render time:
{
"type": "Icon",
"props": {
"name": {
"$cond": { "$state": "/activeTab", "eq": "home" },
"$then": "home",
"$else": "home-outline"
},
"color": {
"$cond": { "$state": "/activeTab", "eq": "home" },
"$then": "#007AFF",
"$else": "#8E8E93"
}
}
}See @json-render/core for full expression syntax.
Tab Navigation Pattern
Combine Pressable, setState, visibility conditions, and dynamic props for functional tabs:
- Each tab button is a
Pressablewithaction: "setState"andactionParams: { statePath: "/activeTab", value: "tabName" } - Tab icons/labels use
$conddynamic props for active/inactive styling - Tab content sections use
visibleconditions:{ "$state": "/activeTab", "eq": "tabName" }
AI Prompt Generation
const systemPrompt = catalog.prompt({
customRules: [
"Use SafeArea as the root element",
"Use Pressable + setState for interactive tabs",
],
});External Store (Controlled Mode)
For full control over state, pass a StateStore to bypass the internal state and wire json-render to any state management library (Redux, Zustand, XState, etc.):
import { createStateStore, type StateStore } from "@json-render/react-native";
// Use the built-in store outside of React
const store = createStateStore({ count: 0 });
<StateProvider store={store}>
{children}
</StateProvider>
// Mutate from anywhere — React will re-render automatically:
store.set("/count", 1);When store is provided, initialState and onStateChange are ignored. The store is the single source of truth. The same store prop is available on createRenderer, JSONUIProvider, and StateProvider.
Hooks
Nested lists can set repeat.statePath to { "$item": "field" } to iterate an array on the enclosing repeat item.
| Hook | Purpose |
|------|---------|
| useStateStore() | Access state context (state, get, set, update) |
| useStateValue(path) | Get single value from state |
| useStateBinding(path) | Two-way data binding (returns [value, setValue]) |
| useVisibility() | Access visibility evaluation |
| useIsVisible(condition) | Check if condition is met |
| useActions() | Access action context |
| useAction(name) | Get a single action dispatch function |
| useUIStream(options) | Stream specs from an API endpoint |
| createStandardActionHandlers(options) | Create handlers for standard actions |
| createStateStore(initialState) | Create a framework-agnostic in-memory StateStore |
