How this skill is triggered — by the user, by Claude, or both
Slash command
/web-state-jotai:web-state-jotaiThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> **Quick Guide:** Jotai provides atomic, bottom-up state management with automatic dependency tracking. Define atoms at module level (never inside components). Use primitive atoms for values, derived atoms for computed state, write-only atoms for actions, and async atoms with Suspense for loading. Components only re-render when their specific atoms change. The `atomFamily` utility is deprecate...
Quick Guide: Jotai provides atomic, bottom-up state management with automatic dependency tracking. Define atoms at module level (never inside components). Use primitive atoms for values, derived atoms for computed state, write-only atoms for actions, and async atoms with Suspense for loading. Components only re-render when their specific atoms change. The
atomFamilyutility is deprecated -- use thejotai-familypackage for new code.
<critical_requirements>
(You MUST define atoms OUTSIDE components -- creating atoms inside render causes broken state)
(You MUST wrap async atom consumers in Suspense boundaries -- async atoms trigger Suspense by default)
(You MUST use write atoms (action atoms) to encapsulate multi-atom updates and post-async state changes)
(You MUST use jotai-family package instead of deprecated atomFamily from jotai/utils for new code)
</critical_requirements>
Auto-detection: Jotai, jotai, atom, useAtom, useAtomValue, useSetAtom, atomWithStorage, atomFamily, splitAtom, selectAtom, derived atom, loadable, unwrap, createStore, Provider store
When to use:
Key patterns covered:
When NOT to use:
Jotai takes an atomic, bottom-up approach to state management. The core principle: "Anything that can be derived from the application state should be derived automatically."
Consider atoms like cells in a spreadsheet:
Key characteristics:
Mental Model: Instead of one large store, you have many small atoms that can be combined. This creates natural code splitting and enables precise re-render optimization without manual memoization.
The simplest atom type -- holds a single value with automatic type inference. Always define at module level.
import { atom } from "jotai";
const INITIAL_COUNT = 0;
const countAtom = atom(INITIAL_COUNT);
const userAtom = atom<User | null>(null);
export { countAtom, userAtom };
Why good: Module-level definition persists state, type inference works automatically, explicit typing only for unions/nullable
See examples/core.md Pattern 1 for complete examples with type patterns.
Compute values from other atoms -- dependencies tracked automatically, cached until dependencies change.
const subtotalAtom = atom((get) => get(priceAtom) * get(quantityAtom));
const taxAtom = atom((get) => get(subtotalAtom) * get(taxRateAtom));
const totalAtom = atom((get) => get(subtotalAtom) + get(taxAtom));
Why good: Automatic dependency tracking, cached computation, chain of derivations is composable
See examples/core.md Pattern 2 for derived atom chains and conditional derivations.
Encapsulate side effects and multi-atom updates. First argument is null (no read value).
const resetAllAtom = atom(null, (get, set) => {
set(countAtom, 0);
set(itemsAtom, []);
set(selectedAtom, null);
});
Why good: Actions are reusable, enables code splitting, multiple atoms updated atomically
See examples/core.md Pattern 3 for action atoms with arguments.
Atoms that can both read derived state and accept writes. Useful for lens-like property access on larger objects.
const nameAtom = atom(
(get) => get(userAtom).name,
(get, set, newName: string) => {
set(userAtom, { ...get(userAtom), name: newName });
},
);
Why good: Granular read/write access to object properties, keeps parent intact
See examples/core.md Pattern 4 for lens patterns and transformations.
Async atoms trigger Suspense by default. Use loadable() for manual loading states or unwrap() for fallback values.
// Triggers Suspense -- wrap consumer in <Suspense>
const userAtom = atom(async (get) => {
const id = get(userIdAtom);
const response = await fetch(`/api/users/${id}`);
return response.json() as Promise<User>;
});
// Non-Suspense alternative
const loadableUserAtom = loadable(userAtom);
// Returns { state: 'loading' } | { state: 'hasData', data } | { state: 'hasError', error }
Why good: First-class Suspense integration, loadable provides type-safe discriminated union
See examples/async.md for Suspense setup, loadable/unwrap patterns, and AbortController support.
Persist atom values to localStorage, sessionStorage, or custom storage. Use RESET symbol to restore defaults.
import { atomWithStorage, RESET } from "jotai/utils";
const STORAGE_KEY = "app-theme";
const themeAtom = atomWithStorage<Theme>(STORAGE_KEY, "light");
// setTheme(RESET) restores to "light"
Gotcha: Default behavior renders initial value first, then stored value (flicker). Set { getOnInit: true } for immediate stored value, but beware SSR hydration issues.
See examples/persistence.md Pattern 1 for storage variants and reset patterns.
Split an array atom into individual item atoms. Each item gets its own atom -- updating one item only re-renders that item's component.
import { splitAtom } from "jotai/utils";
const todosAtom = atom<Todo[]>([]);
const todoAtomsAtom = splitAtom(todosAtom, (todo) => todo.id);
// keyExtractor prevents unnecessary atom recreation on reorder
Why good: Per-item re-render isolation, dispatch for add/remove/move operations
See examples/persistence.md Pattern 3 for list rendering with dispatch.
Custom stores enable state access outside React, test isolation, and multi-store architectures.
import { createStore, Provider } from "jotai";
const store = createStore();
store.set(countAtom, 10); // Access outside React
store.sub(countAtom, () => { /* react to changes */ });
// Provider with store for isolation
<Provider store={store}><App /></Provider>
Gotcha: Each Provider creates isolated state. Atoms in different Providers do not share values unless given the same store instance.
See examples/testing.md for store, provider, and test isolation patterns.
Detailed Resources:
<red_flags>
High Priority Issues:
atomFamily from jotai/utils -- will be removed in v3, use jotai-family packageMedium Priority Issues:
selectAtom as a primary pattern -- official docs call it an "escape hatch", prefer derived atomskeyExtractor with splitAtom for items with IDs -- causes unnecessary atom recreationatomFamily without memory cleanup -- remove() or setShouldRemove() required to prevent leaksGotchas & Edge Cases:
loadable() returns discriminated union -- always check state property firstRESET symbol is special -- pass to setter to reset atomWithStorage to initial valueatomWithStorage default renders initial value first, then stored value (flicker)getOnInit: true avoids flicker but may cause SSR hydration mismatches</red_flags>
<critical_reminders>
(You MUST define atoms OUTSIDE components -- creating atoms inside render causes broken state)
(You MUST wrap async atom consumers in Suspense boundaries -- async atoms trigger Suspense by default)
(You MUST use write atoms (action atoms) to encapsulate multi-atom updates and post-async state changes)
(You MUST use jotai-family package instead of deprecated atomFamily from jotai/utils for new code)
Failure to follow these rules will cause state corruption, Suspense errors, and deprecated API usage.
</critical_reminders>
npx claudepluginhub agents-inc/skills --plugin web-state-jotaiManages atomic state with Jotai for granular, composable React state management. Covers primitive atoms, derived atoms, and writable derived atoms with useAtom, useAtomValue, and useSetAtom.
Guides React state management with useState, useReducer, Context, Zustand, Jotai, TanStack Query, SWR. Covers store setup, optimization, server caching, optimistic updates, normalization.
Implements React state management patterns with Redux Toolkit, Zustand, Jotai, React Query for global state, server state, optimistic updates, and library selection.