mcpField
J
jotai-expert

jotai-expert

github.com/s-hiraokuDevelopment0204 installsUpdated 37w ago
Repository

About jotai-expert

jotai-expert is a skill for implementing and reviewing React state management with Jotai. It covers atom and hook selection, performance patterns, async state, and implementation pitfalls.

Key features

  1. 01Atom types and hook selection
  2. 02Granular subscriptions
  3. 03Efficient list and object state
  4. 04Async state with loadable
  5. 05Guidance on common anti-patterns

Use cases

  1. 01Choosing state management for shared React state
  2. 02Reducing component re-renders
  3. 03Managing large forms and lists
  4. 04Handling asynchronous atom data

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/s-hiraoku/skills-factory --skill jotai-expert

After installing, type/jotai-expertin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
core-api.md
patterns.md
performance.md
utilities.md

Details

From the project README

Jotai Expert

Jotai is a primitive and flexible state management library for React using an atomic approach. Always reference https://jotai.org/ for the latest API details.

Decision Tree

Need state management?
├── Simple local state → useState (no Jotai needed)
├── Shared state across components
│   ├── Few components, same tree → Context may suffice
│   └── Many components, complex → Use Jotai ✓
└── Global app state → Use Jotai ✓

Choosing atom type:
├── Static value → atom(initialValue)
├── Computed from other atoms → atom((get) => ...)
├── Need to modify other atoms → atom(null, (get, set) => ...)
├── Persist to storage → atomWithStorage()
├── List of items → splitAtom() or atoms-in-atom
└── Parameterized data → atomFamily()

Performance issue?
├── Component re-renders too often
│   ├── Only reading? → useAtomValue
│   ├── Only writing? → useSetAtom
│   ├── Large object? → selectAtom / focusAtom
│   └── List items? → splitAtom
└── Async loading states → loadable() or manual loading atoms

Core Patterns

Atom Types
import { atom } from 'jotai'

// 1. Primitive: holds value
const countAtom = atom(0)

// 2. Derived read-only: computed from others
const doubleAtom = atom((get) => get(countAtom) * 2)

// 3. Derived read-write: custom setter
const celsiusAtom = atom(0)
const fahrenheitAtom = atom(
  (get) => get(celsiusAtom) * 9/5 + 32,
  (get, set, newF: number) => set(celsiusAtom, (newF - 32) * 5/9)
)

// 4. Write-only (action): no read value
const incrementAtom = atom(null, (get, set) => {
  set(countAtom, get(countAtom) + 1)
})
Hook Selection
NeedHookRe-renders on change
Read onlyuseAtomValue(atom)Yes
Write onlyuseSetAtom(atom)No
BothuseAtom(atom)Yes
Reference Stability
// ❌ WRONG: Creates new atom every render
function Component() {
  const myAtom = atom(0) // Unstable reference
}

// ✅ CORRECT: Define outside component
const myAtom = atom(0)
function Component() {
  const [value] = useAtom(myAtom)
}

// ✅ CORRECT: useMemo for dynamic atoms
function Component({ id }) {
  const itemAtom = useMemo(() => atom(items[id]), [id])
}

Performance Optimization

1. Granular Subscriptions
// ❌ BAD: Re-renders on any user field change
function UserProfile() {
  const [user] = useAtom(userAtom)
  return <h1>{user.name}</h1>
}

// ✅ GOOD: Only re-renders when name changes
import { selectAtom } from 'jotai/utils'
const nameAtom = selectAtom(userAtom, (u) => u.name)

function UserName() {
  const name = useAtomValue(nameAtom)
  return <h1>{name}</h1>
}
2. Efficient Lists with splitAtom
import { splitAtom } from 'jotai/utils'

const todosAtom = atom<Todo[]>([])
const todoAtomsAtom = splitAtom(todosAtom, (t) => t.id)

function TodoList() {
  const [todoAtoms] = useAtom(todoAtomsAtom)
  return todoAtoms.map((todoAtom) => (
    <TodoItem key={todoAtom.toString()} atom={todoAtom} />
  ))
}

// Each item updates independently
function TodoItem({ atom }) {
  const [todo, setTodo] = useAtom(atom)
  // Changes here don't re-render other items
}
3. Large Objects with focusAtom
import { focusAtom } from 'jotai-optics'

const formAtom = atom({ name: '', email: '', address: { city: '' } })

// Focused atoms for each field
const nameAtom = focusAtom(formAtom, (o) => o.prop('name'))
const cityAtom = focusAtom(formAtom, (o) => o.prop('address').prop('city'))

// Each field component only re-renders when its value changes
4. Async with loadable
import { loadable } from 'jotai/utils'

const asyncDataAtom = atom(async () => fetch('/api').then(r => r.json()))
const loadableDataAtom = loadable(asyncDataAtom)

function Component() {
  const data = useAtomValue(loadableDataAtom)
  if (data.state === 'loading') return <Spinner />
  if (data.state === 'hasError') return <Error />
  return <Data value={data.data} />
}

Anti-Patterns to Avoid

  1. Heavy computation in components: Move to atom read functions or actions
  2. Creating atoms in render: Define outside or use useMemo
  3. Using useAtom when only reading/writing: Use useAtomValue/useSetAtom
  4. Over-fragmenting frequently-updated data: Batch related updates
  5. Nested Suspense for every async atom: Use strategic boundaries or loadable

References

Detailed documentation organized by topic:

When implementing or reviewing Jotai code, load the relevant reference file for detailed guidance.

Comments

Markdown supported