| name | effect-v4-atom |
| description | Effect v4 Atom patterns — reactive state management with effect-atom. Atom.make, Atom.family, Registry, runtime atoms, and React integration via useAtomValue. |
| governed-by | metaskill |
Effect v4 Atom API — Canonical Reference
up: none
prereqs: none
provides: atom-patterns, reactive-state, atom-family, atom-runtime, react-hooks
children: CHANGELOG.md
governed-by: metaskill
Source of truth: submodules/effect-smol/packages/effect/src/unstable/reactivity/Atom.ts
React hooks: submodules/effect-smol/packages/atom/react/src/Hooks.ts
Import Path (v4)
import { Atom, AsyncResult, AtomRegistry, AtomRef, Reactivity } from "effect/unstable/reactivity"
React hooks (separate package):
import { useAtomValue, useAtomSet, useAtom, useAtomSuspense, useAtomMount, useAtomRefresh, useAtomSubscribe } from "@effect/atom-react"
Core Types
interface Atom<A> {
readonly keepAlive: boolean
readonly lazy: boolean
readonly read: (get: Context) => A
readonly refresh?: (f: <A>(atom: Atom<A>) => void) => void
readonly label?: readonly [name: string, stack: string]
readonly idleTTL?: number
}
interface Writable<R, W = R> extends Atom<R> {
readonly write: (ctx: WriteContext<R>, value: W) => void
}
Constructors
Atom.make — Universal constructor
const count = Atom.make(0)
const doubled = Atom.make((get) => get(count) * 2)
const user = Atom.make(
Effect.gen(function*() {
const api = yield* UserApi
return yield* api.fetchUser("u1")
})
)
const feed = Atom.make(
Stream.fromPubSub(pubsub)
)
const data = Atom.make(fetchEffect, { initialValue: [] })
Atom.readable / Atom.writable — Low-level constructors
const custom = Atom.readable((get) => {
const a = get(atomA)
const b = get(atomB)
return a + b
})
const customWritable = Atom.writable(
(get) => get(source),
(ctx, value) => ctx.setSelf(value)
)
Atom.family — Memoized parameterized atoms
const userAtom = Atom.family((id: string) =>
Atom.make(Effect.gen(function*() {
const api = yield* UserApi
return yield* api.fetchUser(id)
}))
)
Uses WeakRef + FinalizationRegistry for automatic cleanup when atoms are no longer referenced.
Atom.runtime — Service-backed atoms (Layer integration)
const appRuntime = Atom.runtime(
Layer.mergeAll(UserApi.layer, HttpClient.layer)
)
const usersAtom = appRuntime.atom(
Effect.gen(function*() {
const api = yield* UserApi
return yield* api.listUsers()
})
)
const createUser = appRuntime.fn(
(input: CreateUserInput, get) =>
Effect.gen(function*() {
const api = yield* UserApi
return yield* api.create(input)
})
)
AtomRuntime provides:
.atom(effect) — derived atom with services
.fn(fn) — callable atom function with services
.pull(stream) — pull-based stream consumption
.subscriptionRef(ref) — reactive subscription ref
Atom.context — Custom RuntimeFactory
const factory = Atom.context({ memoMap: Layer.makeMemoMapUnsafe() })
const runtime = factory(MyLayer)
Context API (inside read functions)
Atom.make((get) => {
get(otherAtom)
get.get(otherAtom)
get.once(otherAtom)
get.set(writableAtom, newValue)
get.setSelf(value)
get.self()
get.refresh(atom)
get.refreshSelf()
get.mount(atom)
get.addFinalizer(() => cleanup())
get.subscribe(atom, (v) => {})
get.stream(atom)
get.result(asyncAtom)
get.registry
})
Combinators
Atom.map(atom, (a) => transform(a))
Atom.setIdleTTL(atom, Duration.minutes(5))
Atom.keepAlive(atom)
Atom.withFallback(atom, fallbackAtom)
Atom.transform(atom, (get) => ...)
Atom.batch(registry, () => { ... })
React Hooks
const value = useAtomValue(atom)
const mapped = useAtomValue(atom, (a) => a.name)
const [value, setValue] = useAtom(writableAtom)
const set = useAtomSet(writableAtom)
const result = useAtomSuspense(asyncAtom)
useAtomMount(atom)
const refresh = useAtomRefresh(atom)
useAtomSubscribe(atom, (v) => console.log(v))
const refValue = useAtomRef(ref)
const propRef = useAtomRefProp(ref, "name")
Registry
import { AtomRegistry } from "effect/unstable/reactivity"
const registry = AtomRegistry
registry.get(atom)
registry.set(writableAtom, value)
registry.subscribe(atom, callback)
registry.mount(atom)
registry.refresh(atom)
AsyncResult (replaces old Result from effect-atom)
import { AsyncResult } from "effect/unstable/reactivity"
type AsyncResult<A, E> =
| { _tag: "Initial"; waiting: boolean }
| { _tag: "Success"; value: A; waiting: boolean }
| { _tag: "Failure"; cause: Cause<E>; waiting: boolean }
AsyncResult.initial()
AsyncResult.success(value)
AsyncResult.fail(error)
AsyncResult.fromExit(exit)
if (result._tag === "Success") { result.value }
if (result._tag === "Failure") { Cause.squash(result.cause) }
Key Differences from effect-atom v3
v3 (@effect-atom/atom) | v4 (effect/unstable/reactivity) |
|---|
import { Atom } from '@effect-atom/atom' | import { Atom } from 'effect/unstable/reactivity' |
import * as Result from '@effect-atom/atom/Result' | import { AsyncResult } from 'effect/unstable/reactivity' |
import * as Registry from '@effect-atom/atom/Registry' | import { AtomRegistry } from 'effect/unstable/reactivity' |
Result.isSuccess(r) | r._tag === "Success" |
Atom.runtime(Layer) | Atom.runtime(Layer) (same API, diff import) |
Registry.make() | AtomRegistry service tag |
| Separate package | Part of core effect (unstable) |