Components
component, view, and hook — the two ways to write a component as an Effect program.
effract gives you two component constructors. Both produce a REC (React Effect Component). You
write a REC only for a component that reaches for the runtime — a service, or a hook bridged through
Effect. Plain React components stay plain: ordinary functions used as <Panel /> JSX, untouched.
The two compose freely in the same tree.
component — the hook-capable REC
The headline. The body yields Effect services and effects, and yield* hook(...) for React hooks,
all interpreted inside the render pass. Note that Panel below is a plain React component placed as
ordinary JSX — effract never asks you to rewrite it.
const Dashboard = rec(function* () {
const stats = yield* Stats;
const [tab, setTab] = yield* hook(useState('overview'));
return <Panel tab={tab} total={stats.total} onTab={setTab} />; // Panel is plain React
});
The component’s required services are inferred from what it yields, so mount can verify the layer
provides them.
Placing a REC
A REC is not a JSX element — <Dashboard /> is a compile error. You place a REC by yield*-ing
it inside another component’s returned JSX:
const Page = rec(function* () {
return (
<main>
{yield* Dashboard /* no props */}
{yield* Greet.with({ name: 'Ada' }) /* with props */}
</main>
);
});
Plain components stay normal JSX, so a REC and a plain component sit side by side:
<Card>{yield* Counter}</Card>.
view — resolve up front
When a component is pure data → markup (no hooks), view runs it as a single Effect. It’s the
simpler, RSC-friendly mode — ideal near the root for flags, the current user, or permissions.
const Banner = view(
Effect.gen(function* () {
const flags = yield* Flags;
return flags.beta ? <aside>You're on the beta.</aside> : null;
}),
);
Async and Suspense
Reading an asynchronous effect suspends the component through React Suspense and resumes inline when
the value is ready — no useEffect, no isLoading flag:
const Profile = rec(function* () {
const api = yield* Api;
const user = yield* api.fetchUser(); // suspends here
return <h2>Welcome, {user.name}</h2>;
});
// <Suspense> is a host element (plain JSX); the Profile REC is placed by yielding it
const Account = rec(function* () {
return <Suspense fallback={<Spinner />}>{yield* Profile}</Suspense>;
});
Errors in the Effect channel are thrown to the nearest React error boundary.