Compare commits

...
Author SHA1 Message Date
github-actions[bot] edbeb5a637 chore: version packages (#87)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-09 07:09:15 -06:00
Chris Tate d9a4efdbeb changeset (#86) 2026-02-09 07:07:17 -06:00
Chris Tate f435643817 more resilient (#85)
* more resilient

* more resilient
2026-02-09 07:05:16 -06:00
Chris Tate 458f3a728c update docs (#84)
* update docs

* fix og
2026-02-09 02:16:06 -06:00
26 changed files with 294 additions and 87 deletions
+16 -15
View File
@@ -1,8 +1,8 @@
# json-render # json-render
**Predictable. Guardrailed. Fast.** **The framework for User-Generated Interfaces (UGI).**
Let end users generate dashboards, widgets, apps, and videos from prompts — safely constrained to components you define. Dynamic, personalized UIs per user without sacrificing reliability. Predefined components and actions for safe, predictable output.
```bash ```bash
npm install @json-render/core @json-render/react npm install @json-render/core @json-render/react
@@ -14,11 +14,12 @@ npm install @json-render/core @json-render/remotion
## Why json-render? ## Why json-render?
When users prompt for UI, you need guarantees. json-render gives AI a **constrained vocabulary** so output is always predictable: json-render enables **User-Generated Interfaces**: dynamic UIs that end users create through natural language prompts, powered by Generative UI. You define the guardrails, AI generates within them:
- **Guardrailed** — AI can only use components in your catalog - **Guardrailed** - AI can only use components in your catalog
- **Predictable** — JSON output matches your schema, every time - **Predictable** - JSON output matches your schema, every time
- **Fast** — Stream and render progressively as the model responds - **Fast** - Stream and render progressively as the model responds
- **Cross-Platform** - React (web) and React Native (mobile) from the same catalog
## Quick Start ## Quick Start
@@ -237,8 +238,8 @@ Any prop value can be data-driven using expressions:
Two expression forms: Two expression forms:
- **`{ "$path": "/state/key" }`** -- reads a value from the data model - **`{ "$path": "/state/key" }`** - reads a value from the data model
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** -- evaluates a condition (same syntax as visibility conditions) and picks a branch - **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - evaluates a condition (same syntax as visibility conditions) and picks a branch
### Actions ### Actions
@@ -265,9 +266,9 @@ pnpm install
pnpm dev pnpm dev
``` ```
- http://localhost:3000 -- Docs & Playground - http://localhost:3000 - Docs & Playground
- http://localhost:3001 -- Example Dashboard - http://localhost:3001 - Example Dashboard
- http://localhost:3002 -- Remotion Video Example - http://localhost:3002 - Remotion Video Example
- React Native example: run `npx expo start` in `examples/react-native` - React Native example: run `npx expo start` in `examples/react-native`
## How It Works ## How It Works
@@ -283,10 +284,10 @@ flowchart LR
D -.- G([streamed]) D -.- G([streamed])
``` ```
1. **Define the guardrails** — what components, actions, and data bindings AI can use 1. **Define the guardrails** - what components, actions, and data bindings AI can use
2. **Users prompt** — end users describe what they want in natural language 2. **Users generate** - end users describe what they want in natural language
3. **AI generates JSON** — output is always predictable, constrained to your catalog 3. **AI generates JSON** - output is always predictable, constrained to your catalog
4. **Render fast** — stream and render progressively as the model responds 4. **Render fast** - stream and render progressively as the model responds
## License ## License
@@ -93,7 +93,7 @@ React Native renderer with standard components, providers, and hooks.
## defineRegistry ## defineRegistry
Create a type-safe component registry. Standard components are built-in -- only register custom components. Create a type-safe component registry. Standard components are built-in; only register custom components.
```tsx ```tsx
import { defineRegistry, type Components } from '@json-render/react-native'; import { defineRegistry, type Components } from '@json-render/react-native';
+1 -1
View File
@@ -100,7 +100,7 @@ const fixed = autoFixSpec(spec);
### Improved: State Management ### Improved: State Management
`DataProvider` has been renamed to `StateProvider` with a clearer API. State is now a first-class part of specs -- elements can bind to state via `$path` expressions, and the built-in `setState` action updates state directly. `DataProvider` has been renamed to `StateProvider` with a clearer API. State is now a first-class part of specs. Elements can bind to state via `$path` expressions, and the built-in `setState` action updates state directly.
### Improved: AI Prompts ### Improved: AI Prompts
+13 -7
View File
@@ -2,17 +2,19 @@ export const metadata = { title: "Introduction" }
# Introduction # Introduction
Predictable. Guardrailed. Fast. Let users generate dashboards, widgets, apps, and data visualizations from prompts. The framework for User-Generated Interfaces (UGI). Dynamic, personalized UIs per user without sacrificing reliability.
## What is json-render? ## What is json-render?
json-render lets end users generate UI from natural language prompts — safely constrained to components you define. You set the guardrails: what components exist, what props they take, what actions are available. AI generates JSON that matches your schema, and your components render it natively. json-render is the framework for **User-Generated Interfaces**: dynamic UIs that end users generate through natural language prompts, powered by Generative UI. You define the guardrails: what components exist, what props they take, what actions are available. AI generates JSON that matches your schema, and your components render it natively on web or mobile.
Every interface is unique to the user, but every interface is safe and predictable.
## Why json-render? ## Why json-render?
### Guardrailed ### Guardrailed
AI can only use components in your catalog. No arbitrary code generation. AI can only use components in your catalog. No arbitrary code generation. Predefined components and actions for safe, predictable output.
### Predictable ### Predictable
@@ -22,9 +24,13 @@ JSON output matches your schema, every time. Actions are declared by name, you c
Stream and render progressively as the model responds. No waiting for completion. Stream and render progressively as the model responds. No waiting for completion.
### Cross-Platform
Render on web with React and on mobile with React Native from the same catalog and spec format.
## How it works ## How it works
1. Define the guardrails — what components, actions, and data bindings AI can use 1. Define the guardrails - what components, actions, and data bindings AI can use
2. Users prompt — end users describe what they want in natural language 2. Users generate - end users describe what they want in natural language
3. AI generates JSON — output is always predictable, constrained to your catalog 3. AI generates JSON - output is always predictable, constrained to your catalog
4. Render fast — stream and render progressively as the model responds 4. Render fast - stream and render progressively as the model responds
+17 -14
View File
@@ -9,12 +9,15 @@ export default function Home() {
<> <>
{/* Hero */} {/* Hero */}
<section className="max-w-5xl mx-auto px-6 pt-24 pb-16 text-center"> <section className="max-w-5xl mx-auto px-6 pt-24 pb-16 text-center">
<h1 className="text-5xl sm:text-6xl md:text-7xl font-bold tracking-tighter mb-6"> <p className="text-xs sm:text-sm font-medium text-muted-foreground tracking-widest uppercase mb-4">
The framework for User-Generated Interfaces
</p>
<h1 className="text-4xl sm:text-6xl md:text-7xl font-bold tracking-tighter mb-6">
AI → json-render → UI AI → json-render → UI
</h1> </h1>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto mb-12 leading-relaxed"> <p className="text-lg text-muted-foreground max-w-2xl mx-auto mb-12 leading-relaxed">
Define a component catalog. Users prompt. AI outputs JSON constrained Dynamic, personalized UIs per user without sacrificing reliability.
to your catalog. Your components render it. Predefined components and actions for safe, predictable output.
</p> </p>
<Demo /> <Demo />
@@ -65,10 +68,10 @@ export default function Home() {
<div className="text-xs text-muted-foreground font-mono mb-3"> <div className="text-xs text-muted-foreground font-mono mb-3">
02 02
</div> </div>
<h3 className="text-lg font-semibold mb-2">Users Prompt</h3> <h3 className="text-lg font-semibold mb-2">Users Generate</h3>
<p className="text-sm text-muted-foreground leading-relaxed"> <p className="text-sm text-muted-foreground leading-relaxed">
End users describe what they want. AI generates JSON constrained End users describe what they want. AI generates JSON constrained
to your catalog. to your catalog. Every interface is unique to the user.
</p> </p>
</div> </div>
<div> <div>
@@ -244,6 +247,10 @@ export default function Page() {
<h2 className="text-2xl font-semibold mb-12 text-center">Features</h2> <h2 className="text-2xl font-semibold mb-12 text-center">Features</h2>
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-8"> <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-8">
{[ {[
{
title: "User-Generated Interfaces",
desc: "Dynamic, personalized UIs per user powered by Generative UI",
},
{ {
title: "Guardrails", title: "Guardrails",
desc: "AI can only use components you define in the catalog", desc: "AI can only use components you define in the catalog",
@@ -253,20 +260,16 @@ export default function Page() {
desc: "Progressive rendering as JSON streams from the model", desc: "Progressive rendering as JSON streams from the model",
}, },
{ {
title: "Code Export", title: "React & React Native",
desc: "Export as standalone React code with no runtime dependencies", desc: "Render on web and mobile from the same catalog and spec format",
}, },
{ {
title: "Data Binding", title: "Data Binding",
desc: "Two-way binding with JSON Pointer paths", desc: "Two-way state binding with dynamic prop expressions",
}, },
{ {
title: "Actions", title: "Code Export",
desc: "Named actions handled by your application", desc: "Export as standalone React code with no runtime dependencies",
},
{
title: "Visibility",
desc: "Conditional show/hide based on data or auth",
}, },
].map((feature) => ( ].map((feature) => (
<div key={feature.title}> <div key={feature.title}>
+10 -6
View File
@@ -22,11 +22,15 @@ export const metadata: Metadata = {
template: "%s | json-render", template: "%s | json-render",
}, },
description: description:
"Let users generate dashboards, widgets, apps, and data visualizations from prompts — safely constrained to components you define.", "The framework for User-Generated Interfaces (UGI). Let users generate dashboards, widgets, and apps from prompts — safely constrained to components you define.",
keywords: [ keywords: [
"json-render", "json-render",
"UGI",
"User-Generated Interfaces",
"AI UI generation", "AI UI generation",
"generative UI",
"React components", "React components",
"React Native",
"guardrails", "guardrails",
"structured output", "structured output",
"dashboard builder", "dashboard builder",
@@ -38,23 +42,23 @@ export const metadata: Metadata = {
locale: "en_US", locale: "en_US",
url: "https://json-render.dev", url: "https://json-render.dev",
siteName: "json-render", siteName: "json-render",
title: "json-render | AI-generated UI with guardrails", title: "json-render | The framework for User-Generated Interfaces",
description: description:
"Let users generate dashboards, widgets, apps, and data visualizations from prompts — safely constrained to components you define.", "The framework for User-Generated Interfaces (UGI). Let users generate dashboards, widgets, and apps from prompts — safely constrained to components you define.",
images: [ images: [
{ {
url: "/og", url: "/og",
width: 1200, width: 1200,
height: 630, height: 630,
alt: "json-render - AI-generated UI with guardrails", alt: "json-render - The framework for User-Generated Interfaces",
}, },
], ],
}, },
twitter: { twitter: {
card: "summary_large_image", card: "summary_large_image",
title: "json-render | AI-generated UI with guardrails", title: "json-render | The framework for User-Generated Interfaces",
description: description:
"Let users generate dashboards, widgets, apps, and data visualizations from prompts — safely constrained to components you define.", "The framework for User-Generated Interfaces (UGI). Let users generate dashboards, widgets, and apps from prompts — safely constrained to components you define.",
images: ["/og"], images: ["/og"],
creator: "@verabornnot", creator: "@verabornnot",
}, },
+17 -13
View File
@@ -66,23 +66,27 @@ export async function renderOgImage(title: string) {
style={{ style={{
display: "flex", display: "flex",
flex: 1, flex: 1,
flexDirection: "column",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
}} }}
> >
<span {title.split("\n").map((line, i) => (
style={{ <span
fontSize: 72, key={i}
fontFamily: "Geist", style={{
fontWeight: 400, fontSize: 72,
color: "white", fontFamily: "Geist",
letterSpacing: "-0.02em", fontWeight: 400,
textAlign: "center", color: "white",
lineHeight: 1.2, letterSpacing: "-0.02em",
}} textAlign: "center",
> lineHeight: 1.2,
{title} }}
</span> >
{line}
</span>
))}
</div> </div>
</div>, </div>,
{ {
+1 -1
View File
@@ -7,7 +7,7 @@
*/ */
export const PAGE_TITLES: Record<string, string> = { export const PAGE_TITLES: Record<string, string> = {
// Home (no slug) // Home (no slug)
"": "AI-generated UI with guardrails", "": "The framework for\nUser-Generated Interfaces",
// Top-level // Top-level
playground: "Playground", playground: "Playground",
+25 -8
View File
@@ -823,7 +823,12 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
const [localValue, setLocalValue] = useState<string>(""); const [localValue, setLocalValue] = useState<string>("");
const value = props.statePath ? (boundValue ?? "") : localValue; const value = props.statePath ? (boundValue ?? "") : localValue;
const setValue = props.statePath ? setBoundValue! : setLocalValue; const setValue = props.statePath ? setBoundValue! : setLocalValue;
const options = props.options ?? []; const rawOptions = props.options ?? [];
// Coerce options to strings – AI may produce objects/numbers instead of
// plain strings which would cause duplicate `[object Object]` keys.
const options = rawOptions.map((opt) =>
typeof opt === "string" ? opt : String(opt ?? ""),
);
return ( return (
<div className="space-y-2"> <div className="space-y-2">
@@ -839,8 +844,11 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
<SelectValue placeholder={props.placeholder ?? "Select..."} /> <SelectValue placeholder={props.placeholder ?? "Select..."} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{options.map((opt) => ( {options.map((opt, idx) => (
<SelectItem key={opt} value={opt}> <SelectItem
key={`${idx}-${opt}`}
value={opt || `option-${idx}`}
>
{opt} {opt}
</SelectItem> </SelectItem>
))} ))}
@@ -876,7 +884,10 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
}, },
Radio: ({ props, emit }) => { Radio: ({ props, emit }) => {
const options = props.options ?? []; const rawOptions = props.options ?? [];
const options = rawOptions.map((opt) =>
typeof opt === "string" ? opt : String(opt ?? ""),
);
const [boundValue, setBoundValue] = props.statePath const [boundValue, setBoundValue] = props.statePath
? useStateBinding<string>(props.statePath) // eslint-disable-line react-hooks/rules-of-hooks ? useStateBinding<string>(props.statePath) // eslint-disable-line react-hooks/rules-of-hooks
: [undefined, undefined]; : [undefined, undefined];
@@ -894,11 +905,17 @@ export const { registry, executeAction } = defineRegistry(playgroundCatalog, {
emit?.("change"); emit?.("change");
}} }}
> >
{options.map((opt) => ( {options.map((opt, idx) => (
<div key={opt} className="flex items-center space-x-2"> <div
<RadioGroupItem value={opt} id={`${props.name}-${opt}`} /> key={`${idx}-${opt}`}
className="flex items-center space-x-2"
>
<RadioGroupItem
value={opt || `option-${idx}`}
id={`${props.name}-${idx}-${opt}`}
/>
<Label <Label
htmlFor={`${props.name}-${opt}`} htmlFor={`${props.name}-${idx}-${opt}`}
className="cursor-pointer" className="cursor-pointer"
> >
{opt} {opt}
+6
View File
@@ -1,5 +1,11 @@
# @json-render/codegen # @json-render/codegen
## 0.5.1
### Patch Changes
- @json-render/core@0.5.1
## 0.5.0 ## 0.5.0
### Minor Changes ### Minor Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@json-render/codegen", "name": "@json-render/codegen",
"version": "0.5.0", "version": "0.5.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"description": "Utilities for generating code from json-render UI trees", "description": "Utilities for generating code from json-render UI trees",
"keywords": [ "keywords": [
+2
View File
@@ -1,5 +1,7 @@
# @json-render/core # @json-render/core
## 0.5.1
## 0.5.0 ## 0.5.0
### Minor Changes ### Minor Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@json-render/core", "name": "@json-render/core",
"version": "0.5.0", "version": "0.5.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"description": "JSON becomes real things. Define your catalog, register your components, let AI generate.", "description": "JSON becomes real things. Define your catalog, register your components, let AI generate.",
"keywords": [ "keywords": [
+7
View File
@@ -1,5 +1,12 @@
# @json-render/react-native # @json-render/react-native
## 0.5.1
### Patch Changes
- d9a4efd: Prevent rendering errors from crashing the application. Added error boundaries to all renderers so a single bad component silently disappears instead of causing a white-screen-of-death. Fixed Select and Radio components to handle non-string option values from AI output.
- @json-render/core@0.5.1
## 0.5.0 ## 0.5.0
### Minor Changes ### Minor Changes
+1 -1
View File
@@ -59,7 +59,7 @@ export const { registry } = defineRegistry(catalog, {
}); });
``` ```
Standard components (Container, Row, Column, Button, TextInput, etc.) are included by default -- you only need to register custom ones. Standard components (Container, Row, Column, Button, TextInput, etc.) are included by default. You only need to register custom ones.
### 3. Render Specs ### 3. Render Specs
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@json-render/react-native", "name": "@json-render/react-native",
"version": "0.5.0", "version": "0.5.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"description": "React Native renderer for @json-render/core. JSON becomes React Native components.", "description": "React Native renderer for @json-render/core. JSON becomes React Native components.",
"keywords": [ "keywords": [
+51 -3
View File
@@ -1,5 +1,6 @@
import React, { import React, {
type ComponentType, type ComponentType,
type ErrorInfo,
type ReactNode, type ReactNode,
useCallback, useCallback,
useMemo, useMemo,
@@ -81,6 +82,51 @@ export interface RendererProps {
fallback?: ComponentRenderer; fallback?: ComponentRenderer;
} }
// ---------------------------------------------------------------------------
// ElementErrorBoundary – catches rendering errors in individual elements so
// a single bad component never crashes the whole page.
// ---------------------------------------------------------------------------
interface ElementErrorBoundaryProps {
elementType: string;
children: ReactNode;
}
interface ElementErrorBoundaryState {
hasError: boolean;
}
class ElementErrorBoundary extends React.Component<
ElementErrorBoundaryProps,
ElementErrorBoundaryState
> {
constructor(props: ElementErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(): ElementErrorBoundaryState {
return { hasError: true };
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error(
`[json-render] Rendering error in <${this.props.elementType}>:`,
error,
info.componentStack,
);
}
render() {
if (this.state.hasError) {
// Render nothing – the element silently disappears rather than
// crashing the entire application.
return null;
}
return this.props.children;
}
}
/** /**
* Element renderer component * Element renderer component
*/ */
@@ -214,9 +260,11 @@ function ElementRenderer({
); );
return ( return (
<Component element={resolvedElement} emit={emit} loading={loading}> <ElementErrorBoundary elementType={resolvedElement.type}>
{children} <Component element={resolvedElement} emit={emit} loading={loading}>
</Component> {children}
</Component>
</ElementErrorBoundary>
); );
} }
+7
View File
@@ -1,5 +1,12 @@
# @json-render/react # @json-render/react
## 0.5.1
### Patch Changes
- d9a4efd: Prevent rendering errors from crashing the application. Added error boundaries to all renderers so a single bad component silently disappears instead of causing a white-screen-of-death. Fixed Select and Radio components to handle non-string option values from AI output.
- @json-render/core@0.5.1
## 0.5.0 ## 0.5.0
### Minor Changes ### Minor Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@json-render/react", "name": "@json-render/react",
"version": "0.5.0", "version": "0.5.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"description": "React renderer for @json-render/core. JSON becomes React components.", "description": "React renderer for @json-render/core. JSON becomes React components.",
"keywords": [ "keywords": [
+51 -3
View File
@@ -2,6 +2,7 @@
import React, { import React, {
type ComponentType, type ComponentType,
type ErrorInfo,
type ReactNode, type ReactNode,
useCallback, useCallback,
useMemo, useMemo,
@@ -77,6 +78,51 @@ export interface RendererProps {
fallback?: ComponentRenderer; fallback?: ComponentRenderer;
} }
// ---------------------------------------------------------------------------
// ElementErrorBoundary – catches rendering errors in individual elements so
// a single bad component never crashes the whole page.
// ---------------------------------------------------------------------------
interface ElementErrorBoundaryProps {
elementType: string;
children: ReactNode;
}
interface ElementErrorBoundaryState {
hasError: boolean;
}
class ElementErrorBoundary extends React.Component<
ElementErrorBoundaryProps,
ElementErrorBoundaryState
> {
constructor(props: ElementErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(): ElementErrorBoundaryState {
return { hasError: true };
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error(
`[json-render] Rendering error in <${this.props.elementType}>:`,
error,
info.componentStack,
);
}
render() {
if (this.state.hasError) {
// Render nothing – the element silently disappears rather than
// crashing the entire application.
return null;
}
return this.props.children;
}
}
/** /**
* Element renderer component * Element renderer component
*/ */
@@ -210,9 +256,11 @@ function ElementRenderer({
); );
return ( return (
<Component element={resolvedElement} emit={emit} loading={loading}> <ElementErrorBoundary elementType={resolvedElement.type}>
{children} <Component element={resolvedElement} emit={emit} loading={loading}>
</Component> {children}
</Component>
</ElementErrorBoundary>
); );
} }
+7
View File
@@ -1,5 +1,12 @@
# @json-render/remotion # @json-render/remotion
## 0.5.1
### Patch Changes
- d9a4efd: Prevent rendering errors from crashing the application. Added error boundaries to all renderers so a single bad component silently disappears instead of causing a white-screen-of-death. Fixed Select and Radio components to handle non-string option values from AI output.
- @json-render/core@0.5.1
## 0.5.0 ## 0.5.0
### Minor Changes ### Minor Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@json-render/remotion", "name": "@json-render/remotion",
"version": "0.5.0", "version": "0.5.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"description": "Remotion renderer for @json-render/core. JSON becomes video compositions.", "description": "Remotion renderer for @json-render/core. JSON becomes video compositions.",
"keywords": [ "keywords": [
+48 -1
View File
@@ -1,5 +1,6 @@
"use client"; "use client";
import React, { type ErrorInfo, type ReactNode } from "react";
import { AbsoluteFill, Sequence } from "remotion"; import { AbsoluteFill, Sequence } from "remotion";
import type { TimelineSpec, ComponentRegistry, Clip } from "./types"; import type { TimelineSpec, ComponentRegistry, Clip } from "./types";
@@ -33,6 +34,50 @@ export const standardComponents: ComponentRegistry = {
VideoClip, VideoClip,
}; };
// ---------------------------------------------------------------------------
// ClipErrorBoundary – catches rendering errors in individual clips so
// a single bad clip never crashes the entire composition.
// ---------------------------------------------------------------------------
interface ClipErrorBoundaryProps {
clipId: string;
component: string;
children: ReactNode;
}
interface ClipErrorBoundaryState {
hasError: boolean;
}
class ClipErrorBoundary extends React.Component<
ClipErrorBoundaryProps,
ClipErrorBoundaryState
> {
constructor(props: ClipErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(): ClipErrorBoundaryState {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error(
`[json-render/remotion] Rendering error in clip "${this.props.clipId}" (<${this.props.component}>):`,
error,
info.componentStack,
);
}
render() {
if (this.state.hasError) {
return null;
}
return this.props.children;
}
}
interface RendererProps { interface RendererProps {
/** The timeline spec to render */ /** The timeline spec to render */
spec: TimelineSpec; spec: TimelineSpec;
@@ -102,7 +147,9 @@ export function Renderer({
from={clip.from} from={clip.from}
durationInFrames={clip.durationInFrames} durationInFrames={clip.durationInFrames}
> >
<Component clip={clip} /> <ClipErrorBoundary clipId={clip.id} component={clip.component}>
<Component clip={clip} />
</ClipErrorBoundary>
</Sequence> </Sequence>
); );
}; };
+2 -2
View File
@@ -81,8 +81,8 @@ const finalSpec = compiler.getResult();
Any prop value can be a dynamic expression resolved at render time: Any prop value can be a dynamic expression resolved at render time:
- **`{ "$path": "/state/key" }`** -- reads a value from the state model - **`{ "$path": "/state/key" }`** - reads a value from the state model
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** -- evaluates a visibility condition and picks a branch - **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - evaluates a visibility condition and picks a branch
`$cond` uses the same syntax as visibility conditions (`eq`, `neq`, `path`, `and`, `or`, `not`). `$then` and `$else` can themselves be expressions (recursive). `$cond` uses the same syntax as visibility conditions (`eq`, `neq`, `path`, `and`, `or`, `not`). `$then` and `$else` can themselves be expressions (recursive).
+3 -3
View File
@@ -110,8 +110,8 @@ Use `Pressable` with the built-in `setState` action for interactive UIs like tab
Any prop value can be a data-driven expression resolved at render time: Any prop value can be a data-driven expression resolved at render time:
- **`{ "$path": "/state/key" }`** -- reads from data model - **`{ "$path": "/state/key" }`** - reads from data model
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** -- conditional value - **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - conditional value
```json ```json
{ {
@@ -123,7 +123,7 @@ Any prop value can be a data-driven expression resolved at render time:
} }
``` ```
Components receive already-resolved props -- no changes needed to component implementations. Components receive already-resolved props. No changes needed to component implementations.
## Built-in Actions ## Built-in Actions
+3 -3
View File
@@ -93,8 +93,8 @@ The React schema uses an element tree format:
Any prop value can be a data-driven expression resolved by the renderer before components receive props: Any prop value can be a data-driven expression resolved by the renderer before components receive props:
- **`{ "$path": "/state/key" }`** -- reads from data model - **`{ "$path": "/state/key" }`** - reads from data model
- **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** -- conditional value - **`{ "$cond": <condition>, "$then": <value>, "$else": <value> }`** - conditional value
```json ```json
{ {
@@ -106,7 +106,7 @@ Any prop value can be a data-driven expression resolved by the renderer before c
} }
``` ```
Components receive already-resolved props -- no changes needed to component implementations. Components receive already-resolved props. No changes needed to component implementations.
## Event System ## Event System