Julio Sampaio Software developer, with an affinity and curiosity for everything else. 🤔💡

What’s new in Recoil 0.3?

3 min read 1043

What's New in Recoil 0.3?

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 a Snapshot
  • Via the useRecoilSnapshot Hook, which allows for synchronous access to a Snapshot
  • Via the useRecoilTransactionObserver_UNSTABLE Hook, which allows for subscribing to Snapshots 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 RecoilRoots 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:

We made a custom demo for .
No really. Click here to check it out.

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.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Julio Sampaio Software developer, with an affinity and curiosity for everything else. 🤔💡

Leave a Reply