Paul Henschel recently released jōtai, a new state management library for React. Jōtai claims a minimalistic API (three functions), TypeScript-readiness, and compatibility with React Suspense and Concurrent Mode.
Pieces of state that are not dependent on any other pieces of state can be created by passing an initial value to jōtai’s atom
factory function. These independent pieces of state can then be composed into derived pieces of state with the same factory function, this time passing a function that computes the derived atom from its dependencies:
const count1 = atom(1)
const count2 = atom(2)
const count3 = atom(3)
const sum = atom(get => get(count1) + get(count2) + get(count3))
The atom
factory function can be used to define in-place reducers, i.e. functions that update a piece of state from some argument parameter:
const decrementCountAtom = atom(
get => get(countAtom),
(get, set, _arg) => set(countAtom, get(countAtom) - 1),
)
function Counter() {
const [count, decrement] = useAtom(decrementCountAtom)
return (
<h1>
{count}
<button onClick={decrement}>Decrease</button>
)
}
The atom
factory function also allows developers to define actions, that is, procedures that may execute effects — and update relevant pieces of state. Actions may be asynchronous:
const fetchCountAtom = atom(
get => get(countAtom),
async (_get, set, url) => {
const response = await fetch(url)
set(countAtom, (await response.json()).count)
}
)
function Controls() {
const [count, compute] = useAtom(fetchCountAtom)
return <button onClick={() => compute("http://count.host.com")}>compute</button>
The previous code showcases the useAtom
hook used to manipulate pieces of state handled by jōtai in a React component context. React applications must be wrapped within jōtai’s Provider
component to use the atom
and useAtom
APIs:
import { Provider } from 'jotai'
const Root = () => (
<Provider>
<App />
</Provider>
)
With the three previously mentioned APIs, jōtai claims to prevent extra re-renders, while being fully compatible with React Suspense and Concurrent Mode. Daishi Kato further explained:
Jotai state is within React component tree. […] Jotai can be seen as a replacement for useState+useContext. Instead of creating multiple contexts, atoms share one big context.
Jōtai provides a state management mechanism similar to Recoil’s, with a smaller API surface (three functions) and code bundle (1.3 KB) vs. Recoil’s full-featured API at 9KB.
Both Recoil and jōtai share pieces of state between components by creating atoms that are available in closure of all sharing components. The standard alternative is to lift the pieces of state up to a common ancestor in the component tree so those pieces of state can be accessed by the dependent components. The Recoil’s team summarized the issues from the latter approach as follows:
For reasons of compatibility and simplicity, it’s best to use React’s built-in state management capabilities rather than external global state. But React has certain limitations:
- Component state can only be shared by pushing it up to the common ancestor, but this might include a huge tree that then needs to re-render.
- Context can only store a single value, not an indefinite set of values each with its own consumers.
- Both of these make it difficult to code-split the top of the tree (where the state has to live) from the leaves of the tree (where the state is used).
Jōtai is an open-source library under the MIT license.