jotai-immer

Immer-powered draft mutations for Jotai atoms, with drop-in atom creators and hooks.

Library
npm
v0.4.3
29stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
29/100Needs Attention
Development Activity28
Maintenance8
Community16
Maturity52
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
73/100Good
Architecture72
Code Quality78
Innovation82
Learning Curve58

jotai-immer is a small integration library that lets Jotai atoms be updated with Immer’s mutable draft syntax instead of hand-written immutable spread logic. It ships four building blocks — atomWithImmer, withImmer, useImmerAtom, and useSetImmerAtom — covering both atom-creation and atom-wrapping styles, plus React hooks for components that prefer to work with an existing writable atom.

Under the hood every setter runs the atom’s current value through Immer’s produce, so updates read like plain object or array mutations (draft.count += 1) while Jotai still receives an immutable next value. Setting a value to undefined resolves to Immer’s nothing sentinel, correctly resetting the atom instead of being ignored.

What You Get

  • atomWithImmer(initialValue) — a writable atom whose setter accepts either a full value or an Immer draft-mutation function
  • withImmer(anAtom) — wraps an existing writable atom so its setter gains Immer draft semantics without redefining the atom
  • useImmerAtom(anAtom) — a useAtom-style hook returning [value, setDraft] for atoms with Immer setters
  • useSetImmerAtom(anAtom) — a useSetAtom-style hook for write-only access with Immer draft mutations
  • Full TypeScript types covering both single-value and multi-arg atom setter signatures

Common Use Cases

  • Managing deeply nested form or settings state where spread-based immutable updates would be verbose
  • Migrating a Redux/Immer-based reducer pattern to Jotai without rewriting update logic into spreads
  • Building collection state (todo lists, tables, trees) where items are pushed, removed, or edited by index
  • Wrapping a derived/writable atom from another Jotai utility library so its setter also accepts draft mutations

Under The Hood

Architecture The library exposes four files re-exported via index.ts. atomWithImmer creates a self-referential writable atom whose write function calls Immer’s produce on the atom’s own current value, then writes the result back through Jotai’s low-level atom() from ‘jotai/vanilla’. withImmer takes an arbitrary existing writable atom and wraps it in a derived atom using the same produce-based logic, so the pattern also works when you don’t own the original atom’s creation; a WeakMap-based memo1 cache keyed by the wrapped atom instance ensures calling withImmer(anAtom) repeatedly returns the same derived atom rather than creating duplicates on every render. useImmerAtom and useSetImmerAtom are thin React hooks layered on jotai/react’s useAtomValue and useSetAtom — useSetImmerAtom wraps the setter in Immer’s produce(fn) curried form and memoizes it with useCallback, while useImmerAtom composes useAtomValue and useSetImmerAtom into the familiar [value, setter] tuple. Data flow is unidirectional and synchronous: draft mutation callback → produce() computes the next immutable value → set() writes it into the underlying Jotai atom → subscribers re-render.

Tech Stack Peer dependencies are immer >=9.0.0 and jotai >=2.0.0; devDependencies use React 19, TypeScript 6, Vite 8, Vitest 4, and ESLint 9’s flat config with Prettier for formatting. The build produces dual ESM/CJS output via two dedicated tsconfig files (tsconfig.esm.json, tsconfig.cjs.json) compiled with tsc, with a conditional exports map in package.json providing require/default entry points and a synthesized {“type”:“commonjs”} marker for the CJS output. The repo is a pnpm workspace with four example apps under examples/, each with its own package.json and Vite dev script.

Code Quality Tests exist under tests/ (atomWithImmer.spec.tsx, withImmer.spec.tsx, useImmerAtom.spec.tsx, useSetImmerAtom.spec.tsx) using Vitest, @testing-library/react, and happy-dom, covering both the hook-based path and direct store.set() access, including the undefined-to-nothing reset edge case. Source files are small (20-50 lines each), consistently typed with generics constrained to WritableAtom<Value, Args, Result>, and use .js-suffixed relative imports per the NodeNext ESM convention. There is no explicit error handling because failure modes are delegated to Immer and Jotai themselves; naming is consistent and deliberately mirrors Jotai’s own primitives.

API Design The public surface is deliberately minimal — four exports that mirror Jotai’s own naming conventions (atom/useAtom/useSetAtom → atomWithImmer/useImmerAtom/useSetImmerAtom), so anyone already familiar with Jotai has almost no new API to learn. Overloaded TypeScript signatures on withImmer, useImmerAtom, and useSetImmerAtom give accurate types for both the draft-callback and plain-value setter forms. Getting started requires only swapping atom() for atomWithImmer() or wrapping an existing atom — no provider, configuration, or additional boilerplate.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search