mcpField
J
jotai-expert

jotai-expert

github.com/s-hiraoku開発0204 回インストール37 週間前に更新
リポジトリ

jotai-expert について

jotai-expert は、Jotai を使った React の状態管理を実装・レビューするスキルです。Atom とフックの選択、パフォーマンスパターン、非同期状態、実装上の注意点を扱います。

主な機能

  1. 01Atom の種類とフックの選択
  2. 02細粒度の購読
  3. 03リストやオブジェクト状態の効率的な管理
  4. 04loadable による非同期状態の処理
  5. 05よくあるアンチパターンの指針

活用シーン

  1. 01共有 React 状態の管理方法の選定
  2. 02コンポーネントの再レンダリング削減
  3. 03大規模なフォームやリストの管理
  4. 04非同期 Atom データの処理

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/jotai-expertを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

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

詳細

Jotai エキスパート

Jotai は、アトミックなアプローチを採用した、プリミティブで柔軟な React 用状態管理ライブラリです。最新の API 詳細については、常に https://jotai.org/ を参照してください。

判断ツリー

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

コアパターン

Atom の種類
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)
})
フックの選択
必要な操作フック変更時に再レンダリング
読み取りのみuseAtomValue(atom)はい
書き込みのみuseSetAtom(atom)いいえ
読み書き両方useAtom(atom)はい
参照の安定性
// ❌ 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])
}

パフォーマンスの最適化

1. 粒度の細かいサブスクリプション
// ❌ 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. 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. 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. 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} />
}

避けるべきアンチパターン

  1. コンポーネント内での重い計算:atom の読み取り関数またはアクションに移動します
  2. レンダー時の atom の作成:コンポーネントの外で定義するか、useMemo を使います
  3. 読み取りまたは書き込みのみの場合に useAtom を使うこと:useAtomValue/useSetAtom を使います
  4. 頻繁に更新されるデータの過度な細分化:関連する更新をまとめます
  5. 非同期 atom ごとのネストされた Suspense:適切な境界または loadable を使います

参考資料

トピック別に整理された詳細なドキュメント:

Jotai のコードを実装またはレビューする際は、詳細なガイダンスを確認するために、該当する参考ファイルを読み込んでください。

コメント