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 a NuProvider.
  • observer(Component) — a higher-order component (mobx-react-lite style) that re-renders the wrapped component whenever the reactive state it reads changes. NuFrame is 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:

  • class becomes className.
  • for becomes htmlFor.
  • A two-way binding (value:={x} in the DSL) renders a controlled input. It keeps the value prop and adds an onChange handler that writes the new value (or checked) 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 (wraps Nu.fromSource).
  • useFrame(nu, component) — create and auto-dispose a frame.
  • useObserved(compute) — the granular primitive: re-render only when the reactive values read inside compute change, 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.