A Mutative extension for Jotai
With the Mutative extension, you can simplify the handling of immutable data in Jotai in a mutable way, allowing you to use immutable state more conveniently.
jotai-mutative is more than 10x faster than jotai-immer. Read more about the performance comparison in Mutative.
In order to use the Mutative extension in Jotai, you will need to install Mutative and Jotai as a direct dependency.
npm install jotai mutative jotai-mutative
# Or use any package manager of your choice.| jotai-mutative | mutative | jotai |
|---|---|---|
| 2.x | 2.x | 2.x, 3.x |
| 1.x | 1.x | 2.x |
Jotai 3 needs React 18 or later, so stay on Jotai 2 with React 17. Jotai 3 has no UMD build, so the UMD build of jotai-mutative, which reads the jotaiVanilla and jotaiReact globals of the UMD builds of Jotai 2, works only with Jotai 2. See the Jotai v3 migration guide for the other changes in Jotai 3.
jotai-mutative 2.x declares mutative ^2.0.0 as its peer dependency, so upgrade both together:
npm install jotai-mutative@2 mutative@2The API is unchanged: the atoms and hooks take the same recipes and the same Mutative options. The changes that can affect an atom are:
- With
enableAutoFreeze: true, Map and Set instances in the atom value are frozen too, so adding a property to them or replacing one of their methods fails. useMutativeAtom()anduseSetMutativeAtom()without Mutative options pass the recipe to an atom fromatomWithMutative()orwithMutative(), so the Mutative options of the atom apply to it.- Mutative selects its development or production build from
process.env.NODE_ENV. Define it in every bundle, including bundles for Node.js, and in production, match errors by theirMinified Mutative error #<code>codes instead of their messages. - Mutative targets ES2018; transpile it for older engines. Without a bundler, map
mutativeto its production ESM build,mutative/dist/mutative.esm.production.min.mjs, as its default ESM build readsprocess, or load its UMD build, which defines theMutativeglobal that the UMD build ofjotai-mutativereads. - The type declarations of Mutative support TypeScript 5.0 and later.
See the migration guide for every change in Mutative v2.
atomWithMutative creates a new atom similar to the regular atom with a different writeFunction. In this bundle, we don't have read-only atoms, because the point of these functions is the mutative create(mutability) function. The signature of writeFunction is (get, set, update: (draft: Draft<Value>) => void) => void.
import { useAtom } from 'jotai';
import { atomWithMutative } from 'jotai-mutative';
const countAtom = atomWithMutative({ value: 0 });
const Counter = () => {
const [count] = useAtom(countAtom);
return <div>count: {count.value}</div>;
};
const Controls = () => {
const [, setCount] = useAtom(countAtom);
// setCount === update : (draft: Draft<Value>) => void
const inc = () =>
setCount((draft) => {
++draft.value;
});
return <button onClick={inc}>+1</button>;
};withMutative takes an atom and returns a derived atom, same as atomWithMutative it has a different writeFunction.
import { useAtom, atom } from 'jotai';
import { withMutative } from 'jotai-mutative';
const primitiveAtom = atom({ value: 0 });
const countAtom = withMutative(primitiveAtom);
const Counter = () => {
const [count] = useAtom(countAtom);
return <div>count: {count.value}</div>;
};
const Controls = () => {
const [, setCount] = useAtom(countAtom);
// setCount === update : (draft: Draft<Value>) => void
const inc = () =>
setCount((draft) => {
++draft.value;
});
return <button onClick={inc}>+1</button>;
};This hook takes an atom and replaces the atom's writeFunction with the new mutative-like writeFunction like the previous helpers.
import { useAtom } from 'jotai';
import { useMutativeAtom } from 'jotai-mutative';
const primitiveAtom = atom({ value: 0 });
const Counter = () => {
const [count] = useMutativeAtom(primitiveAtom);
return <div>count: {count.value}</div>;
};
const Controls = () => {
const [, setCount] = useMutativeAtom(primitiveAtom);
// setCount === update : (draft: Draft<Value>) => void
const inc = () =>
setCount((draft) => {
++draft.value;
});
return <button onClick={inc}>+1</button>;
};It would be better if you don't use
withMutativeandatomWithMutativewithuseMutativeAtombecause they provide the mutative-likewriteFunctionand we don't need to create a new one. If you do, the hook passes the recipe to the atom'swriteFunction, so the Mutative options of the atom apply. With Mutative options of its own, the hook creates the next value from the draft that the atom passes, which Mutative 2 reports once in development; set the options on the atom instead. You can useuseSetMutativeAtomif you need only the setter part ofuseMutativeAtom.
jotai-mutative is inspired by jotai-immer.
It uses the same API as jotai-immer but uses Mutative under the hood. The repository is based on the jotai-immer repository.
jotai-mutative is MIT licensed.