Jotai is built with TypeScript and provides excellent type inference out of the box. This guide covers TypeScript usage patterns and best practices.
Version Requirements
Jotai uses TypeScript 3.8+ syntax. Make sure you’re using TypeScript 3.8 or higher.
Jotai relies heavily on type inference and requires strictNullChecks to be enabled. We recommend adding "strict": true in your tsconfig.json.
Primitive Atoms
Type Inference
Primitive atoms are automatically type inferred:
const numAtom = atom(0) // PrimitiveAtom<number>
const strAtom = atom('hello') // PrimitiveAtom<string>
const boolAtom = atom(true) // PrimitiveAtom<boolean>
Explicit Typing
You can explicitly type primitive atoms when needed:
const numAtom = atom<number>(0)
const numAtom = atom<number | null>(null)
const arrAtom = atom<string[]>([])
const userAtom = atom<User | undefined>(undefined)
Derived Atoms
Type Inference (Recommended)
In most cases, derived atoms can have their types inferred automatically:
// Read-only derived atoms
const readOnlyAtom = atom((get) => get(numAtom))
const asyncReadOnlyAtom = atom(async (get) => await get(someAsyncAtom))
// Write-only atoms
const writeOnlyAtom = atom(null, (_get, set, str: string) => {
set(fooAtom, str)
})
const multipleArgumentsAtom = atom(
null,
(_get, set, valueOne: number, valueTwo: number) => {
set(fooAtom, Math.max(valueOne, valueTwo))
}
)
// Read/Write atoms
const readWriteAtom = atom(
(get) => get(strAtom),
(_get, set, num: number) => set(strAtom, String(num))
)
Explicit Typing
For complex scenarios, you can explicitly type derived atoms:
const asyncStrAtom = atom<Promise<string>>(async () => 'foo')
// Write-only atoms with explicit types
// Type parameters: [read value, write args, write return]
const writeOnlyAtom = atom<null, [string, number], void>(
null,
(_get, set, stringValue, numberValue) => {
set(fooAtom, stringValue)
}
)
// Read/Write atoms with explicit types
const readWriteAtom = atom<Promise<string>, [number], void>(
async (get) => await get(asyncStrAtom),
(_get, set, num) => set(strAtom, String(num))
)
Using Atoms in Components
useAtom Type Inference
The useAtom hook is automatically typed based on the atom:
const [num, setNum] = useAtom(primitiveNumAtom) // [number, SetAtom<number, void>]
const [num] = useAtom(readOnlyNumAtom) // [number]
const [, setNum] = useAtom(writeOnlyNumAtom) // [never, SetAtom<Args, Result>]
Use ExtractAtomValue to get the value type of an atom:
import { ExtractAtomValue, useAtomValue } from 'jotai'
import { userAtom } from './state'
function useGetReviewQuery(user: ExtractAtomValue<typeof userAtom>) {
return fetch('/api/user/' + user.id + '/review')
}
export default function WriteReview() {
const user = useAtomValue(userAtom)
const res = useGetReviewQuery(user)
}
Async Atoms
Async Read Atoms
const asyncDataAtom = atom(async () => {
const response = await fetch('/api/data')
return response.json()
})
// Type is inferred as Atom<Promise<any>>
Typed Async Atoms
interface User {
id: string
name: string
}
const userAtom = atom<Promise<User>>(async () => {
const response = await fetch('/api/user')
return response.json()
})
Mixed Sync/Async Values
const baseAtom = atom<number | Promise<number>>(0)
// Can accept both sync and async values
const Component = () => {
const [value, setValue] = useAtom(baseAtom)
const handleSync = () => setValue(42)
const handleAsync = () => setValue(fetch('/api/number'))
}
Common Patterns
Optional Values
const userAtom = atom<User | null>(null)
const optionalCountAtom = atom<number | undefined>(undefined)
Generic Atoms
function atomWithDefault<T>(defaultValue: T) {
const baseAtom = atom<T | null>(null)
return atom(
(get) => get(baseAtom) ?? defaultValue,
(_get, set, value: T) => set(baseAtom, value)
)
}
const myAtom = atomWithDefault<string>('hello')
Atom Families
import { atomFamily } from 'jotai/utils'
interface TodoParams {
id: string
}
interface Todo {
id: string
title: string
completed: boolean
}
const todoAtomFamily = atomFamily((param: TodoParams) =>
atom<Todo>({
id: param.id,
title: '',
completed: false,
})
)
Tips
Let TypeScript infer types when possible. Explicit typing is only needed for complex scenarios or when you need to accept multiple types.
Use ExtractAtomValue to extract atom value types instead of duplicating type definitions.
If you encounter type errors with async atoms, make sure you’re using Suspense or wrapping with loadable.