React / foldkit-react
Foldkit inside React
import { FoldkitComponent } from 'foldkit-react'
const Counter = FoldkitComponent.define({
// Runs once per runtime. Props here are initial configuration only.
make: (container, props: CounterProps) => makeCounter(container, props.initialCount),
// Re-sent whenever the selected value changes (Object.is).
inbound: { stepChanged: props => props.step },
// Called with the latest props, so a new onCountChange needs no resubscribe.
outbound: { countChanged: (count, props) => props.onCountChange?.(count) },
})
// <Counter initialCount={10} step={2} onCountChange={setCount} />makeCounter returns a program from Runtime.makeElement with ports. Port
names and value types are checked against it: bindings send and receive each
Port's Encoded side, the same values EmbedHandle.ports uses.
The component renders a <div> (it accepts className and style) and embeds
the program in a child element React never renders. On unmount it disposes the
runtime, stopping its Subscriptions, Managed Resources, and in-flight
Commands.
- Strict Mode mounts, disposes, and mounts again; only the second runtime keeps running.
- Initial values arrive in order. Inbound values sent before the program's
Subscriptions start are buffered, and outbound values emitted by
initCommands are delivered after the listeners are attached. - Invalid inbound values fail the Port's Schema, are logged, and never reach the app. The same invalid value is not re-sent on every render.
Custom equality and restarts
FoldkitComponent.define({
make: (container, props: DashboardProps) => makeDashboard(container, props.userId),
inbound: { filtersChanged: { select: props => props.filters, equals: shallowEqual } },
// A different user is a different program, not a new Port value.
restartKey: props => props.userId,
})When restartKey changes (compared with Object.is), the runtime is disposed
and a fresh one is made with the current props, which also receives every
inbound value.
useFoldkitElement
The hook under FoldkitComponent, for components that need the Port handles
themselves:
const { ref, handle } = useFoldkitElement({
make: container => makeCounter(container, 0),
})
useEffect(() => {
handle?.ports.stepChanged.send(step)
}, [handle, step])
return createElement('div', { ref })handle is undefined before the first effect and after unmount. onEmbed
runs synchronously after embed, the place to subscribe to an outbound Port
without missing emissions from init. Give the ref'd element no React
children.
Limits
- No Foldkit children inside a React island: pass React children as
props.children, and place Foldkit content beside the island. - Foldkit Commands are not React Suspense. An outer
<Suspense>does not wait for an embedded program; the program shows its own loading states from its Model. - A server render emits an empty
<foldkit-react-host>; the React component renders on the client. - The event mapper's argument types come from the last overload of an overloaded callback prop.