Skip to main content
selectAtom creates a read-only derived atom that selects and memoizes a slice of another atom’s value. It only re-renders when the selected slice changes according to the equality function.

Signature

function selectAtom<Value, Slice>(
  anAtom: Atom<Value>,
  selector: (value: Value, prevSlice?: Slice) => Slice,
  equalityFn?: (a: Slice, b: Slice) => boolean
): Atom<Slice>
anAtom
Atom<Value>
required
The source atom to select from
selector
(value: Value, prevSlice?: Slice) => Slice
required
Function that extracts the slice from the atom’s value
equalityFn
(a: Slice, b: Slice) => boolean
Custom equality function to determine if the slice changed. Defaults to Object.is

Usage

Basic field selection

import { selectAtom } from 'jotai/utils'
import { atom, useAtom } from 'jotai'

const userAtom = atom({
  name: 'John',
  age: 30,
  email: '[email protected]'
})

const nameAtom = selectAtom(userAtom, (user) => user.name)

function UserName() {
  const [name] = useAtom(nameAtom)
  // Only re-renders when name changes, not when age or email change
  return <div>{name}</div>
}

Selecting multiple fields

import { selectAtom } from 'jotai/utils'
import { atom, useAtom } from 'jotai'

const userAtom = atom({
  name: 'John',
  age: 30,
  email: '[email protected]',
  address: '123 Main St'
})

const contactAtom = selectAtom(
  userAtom,
  (user) => ({ name: user.name, email: user.email }),
  (a, b) => a.name === b.name && a.email === b.email
)

function ContactInfo() {
  const [contact] = useAtom(contactAtom)
  // Only re-renders when name or email change
  return (
    <div>
      <div>{contact.name}</div>
      <div>{contact.email}</div>
    </div>
  )
}

Array filtering

import { selectAtom } from 'jotai/utils'
import { atom, useAtom } from 'jotai'

const todosAtom = atom([
  { id: 1, text: 'Buy milk', completed: false },
  { id: 2, text: 'Walk dog', completed: true },
  { id: 3, text: 'Write code', completed: false }
])

const activeTodosAtom = selectAtom(
  todosAtom,
  (todos) => todos.filter(todo => !todo.completed)
)

function ActiveTodos() {
  const [activeTodos] = useAtom(activeTodosAtom)
  
  return (
    <ul>
      {activeTodos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  )
}

Deep equality for objects

import { selectAtom } from 'jotai/utils'
import { atom, useAtom } from 'jotai'
import isEqual from 'lodash/isEqual'

const stateAtom = atom({
  user: { name: 'John', age: 30 },
  settings: { theme: 'dark', notifications: true }
})

const userAtom = selectAtom(
  stateAtom,
  (state) => state.user,
  isEqual // Deep equality comparison
)

function UserProfile() {
  const [user] = useAtom(userAtom)
  // Only re-renders when user object actually changes
  return <div>{user.name}, {user.age}</div>
}

Computed selection

import { selectAtom } from 'jotai/utils'
import { atom, useAtom } from 'jotai'

const cartAtom = atom([
  { id: 1, name: 'Apple', price: 1.5, quantity: 3 },
  { id: 2, name: 'Banana', price: 0.8, quantity: 5 },
  { id: 3, name: 'Orange', price: 1.2, quantity: 2 }
])

const totalAtom = selectAtom(
  cartAtom,
  (cart) => cart.reduce((sum, item) => sum + item.price * item.quantity, 0)
)

function CartTotal() {
  const [total] = useAtom(totalAtom)
  
  return <div>Total: ${total.toFixed(2)}</div>
}

Using previous slice

import { selectAtom } from 'jotai/utils'
import { atom, useAtom } from 'jotai'

const countersAtom = atom({
  a: 0,
  b: 0,
  c: 0
})

// Track how counter 'a' changed compared to last selection
const counterADeltaAtom = selectAtom(
  countersAtom,
  (counters, prevSlice) => {
    const current = counters.a
    const previous = prevSlice?.value ?? current
    return {
      value: current,
      delta: current - previous
    }
  }
)

function CounterAWithDelta() {
  const [counterA] = useAtom(counterADeltaAtom)
  
  return (
    <div>
      <div>Current: {counterA.value}</div>
      <div>Change: {counterA.delta > 0 ? '+' : ''}{counterA.delta}</div>
    </div>
  )
}

Selecting from async atoms

import { selectAtom } from 'jotai/utils'
import { atom, useAtom } from 'jotai'
import { Suspense } from 'react'

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

const userNameAtom = selectAtom(userDataAtom, (data) => data.name)

function UserName() {
  const [name] = useAtom(userNameAtom)
  return <div>{name}</div>
}

function App() {
  return (
    <Suspense fallback="Loading...">
      <UserName />
    </Suspense>
  )
}

Features

  • Memoization: Only updates when the selected slice changes
  • Custom equality: Use custom equality functions for complex comparisons
  • Performance: Prevents unnecessary re-renders by comparing only the slice
  • Type-safe: Full TypeScript support with proper type inference
  • Previous value access: Selector can access the previous slice value

Notes

  • The default equality function is Object.is (reference equality)
  • For object/array selections, consider using deep equality functions like lodash/isEqual
  • The selector function is called every time the source atom updates
  • Only the equality check determines if consumers re-render
  • The previous slice value is available in the selector on subsequent calls
  • Works with both sync and async atoms

Performance comparison

Without selectAtom:
// This re-renders whenever ANY property changes
function UserName() {
  const [user] = useAtom(userAtom)
  return <div>{user.name}</div>
}
With selectAtom:
// This only re-renders when name changes
const nameAtom = selectAtom(userAtom, (user) => user.name)

function UserName() {
  const [name] = useAtom(nameAtom)
  return <div>{name}</div>
}

Build docs developers (and LLMs) love