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.

Overview

The useAtomSuspense hook reads atoms that contain Result values and integrates with React Suspense. It automatically suspends the component while the async operation is pending and throws errors in the failure case (unless configured otherwise).

Signature

export const useAtomSuspense: <A, E, const IncludeFailure extends boolean = false>(
  atom: Atom.Atom<Result.Result<A, E>>,
  options?: {
    readonly suspendOnWaiting?: boolean | undefined
    readonly includeFailure?: IncludeFailure | undefined
  }
) => Result.Success<A, E> | (IncludeFailure extends true ? Result.Failure<A, E> : never)

Parameters

atom
Atom.Atom<Result.Result<A, E>>
required
An atom that contains a Result value. This is typically created with Atom.makeWithEffect or other async atom constructors.
options
object
Configuration options for Suspense behavior.
options.suspendOnWaiting
boolean
If true, the component will suspend when the result is in a “waiting” state (e.g., during background revalidation). Defaults to false.
options.includeFailure
boolean
If true, failure results are returned instead of thrown as errors. This allows you to handle errors in the component. Defaults to false.

Returns

result
Result.Success<A, E> | Result.Failure<A, E>
The result value from the atom:
  • When includeFailure is false (default): Returns only Result.Success<A, E>. Failures are thrown as exceptions.
  • When includeFailure is true: Returns either Result.Success<A, E> or Result.Failure<A, E>.
  • Suspends: When the result is Initial or (if suspendOnWaiting: true) when waiting is true.

Usage

Basic async data fetching

Fetch data with automatic Suspense:
import { useAtomSuspense } from "@effect-atom/atom-react"
import { Atom } from "@effect-atom/atom"
import { Effect } from "effect"
import { Suspense } from "react"

const userAtom = Atom.makeWithEffect((get) =>
  Effect.gen(function* () {
    const response = yield* Effect.tryPromise(() =>
      fetch("/api/user")
    )
    return yield* Effect.tryPromise(() => response.json())
  })
)

function UserProfile() {
  const result = useAtomSuspense(userAtom)
  
  return (
    <div>
      <h1>{result.value.name}</h1>
      <p>{result.value.email}</p>
    </div>
  )
}

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <UserProfile />
    </Suspense>
  )
}

Handling errors with includeFailure

Manage both success and failure states:
import { useAtomSuspense } from "@effect-atom/atom-react"
import { Atom } from "@effect-atom/atom"
import { Effect } from "effect"
import { Suspense } from "react"

const dataAtom = Atom.makeWithEffect((get) =>
  Effect.gen(function* () {
    const response = yield* Effect.tryPromise({
      try: () => fetch("/api/data"),
      catch: (error) => ({ _tag: "FetchError" as const, error })
    })
    
    if (!response.ok) {
      return yield* Effect.fail({ _tag: "ApiError" as const, status: response.status })
    }
    
    return yield* Effect.tryPromise({
      try: () => response.json(),
      catch: (error) => ({ _tag: "ParseError" as const, error })
    })
  })
)

function DataDisplay() {
  const result = useAtomSuspense(dataAtom, { includeFailure: true })
  
  if (result._tag === "Failure") {
    return <div className="error">Failed to load data</div>
  }
  
  return (
    <div>
      <pre>{JSON.stringify(result.value, null, 2)}</pre>
    </div>
  )
}

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <DataDisplay />
    </Suspense>
  )
}

Suspending on background updates

Suspend during revalidation:
import { useAtomSuspense } from "@effect-atom/atom-react"
import { Atom } from "@effect-atom/atom"
import { Effect } from "effect"
import { Suspense } from "react"

const pollAtom = Atom.makeWithEffect(
  (get) => fetchLatestData(),
  { refreshInterval: 5000 } // Refresh every 5 seconds
)

function LiveData() {
  // Will suspend on both initial load and background refreshes
  const result = useAtomSuspense(pollAtom, { suspendOnWaiting: true })
  
  return <div>Latest: {result.value}</div>
}

function App() {
  return (
    <Suspense fallback={<div>Updating...</div>}>
      <LiveData />
    </Suspense>
  )
}

Showing stale data during revalidation

Keep showing data while refreshing:
import { useAtomSuspense } from "@effect-atom/atom-react"
import { Atom } from "@effect-atom/atom"
import { Effect } from "effect"
import { Suspense } from "react"

const dataAtom = Atom.makeWithEffect((get) => fetchData())

function DataWithRefreshIndicator() {
  // Don't suspend on waiting - show stale data instead
  const result = useAtomSuspense(dataAtom, { suspendOnWaiting: false })
  
  return (
    <div>
      <div className="data">
        {result.value}
      </div>
      {result.waiting && (
        <span className="badge">Refreshing...</span>
      )}
    </div>
  )
}

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <DataWithRefreshIndicator />
    </Suspense>
  )
}

Error boundary integration

Use Error Boundaries to catch failures:
import { useAtomSuspense } from "@effect-atom/atom-react"
import { Atom } from "@effect-atom/atom"
import { Effect } from "effect"
import { Suspense, Component } from "react"

class ErrorBoundary extends Component {
  state = { hasError: false, error: null }
  
  static getDerivedStateFromError(error) {
    return { hasError: true, error }
  }
  
  render() {
    if (this.state.hasError) {
      return <div>Error: {this.state.error.message}</div>
    }
    return this.props.children
  }
}

const riskyAtom = Atom.makeWithEffect((get) =>
  Effect.gen(function* () {
    const data = yield* fetchRiskyData()
    if (!data) {
      return yield* Effect.fail(new Error("No data available"))
    }
    return data
  })
)

function RiskyComponent() {
  // Failures will be thrown and caught by ErrorBoundary
  const result = useAtomSuspense(riskyAtom)
  
  return <div>{result.value}</div>
}

function App() {
  return (
    <ErrorBoundary>
      <Suspense fallback={<div>Loading...</div>}>
        <RiskyComponent />
      </Suspense>
    </ErrorBoundary>
  )
}

Multiple async dependencies

Combine multiple async atoms:
import { useAtomSuspense } from "@effect-atom/atom-react"
import { Atom } from "@effect-atom/atom"
import { Effect } from "effect"
import { Suspense } from "react"

const userAtom = Atom.makeWithEffect((get) => fetchUser())
const postsAtom = Atom.makeWithEffect((get) => fetchPosts())

function UserDashboard() {
  const user = useAtomSuspense(userAtom)
  const posts = useAtomSuspense(postsAtom)
  
  return (
    <div>
      <h1>{user.value.name}'s Posts</h1>
      <ul>
        {posts.value.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  )
}

function App() {
  return (
    <Suspense fallback={<div>Loading dashboard...</div>}>
      <UserDashboard />
    </Suspense>
  )
}

Best practices

Always wrap components using useAtomSuspense with a Suspense boundary. Without one, React will throw an error.
Use suspendOnWaiting: false (default) to show stale data during background revalidation. This provides a better user experience than showing a loading spinner for updates.
By default, failures are thrown as exceptions. Use Error Boundaries to catch them, or set includeFailure: true to handle errors in your component.
This hook only works with atoms that contain Result values. For regular atoms, use useAtomValue or useAtom instead.

Build docs developers (and LLMs) love