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.
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:
useRecoilCallback
Hook, which allows for async access to a Snapshot
useRecoilSnapshot
Hook, which allows for synchronous access to a Snapshot
useRecoilTransactionObserver_UNSTABLE
Hook, which allows for subscribing to Snapshot
s for any state change that happens to the atomRecoil 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.
getCallback()
functionA 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.
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.
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowUnderstanding and supporting pinch, text, and browser zoom significantly enhances the user experience. Let’s explore a few ways to do so.
Playwright is a popular framework for automating and testing web applications across multiple browsers in JavaScript, Python, Java, and C#. […]
Matcha, a famous green tea, is known for its stress-reducing benefits. I wouldn’t claim that this tea necessarily inspired the […]
Backdrop and background have similar meanings, as they both refer to the area behind something. The main difference is that […]