Skip to main content
atomWithDefault creates an atom with a default value that is computed from other atoms. The atom can be reset to recompute its default value.

Signature

function atomWithDefault<Value>(
  getDefault: (get: Getter, options: Options) => Value
): WritableAtom<Value, [DefaultSetStateAction<Value>], void>

type DefaultSetStateAction<Value> =
  | Value
  | typeof RESET
  | ((prev: Value) => Value | typeof RESET)
getDefault
(get: Getter, options: Options) => Value
required
Function that computes the default value. Has access to get to read other atoms

Usage

Basic dynamic default

import { atomWithDefault, RESET } from 'jotai/utils'
import { atom, useAtom } from 'jotai'

const baseCountAtom = atom(10)

// Default value is computed from baseCountAtom
const countAtom = atomWithDefault((get) => get(baseCountAtom))

function Counter() {
  const [baseCount, setBaseCount] = useAtom(baseCountAtom)
  const [count, setCount] = useAtom(countAtom)
  
  return (
    <div>
      <div>Base: {baseCount}</div>
      <div>Count: {count}</div>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
      <button onClick={() => setCount(RESET)}>Reset to base</button>
      <button onClick={() => setBaseCount(b => b + 5)}>Increase base</button>
    </div>
  )
}

User preferences with fallback

import { atomWithDefault, RESET } from 'jotai/utils'
import { atom, useAtom } from 'jotai'

const systemThemeAtom = atom<'light' | 'dark'>('light')

// Use system theme as default
const themeAtom = atomWithDefault((get) => get(systemThemeAtom))

function ThemeSelector() {
  const [systemTheme] = useAtom(systemThemeAtom)
  const [theme, setTheme] = useAtom(themeAtom)
  
  return (
    <div>
      <p>System theme: {systemTheme}</p>
      <p>Current theme: {theme}</p>
      <button onClick={() => setTheme('light')}>Light</button>
      <button onClick={() => setTheme('dark')}>Dark</button>
      <button onClick={() => setTheme(RESET)}>Use system</button>
    </div>
  )
}

Form with server-loaded defaults

import { atomWithDefault, RESET } from 'jotai/utils'
import { atom, useAtom } from 'jotai'

const userDataAtom = atom(async () => {
  const response = await fetch('/api/user')
  return response.json()
})

interface UserForm {
  name: string
  email: string
  bio: string
}

const userFormAtom = atomWithDefault<UserForm>(async (get) => {
  const data = await get(userDataAtom)
  return {
    name: data.name,
    email: data.email,
    bio: data.bio || ''
  }
})

function UserProfile() {
  const [form, setForm] = useAtom(userFormAtom)
  
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    await fetch('/api/user', {
      method: 'PUT',
      body: JSON.stringify(form)
    })
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        value={form.name}
        onChange={(e) => setForm({ ...form, name: e.target.value })}
      />
      <input
        value={form.email}
        onChange={(e) => setForm({ ...form, email: e.target.value })}
      />
      <textarea
        value={form.bio}
        onChange={(e) => setForm({ ...form, bio: e.target.value })}
      />
      <button type="submit">Save</button>
      <button type="button" onClick={() => setForm(RESET)}>Reset</button>
    </form>
  )
}

Computed default from multiple atoms

import { atomWithDefault, RESET } from 'jotai/utils'
import { atom, useAtom } from 'jotai'

const firstNameAtom = atom('John')
const lastNameAtom = atom('Doe')

const fullNameAtom = atomWithDefault((get) => {
  const first = get(firstNameAtom)
  const last = get(lastNameAtom)
  return `${first} ${last}`
})

function NameEditor() {
  const [firstName, setFirstName] = useAtom(firstNameAtom)
  const [lastName, setLastName] = useAtom(lastNameAtom)
  const [fullName, setFullName] = useAtom(fullNameAtom)
  
  return (
    <div>
      <input
        value={firstName}
        onChange={(e) => setFirstName(e.target.value)}
        placeholder="First name"
      />
      <input
        value={lastName}
        onChange={(e) => setLastName(e.target.value)}
        placeholder="Last name"
      />
      <div>
        <p>Full name: {fullName}</p>
        <input
          value={fullName}
          onChange={(e) => setFullName(e.target.value)}
        />
        <button onClick={() => setFullName(RESET)}>
          Reset to computed name
        </button>
      </div>
    </div>
  )
}

Features

  • Dynamic defaults: Default value is computed from other atoms
  • Async support: The getDefault function can be async
  • Resettable: Use RESET to revert to the computed default value
  • Reactive: Default value updates when dependencies change (only when reset)
  • Type-safe: Full TypeScript support

Notes

  • The default value is only recomputed when the atom is reset with RESET
  • Once the atom is written to, it holds that value until reset
  • The getDefault function has access to get for reading other atoms
  • The getDefault function can be async and return a Promise
  • Resetting the atom will trigger a recomputation of the default value

Difference from regular atoms

A regular derived atom always recomputes when dependencies change:
// This always shows firstName + lastName
const fullNameAtom = atom(
  (get) => `${get(firstNameAtom)} ${get(lastNameAtom)}`
)
With atomWithDefault, the value is independent after being set:
// This can be overwritten and holds its value
const fullNameAtom = atomWithDefault(
  (get) => `${get(firstNameAtom)} ${get(lastNameAtom)}`
)
// User can edit it, and it won't change when firstName/lastName change
// unless you call setFullName(RESET)

Build docs developers (and LLMs) love