git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/state-jotai-atoms" ~/.claude/skills/intense-visions-harness-engineering-state-jotai-atoms-599969 && rm -rf "$T"
agents/skills/codex/state-jotai-atoms/SKILL.mdJotai Atoms
Build bottom-up atomic state with Jotai for granular, composable React state management
When to Use
- Managing state that is naturally composed of small independent pieces (form fields, toggles, filters)
- Needing derived/computed state that automatically updates when dependencies change (like Recoil selectors)
- Wanting React Context-like convenience without re-render performance problems
- Building components that each need a small piece of global state without subscribing to everything
Instructions
- Create primitive atoms with
. These are the smallest units of state.atom(initialValue) - Create derived (read-only) atoms with
. They auto-update when dependencies change.atom((get) => computation) - Create writable derived atoms with
for computed state with custom setters.atom(readFn, writeFn) - Use
in components — it returnsuseAtom
like[value, setValue]
.useState - Use
for read-only access anduseAtomValue
for write-only access to minimize subscriptions.useSetAtom - Group related atoms in a file per domain (
,atoms/auth.ts
).atoms/cart.ts - No Provider needed for basic usage — Jotai uses a default store. Add
only for scoped state or testing.<Provider>
// atoms/cart.ts import { atom } from 'jotai'; interface CartItem { id: string; name: string; price: number; qty: number; } // Primitive atoms export const cartItemsAtom = atom<CartItem[]>([]); export const couponCodeAtom = atom<string | null>(null); // Derived atom (read-only) — auto-updates when cartItemsAtom changes export const cartTotalAtom = atom((get) => { const items = get(cartItemsAtom); return items.reduce((sum, item) => sum + item.price * item.qty, 0); }); export const cartCountAtom = atom((get) => { return get(cartItemsAtom).reduce((sum, item) => sum + item.qty, 0); }); // Derived atom with discount applied export const discountedTotalAtom = atom((get) => { const total = get(cartTotalAtom); const coupon = get(couponCodeAtom); return coupon ? total * 0.9 : total; }); // Writable derived atom — custom setter export const addToCartAtom = atom( null, // read value (null = write-only) (get, set, newItem: Omit<CartItem, 'qty'>) => { const items = get(cartItemsAtom); const existing = items.find((i) => i.id === newItem.id); if (existing) { set( cartItemsAtom, items.map((i) => (i.id === newItem.id ? { ...i, qty: i.qty + 1 } : i)) ); } else { set(cartItemsAtom, [...items, { ...newItem, qty: 1 }]); } } );
// Component usage import { useAtomValue, useSetAtom } from 'jotai'; function CartSummary() { const total = useAtomValue(discountedTotalAtom); // Read-only, re-renders only when total changes const count = useAtomValue(cartCountAtom); return <div>{count} items - ${total.toFixed(2)}</div>; } function AddButton({ product }: { product: { id: string; name: string; price: number } }) { const addToCart = useSetAtom(addToCartAtom); // Write-only, never re-renders from this atom return <button onClick={() => addToCart(product)}>Add to Cart</button>; }
Details
Atom dependency graph: Derived atoms automatically track which atoms they read via
get(). When any dependency changes, the derived atom recomputes. This is reactive — no manual subscription management.
Async atoms: Atoms can return promises. Jotai integrates with React Suspense:
const userAtom = atom(async () => { const res = await fetch('/api/user'); return res.json(); }); // Component suspends until data loads function User() { const user = useAtomValue(userAtom); // Suspends, then returns data return <div>{user.name}</div>; }
atomWithStorage: Persist atoms to localStorage:
import { atomWithStorage } from 'jotai/utils'; const themeAtom = atomWithStorage('theme', 'light'); // Automatically reads from and writes to localStorage
Jotai vs Zustand: Jotai is bottom-up (compose small atoms into larger state). Zustand is top-down (define a store, select slices). Choose Jotai when state is naturally fragmented across many components. Choose Zustand when state is a cohesive domain object.
Jotai vs React Context: Both are Provider-based. But Context re-renders all consumers when any value changes. Jotai only re-renders consumers of the specific atom that changed.
Testing: Use
<Provider> in tests to create isolated atom scopes:
import { Provider, createStore } from 'jotai'; const store = createStore(); store.set(cartItemsAtom, mockItems); render( <Provider store={store}> <CartSummary /> </Provider> );
Source
https://jotai.org/docs/introduction
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.