Skip to main content

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.

Transform values

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>
  )
}

Build docs developers (and LLMs) love