Documentation Index
Fetch the complete documentation index at: https://mintlify.com/tim-smart/effect-atom/llms.txt
Use this file to discover all available pages before exploring further.
The @effect-atom/atom-react package provides a complete set of React hooks for integrating Effect Atom into your React applications.
Installation
pnpm add @effect-atom/atom-react
Core hooks
useAtomValue
Read the current value of an atom and subscribe to changes.
import { Atom, useAtomValue } from "@effect-atom/atom-react"
const countAtom = Atom.make(0)
function Counter() {
const count = useAtomValue(countAtom)
return <h1>{count}</h1>
}
useAtomValue automatically subscribes to atom changes and triggers re-renders when the value updates.
You can pass a transformation function to derive a new value:
function DoubleCounter() {
const doubled = useAtomValue(countAtom, (count) => count * 2)
return <h1>{doubled}</h1>
}
useAtomSet
Get a setter function for an atom without subscribing to its value.
import { Atom, useAtomSet } from "@effect-atom/atom-react"
const countAtom = Atom.make(0)
function CounterButton() {
const setCount = useAtomSet(countAtom)
return (
<button onClick={() => setCount((count) => count + 1)}>
Increment
</button>
)
}
Mode options
For atoms that return Result types, you can specify how to handle the result:
function Component() {
const setCount = useAtomSet(effectfulAtom)
// Returns void immediately
setCount(42)
}
useAtom
Combines useAtomValue and useAtomSet for reading and writing in a single hook.
import { Atom, useAtom } from "@effect-atom/atom-react"
const countAtom = Atom.make(0)
function Counter() {
const [count, setCount] = useAtom(countAtom)
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount((c) => c + 1)}>+</button>
<button onClick={() => setCount((c) => c - 1)}>-</button>
</div>
)
}
useAtomSuspense
Use React Suspense with atoms that return Result types.
import { Atom, useAtomSuspense, Result } from "@effect-atom/atom-react"
import { Effect } from "effect"
import { Suspense } from "react"
const userAtom = Atom.make(
Effect.gen(function* () {
const response = yield* Effect.tryPromise(() =>
fetch("/api/user").then(r => r.json())
)
return response
})
)
function UserProfile() {
const result = useAtomSuspense(userAtom)
return Result.builder(result)
.onSuccess((user) => <div>Hello, {user.name}!</div>)
.render()
}
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<UserProfile />
</Suspense>
)
}
Options
suspendOnWaiting: Whether to suspend when the result is in a waiting state (default: false)
includeFailure: Include failure results instead of throwing (default: false)
// Suspend while refetching
const result = useAtomSuspense(userAtom, { suspendOnWaiting: true })
// Handle failures manually
const result = useAtomSuspense(userAtom, { includeFailure: true })
if (result._tag === "Failure") {
return <div>Error: {Cause.pretty(result.cause)}</div>
}
useAtomRefresh
Get a function to manually refresh an atom’s value.
import { Atom, useAtomValue, useAtomRefresh } from "@effect-atom/atom-react"
import { Effect } from "effect"
const timeAtom = Atom.make(Effect.sync(() => Date.now()))
function CurrentTime() {
const result = useAtomValue(timeAtom)
const refresh = useAtomRefresh(timeAtom)
return (
<div>
<p>Current time: {Result.getOrElse(result, () => 0)}</p>
<button onClick={refresh}>Refresh</button>
</div>
)
}
Additional hooks
useAtomMount
Mount an atom without reading or writing its value. Useful for atoms with side effects.
import { Atom, useAtomMount } from "@effect-atom/atom-react"
const analyticsAtom = Atom.make(
Effect.gen(function* () {
yield* Effect.log("Component mounted")
yield* Effect.addFinalizer(() => Effect.log("Component unmounted"))
})
)
function TrackedComponent() {
useAtomMount(analyticsAtom)
return <div>This component is tracked</div>
}
useAtomSubscribe
Subscribe to atom changes with a custom callback.
import { Atom, useAtomSubscribe } from "@effect-atom/atom-react"
const countAtom = Atom.make(0)
function Logger() {
useAtomSubscribe(
countAtom,
(count) => console.log("Count changed:", count),
{ immediate: true }
)
return null
}
useAtomRef
Subscribe to an AtomRef value.
import { AtomRef, useAtomRef } from "@effect-atom/atom-react"
const formRef = AtomRef.make({ name: "", email: "" })
function FormDisplay() {
const form = useAtomRef(formRef)
return <div>{form.name} - {form.email}</div>
}
useAtomInitialValues
Set initial values for atoms on the server or during hydration.
import { Atom, useAtomInitialValues } from "@effect-atom/atom-react"
const userAtom = Atom.make<User | null>(null)
function App({ initialUser }: { initialUser: User }) {
useAtomInitialValues([[userAtom, initialUser]])
// ...
}
Registry context
By default, all atoms share a global registry. You can create isolated registries using RegistryProvider.
RegistryProvider
Provide a custom registry to a component tree.
import { RegistryProvider } from "@effect-atom/atom-react"
function App() {
return (
<RegistryProvider
defaultIdleTTL={1000}
initialValues={[[countAtom, 10]]}
>
<Counter />
</RegistryProvider>
)
}
Props
children: React elements to render
initialValues: Initial atom values as [atom, value] pairs
scheduleTask: Custom task scheduler function
timeoutResolution: Timeout resolution in milliseconds
defaultIdleTTL: Default time-to-live for idle atoms in milliseconds
Each RegistryProvider creates an isolated atom state scope. Atoms are not shared between different registries.
RegistryContext
Access the current registry directly.
import { RegistryContext } from "@effect-atom/atom-react"
import { useContext } from "react"
function CustomHook() {
const registry = useContext(RegistryContext)
// Use registry directly
}
Server-side rendering
Effect Atom supports SSR with Next.js and other React frameworks.
HydrationBoundary
Hydrate atoms from server-rendered state.
import { HydrationBoundary } from "@effect-atom/atom-react"
import type { DehydratedAtom } from "@effect-atom/atom/Hydration"
function Page({ dehydratedState }: { dehydratedState: DehydratedAtom[] }) {
return (
<HydrationBoundary state={dehydratedState}>
<Counter />
</HydrationBoundary>
)
}
Server snapshots
Atoms automatically provide server snapshots via Atom.getServerValue for SSR compatibility.
const countAtom = Atom.make(0)
// During SSR, useAtomValue uses getServerSnapshot internally
function Counter() {
const count = useAtomValue(countAtom) // Works in SSR
return <h1>{count}</h1>
}
Atoms with asynchronous effects return Result.Initial during SSR. Use Result.getOrElse or useAtomSuspense to handle loading states.
Working with streams
Effect Atom works seamlessly with Effect streams for reactive data sources.
import { Atom, Result, useAtom } from "@effect-atom/atom-react"
import { Cause, Schedule, Stream } from "effect"
// Auto-incrementing counter stream
const countAtom = Atom.make(Stream.fromSchedule(Schedule.spaced(1000)))
function StreamCounter() {
const result = useAtomValue(countAtom)
return Result.builder(result)
.onInitial(() => <div>Loading...</div>)
.onFailure((cause) => <div>Error: {Cause.pretty(cause)}</div>)
.onSuccess((count) => <div>Count: {count}</div>)
.render()
}
Pull-based streams
For paginated or infinite scroll data:
import { Atom, Result, useAtom } from "@effect-atom/atom-react"
import { Stream } from "effect"
const itemsAtom = Atom.pull(Stream.range(1, 100))
function InfiniteList() {
const [result, loadMore] = useAtom(itemsAtom)
return Result.builder(result)
.onSuccess(({ items, done }) => (
<div>
<ul>
{items.map(item => <li key={item}>{item}</li>)}
</ul>
{!done && <button onClick={() => loadMore()}>Load more</button>}
</div>
))
.render()
}
Complete example
import { Atom, useAtomValue, useAtomSet } from "@effect-atom/atom-react"
const countAtom = Atom.make(0).pipe(Atom.keepAlive)
function App() {
return (
<div>
<Counter />
<CounterButton />
</div>
)
}
function Counter() {
const count = useAtomValue(countAtom)
return <h1>{count}</h1>
}
function CounterButton() {
const setCount = useAtomSet(countAtom)
return (
<button onClick={() => setCount((count) => count + 1)}>
Increment
</button>
)
}