It’s been a while since Facebook introduced Recoil to the world as its new standard management library for React apps. Although the library is still under the experimental flag for open-source projects, Recoil’s promising features have proven its worth release after release.
Recently, Facebook released two new versions, Recoil 0.2 and Recoil 0.3, in quick succession. The most recent updates introduced a bunch of bug fixes and improvements to the overall development experience. In this guide, we’ll highlight some of the most important new features.
For a refresher on how Recoil works, check out our intro to Recoil and our guide to creating a CRUD app.
Recoil’s Snapshot
If you’ve used Recoil before, you’re familiar with the concept of atoms. An atom is basically a piece of state in Recoil. It’s represented by an atom()
function that returns a RecoilState
object to which you can write data.
Because we’re primarily using React Hooks for everything, Recoil enables you to work with atoms via a couple of Hooks, including useRecoilState()
.
A Snapshot
object, as its name suggests, is an immutable snapshot of the state within these atoms. You aren’t likely to use a snapshot in your daily life as a programmer because the state is constantly changing. However, it’s very useful for things like state synchronization, dev tools features, etc.
There are basically three main ways to get snapshots for a given state in Recoil:
- Via the
useRecoilCallback
Hook, which allows for async access to aSnapshot
- Via the
useRecoilSnapshot
Hook, which allows for synchronous access to aSnapshot
- Via the
useRecoilTransactionObserver_UNSTABLE
Hook, which allows for subscribing toSnapshot
s for any state change that happens to the atom
Recoil 0.3 introduces a slight breaking change for snapshots. Now, a Snapshot
object may only live until the callback or the rendering process finishes. If your current code uses snapshots for longer than that, you’ll see some warning messages appear to your console while in developer mode. However, the team is working on a new API called retain()
to enable its usage for longer periods. Watch out for this feature in upcoming releases.
RecoilRoot
‘s override
RecoilRoot
must always be the ancestor for any component that uses Recoil Hooks. It’s usually added to the root component of a React app. However, that’s up to you because your application can also share multiple RecoilRoot
s representing independent stores for atom state.
Because of that peculiarity, Recoil guarantees that each atom will always have its own values for each root it belongs to. For example:
const container = renderElements( <RecoilRoot> <ReadsAtom atom={myAtom} /> <RecoilRoot> <ReadsAtom atom={myAtom} /> </RecoilRoot> </RecoilRoot> );
In this scenario, the behavior remains the same for nested roots in a way that the inner root masks the outer ones. To prevent this, Recoil 0.3 introduced a new property for RecoilRoot
called override
.
If an inner RecoilRoot
is specified with the override
property (default true
) set to false
, no function is performed for its ancestor:
const container = renderElements( <RecoilRoot> <ReadsAtom atom={myAtom} /> <RecoilRoot override={false}> <ReadsAtom atom={myAtom} /> </RecoilRoot> </RecoilRoot> );
The same behavior is expected now when you, for example, unmount a nested root set with override
to false.
That action won’t clean up the ancestor root atoms.
New selector’s getCallback()
function
A Recoil selector is a side effect-free “pure function” that returns the same value for a set of dependency values. Recoil uses them to determine when state changes happen and to notify the component that subscribed to that specific selector so it can rerender properly.
Selectors can be read-only or writable depending on the functions provided. If you only provide a get
, the selector returns a RecoilValueReadOnly
object. If there’s a set
, it returns a RecoilState
object.
Here’s a simple example of how to use selectors with Recoil:
const doubleSelector = selector({ key: 'DoubleSelector', get: ({get}) => get(myAtom) * 2, });
The logic works like this: if any of the selector dependencies get updated, the selector re-evaluates its get
method. In our example, if myAtom
state updates, the get
method updates as well because we now have a new value for the double equation. Simple, isn’t it?
Sometimes, however, you may want to use the same selectors to return objects with callbacks within them. Let’s say you have a selector that returns a component every time the ID of an item within a list updates. The selector detects the change and triggers the get
method to recalculate the modal that shows item-related information.
You can easily achieve this with the new getCallback()
function included in Recoil 0.3:
const heySelector = selector({ key: 'HeySelector', get: itemID => ({get, getCallback}) => { const onClick = getCallback(({snapshot}) => async () => { const item = await snapshot.getPromise(queryForSomethingById(itemID)); showModal(item); }); return { title: `Hey, I'm a component!`, onClick, }; }, });
It’s very similar to what we have with useRecoilCallback()
. However, the getCallback()
function is more suitable for use cases in which you need to access the state later, such as when some logic outside the context of the current component needs that object to perform a given operation.
Improved performance using HAMT
Recoil 0.2 introduced an enhancement that significantly improves the speed at which atom values are cloned.
Today, recoil uses built-in map data structures to copy and set maps of atom values when a write happens. The change introduces the use of the hash array mapped trie (HAMT) implementation, which deals with associative arrays to combine the capabilities of hash tables and array mapped trie (search trees).
The change increased the speed for such writing operations by 325 times for executions up to a thousand entries. Executions with 10,000 entries are an incredible 3,000 times faster.
You can take a look at the new implementation on GitHub.
Conclusion
Recoil versions 0.2 and 0.3 introduced mostly basic and nonbreaking changes. Among them are improvements in scalability for time and memory consumption of atom families, more friendly error throws on various use cases, better support for Safari, and more.
You can probably expect a larger list of substantial changes when the next major release comes out and the Recoil project migrates from experimental to an official state.
Cut through the noise of traditional React error reporting with LogRocket
LogRocket is a React analytics solution that shields you from the hundreds of false-positive errors alerts to just a few truly important items. LogRocket tells you the most impactful bugs and UX issues actually impacting users in your React applications.

Focus on the React bugs that matter — try LogRocket today.