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