React Rendering
The @nuforge/react package renders a Frame's reactive View tree with React. A Frame holds a live View tree produced from your program, and this package binds that tree to React so the UI updates automatically as reactive state changes.
Client rendering
Wrap your app in NuProvider and render a frame with NuFrame:
'use client';
import { NuProvider, NuFrame } from '@nuforge/react';
export function Page({ nu, frame }) {
return (
<NuProvider nu={nu}>
<NuFrame frame={frame} />
</NuProvider>
);
}
Exports
NuProvider— puts the Nu instance on React context so descendants can reach it.useNu()— reads the Nu from context. Call it inside a component rendered under aNuProvider.observer(Component)— a higher-order component (mobx-react-lite style) that re-renders the wrapped component whenever the reactive state it reads changes.NuFrameis itself an observer, so it updates live as state changes.NuFrame— renders a frame's View tree.
DOM mapping
When the View tree maps to DOM elements, attribute names are normalized to their React equivalents:
classbecomesclassName.forbecomeshtmlFor.- A two-way binding (
value:={x}in the DSL) renders a controlled input. It keeps thevalueprop and adds anonChangehandler that writes the new value (orchecked) back into state.
Server rendering (RSC / SSR)
For zero-client-JS static output, import from the server entry. NuStatic and renderView are both exported from @nuforge/react/server:
import { Nu } from '@nuforge/core';
import { Parser } from '@nuforge/parser';
import { NuStatic } from '@nuforge/react/server';
import * as t from '@nuforge/types';
// A React Server Component — no client JS for the runtime
export default function Page() {
const nu = Nu.create();
nu.load(t.state({ program: Parser.parseProgram(SOURCE) }));
const frame = nu.createFrame({ component: { name: 'App' } });
return <NuStatic frame={frame} />;
}
NuStatic and renderView are pure: they use no hooks, no memo, and carry no 'use client' banner. That makes them produce a static first paint with zero runtime JavaScript shipped to the browser. When you need interactivity, pair the static output with the client NuFrame (hydrated) so the page comes alive after first paint.
This is Next.js App Router ready: the client entry ships a 'use client' banner, while the server entry stays free of client directives so it can run inside a React Server Component.
Hooks & helpers
Convenience hooks cut the boilerplate of wiring a runtime into React:
'use client';
import { useNuFromSource, useFrame, NuProvider, NuFrame } from '@nuforge/react';
export function Demo({ source }: { source: string }) {
const nu = useNuFromSource(source); // parses + loads once, disposes on unmount
const frame = useFrame(nu, 'App'); // creates the frame, disposes on unmount
return (
<NuProvider nu={nu}>
<NuFrame frame={frame} />
</NuProvider>
);
}
useNuFromSource(source, opts?)— build a runtime from DSL in one call (wrapsNu.fromSource).useFrame(nu, component)— create and auto-dispose a frame.useObserved(compute)— the granular primitive: re-render only when the reactive values read insidecomputechange, so you read exactly what you need instead of wrapping a whole component:
import { useObserved, useNode } from '@nuforge/react';
function TitleField({ id }: { id: string }) {
const node = useNode(id); // typed lookup from <NuProvider>
const title = useObserved(() => node?.props.title); // re-renders only on title change
return <input value={String(title ?? '')} readOnly />;
}
useNode(id)— look up a node by id from the nearest provider.
Next steps
- Code Export — turn a program into a standalone React component with no nuforge at runtime.
- Getting Started — set up a project and render your first frame.