effract docs GitHub ↗

Signals

observe, atom, and useAtom — precise reactivity over Effect's AtomRef.

effract bridges Effect’s reactive cell (AtomRef) to React with surgical re-renders — and no stray Effect.runSync at the call site.

atom and observe

atom creates a reactive cell. observe reads one (or several) and subscribes the component precisely — it re-renders only when an atom it actually read changes.

import { atom, observe } from '@tmonier/effract';

const count = atom(0);

const doubled = observe(($) => $(count) * 2); // re-renders only when count changes

useAtom

Read and write a single atom, the useState shape — backed by Effect:

const [n, setN] = useAtom(count);

There’s also useAtomValue (read-only) and useAtomSet (a stable setter).

<Observe>

The render-prop form, for inline reactive values inside JSX:

<Observe>{($) => <b>{$(count)}</b>}</Observe>

State inside a service

For app state, put the atoms inside a service so they’re reachable from anywhere an Effect runs:

class Store extends Context.Service<Store, { likes: AtomRef.AtomRef<number>; like: () => void }>()(
  'app/Store',
) {}

const StoreLive = Layer.sync(Store)(() => {
  const likes = AtomRef.make(0);
  return { likes, like: () => likes.update((n) => n + 1) };
});

// in a component:
const store = yield* Store;
const likes = yield* hook(observe(($) => $(store.likes)));

The component reads reactive state via observe and mutates it by calling a real service method.