How do their APIs compare?
How do their APIs compare?
About pmndrs/jotai · 2 messages · last activity 8/8/2026
Here's a comparison of the two libraries' APIs, grounded in what's shown in the repository data and the retrieved source:
pmndrs/jotai — atom-based, primitive-first
Jotai's core primitive is the atom, created with atom(). Per the README excerpt, an atom represents a single piece of state and can hold primitives, objects, or arrays:
const countAtom = atom(0)
const countryAtom = atom('Japan')
Derived (computed) atoms are created by passing a read function that receives a get accessor to compose other atoms:
const uppercaseAtom = atom((get) => get(textAtom).toUpperCase())
This pattern is shown concretely in the example app [examples/hello/src/App.tsx:L21-L32], where uppercaseAtom derives from textAtom.
Consumption in React is via the useAtom hook, styled deliberately like useState:
const [count, setCount] = useAtom(countAtom)
seen both in the README and in [examples/hello/src/App.tsx:L38-L43] (useAtom(uppercaseAtom) returning a read-only value).
Key API characteristics:
- No string keys — atoms are just JS references, unlike Recoil (explicitly called out in the README).
- Minimal core (~2kb), with additional functionality split into subpaths:
jotai/vanilla,jotai/utils,jotai/react,jotai/vanilla/utils,jotai/react/utils, visible in the import map in [.livecodes/react.json:L1-L28]. - The internal architecture (per RepoPilot's import graph) centers on
src/vanilla.ts(22 importers),src/vanilla/atom.ts(11 importers), andsrc/vanilla/store.ts(9 importers) — meaning atoms and a vanilla (framework-agnostic) store are the load-bearing abstractions that the React bindings sit on top of.
statelyai/xstate — state machine / actor model
xstate's API is structurally different — it's built around state machines/statecharts and, in the retrieved React bindings, an actor model:
createMachinedefines a machine from a config object withstates,context,on(transitions),invoke, entry/exit actions, etc. This schema is formalized in [packages/core/src/machine.schema.json:L36-L75] (compound state nodes) and [packages/core/src/machine.schema.json:L106-L145] (atomic state nodes), showing structured concepts likeinitial,transitions,invoke,entry/exitactions that have no equivalent in jotai's atom model.- React integration (
packages/xstate-react) exposes hooks likeuseActor,useActorRef,useSelector, andcreateActorContext, per the barrel file [packages/xstate-react/src/index.ts:L1-L9].useMachineis explicitly marked deprecated in favor of the actor-based hooks. useSelectorsupports a comparison function to avoid unnecessary re-renders, defaulting to reference equality [packages/xstate-react/src/useSelector.ts:L9-L12], and there's a dedicatedshallowEqualutility for deep-ish comparisons of selected values [packages/xstate-react/src/shallowEqual.ts:L10-L38].- Testing/graph utilities like
createTestModelandgetShortestPathsoperate over machine definitions to generate paths through the statechart, as seen in [packages/core/src/graph/types.test.ts:L1-L40] — this reflects xstate's emphasis on modeling behavior/transitions explicitly, which jotai has no analogue for since it models data dependencies, not state transitions.
High-level contrast
| Aspect | jotai | xstate |
|---|---|---|
| Core unit | atom (a value + optional derivation function) | machine (states, transitions, context) |
| Composition | get/set inside atom read/write functions | events (on), transitions, invoked actors |
| React binding | useAtom (useState-like) | useActor/useActorRef/useSelector (actor-model-like) |
| Equality/render control | Implicit via atom identity/value diffing | Explicit via useSelector compare fn or shallowEqual |
| Mental model | Bottom-up |
Want to ask your own question?
Open chat for pmndrs/jotaiPublic Q&A. Generated by RepoPilot from the actual source of pmndrs/jotai. AI answers can be incomplete or stale — verify before relying on them.