Skip to main content

Function Signature

function createAsyncAtom<T>(
  getValue: () => Promise<T>,
  options?: AtomOptions<AsyncAtomState<T>>
): ReadonlyAtom<AsyncAtomState<T>>

type AsyncAtomState<TData, TError = unknown> =
  | { status: 'pending' }
  | { status: 'done'; data: TData }
  | { status: 'error'; error: TError }
Creates a reactive atom that manages asynchronous operations. The atom automatically tracks the state of the promise (pending, done, or error) and updates subscribers when the promise resolves or rejects.

Parameters

getValue
() => Promise<T>
required
An async function that returns a Promise. This function is called immediately when the atom is created and whenever the atom is recomputed (if it depends on other atoms).
options
AtomOptions<AsyncAtomState<T>>
Optional configuration for the atom.

Return Type

ReadonlyAtom<AsyncAtomState<T>>
ReadonlyAtom<AsyncAtomState<T>>
Returns a readonly atom containing the async operation state. The state is a discriminated union:

Examples

Basic Async Atom

import { createAsyncAtom } from '@tanstack/store'

const userAtom = createAsyncAtom(async () => {
  const response = await fetch('https://api.example.com/user/1')
  return response.json()
})

// Initially pending
console.log(userAtom.get())
// { status: 'pending' }

// After promise resolves
setTimeout(() => {
  const state = userAtom.get()
  if (state.status === 'done') {
    console.log(state.data)
    // { id: 1, name: 'John Doe', ... }
  }
}, 1000)

Handling All States

import { createAsyncAtom } from '@tanstack/store'

const dataAtom = createAsyncAtom(async () => {
  const response = await fetch('https://api.example.com/data')
  if (!response.ok) {
    throw new Error('Failed to fetch data')
  }
  return response.json()
})

function renderData() {
  const state = dataAtom.get()
  
  switch (state.status) {
    case 'pending':
      return 'Loading...'
    
    case 'done':
      return `Data: ${JSON.stringify(state.data)}`
    
    case 'error':
      return `Error: ${state.error.message}`
  }
}

Subscribing to Async State Changes

import { createAsyncAtom } from '@tanstack/store'

const todosAtom = createAsyncAtom(async () => {
  const response = await fetch('https://api.example.com/todos')
  return response.json()
})

// Subscribe to state changes
const subscription = todosAtom.subscribe((state) => {
  if (state.status === 'pending') {
    console.log('Fetching todos...')
  } else if (state.status === 'done') {
    console.log('Todos loaded:', state.data)
  } else if (state.status === 'error') {
    console.error('Failed to load todos:', state.error)
  }
})

// Unsubscribe when done
subscription.unsubscribe()

Reactive Async Atoms

import { createAtom, createAsyncAtom } from '@tanstack/store'

const userIdAtom = createAtom(1)

// Async atom that refetches when userIdAtom changes
const userAtom = createAsyncAtom(async () => {
  const userId = userIdAtom.get()
  const response = await fetch(`https://api.example.com/users/${userId}`)
  return response.json()
})

// Initially fetches user 1
console.log(userAtom.get().status) // 'pending'

// Change user ID - automatically refetches
userIdAtom.set(2)

// Now fetching user 2
console.log(userAtom.get().status) // 'pending'

Error Handling

import { createAsyncAtom } from '@tanstack/store'

interface User {
  id: number
  name: string
}

const userAtom = createAsyncAtom<User>(async () => {
  const response = await fetch('https://api.example.com/user')
  
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`)
  }
  
  const data = await response.json()
  
  if (!data.id || !data.name) {
    throw new Error('Invalid user data')
  }
  
  return data
})

function displayUser() {
  const state = userAtom.get()
  
  if (state.status === 'error') {
    console.error('Error loading user:', state.error)
    return null
  }
  
  if (state.status === 'pending') {
    return 'Loading...'
  }
  
  return state.data.name
}

TypeScript Error Types

import { createAsyncAtom } from '@tanstack/store'
import type { ReadonlyAtom } from '@tanstack/store'

interface ApiError {
  code: string
  message: string
}

type AsyncAtomState<TData, TError = unknown> =
  | { status: 'pending' }
  | { status: 'done'; data: TData }
  | { status: 'error'; error: TError }

const dataAtom: ReadonlyAtom<AsyncAtomState<string, ApiError>> = 
  createAsyncAtom(async () => {
    try {
      const response = await fetch('https://api.example.com/data')
      return await response.text()
    } catch (err) {
      throw {
        code: 'FETCH_ERROR',
        message: err instanceof Error ? err.message : 'Unknown error'
      } as ApiError
    }
  })

const state = dataAtom.get()
if (state.status === 'error') {
  // TypeScript knows error is ApiError
  console.log(state.error.code, state.error.message)
}

Combining Multiple Async Atoms

import { createAsyncAtom, createAtom } from '@tanstack/store'

const userAtom = createAsyncAtom(async () => {
  const response = await fetch('https://api.example.com/user')
  return response.json()
})

const postsAtom = createAsyncAtom(async () => {
  const response = await fetch('https://api.example.com/posts')
  return response.json()
})

// Computed atom that combines both async atoms
const combinedAtom = createAtom(() => {
  const userState = userAtom.get()
  const postsState = postsAtom.get()
  
  if (userState.status === 'pending' || postsState.status === 'pending') {
    return { status: 'pending' as const }
  }
  
  if (userState.status === 'error') {
    return { status: 'error' as const, error: userState.error }
  }
  
  if (postsState.status === 'error') {
    return { status: 'error' as const, error: postsState.error }
  }
  
  return {
    status: 'done' as const,
    data: {
      user: userState.data,
      posts: postsState.data
    }
  }
})

Retry Logic

import { createAsyncAtom } from '@tanstack/store'

async function fetchWithRetry<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3
): Promise<T> {
  let lastError: Error
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn()
    } catch (error) {
      lastError = error as Error
      if (i < maxRetries - 1) {
        await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)))
      }
    }
  }
  
  throw lastError!
}

const dataAtom = createAsyncAtom(async () => {
  return fetchWithRetry(async () => {
    const response = await fetch('https://api.example.com/data')
    if (!response.ok) throw new Error('Fetch failed')
    return response.json()
  })
})

Key Features

Automatic State Tracking

Tracks pending, success, and error states automatically without manual state management.

Type-Safe States

TypeScript discriminated unions ensure type safety when accessing data or error properties.

Reactive Dependencies

Automatically refetches when dependent atoms change, keeping async data fresh.

Observable Updates

Subscribers are notified when the async operation completes or fails.