Recoil 0.0.11

Today we are releasing Recoil 0.0.11. It contains bug fixes, new features, better performance, and experimental compatibility with Concurrent Mode. Thank you so much to everyone who contributed to this release!

Experimental Concurrent Mode Support

Recoil now supports Concurrent Mode when used with the experimental release of React. Please try this out and report any problems!

Concurrent Mode, which enables smoother, more consistent user experiences, is the future of React. But up until now it's been incompatible with any kind of external state. This is because, in Concurrent Mode, renders can be spread out over time: React can pause the rendering of components, then later pick up where it left off, starting from the partial component tree that it already built:

React pauses while rendering a tree of components. Some components are rendered before the pause while others are rendered after the pause.

This presents a problem with external state. Since React now relinquishes control flow, anything could happen between the time when rendering starts and when it is completed. If the external state changes during this time, it will result in an inconsistent UI, as the earlier-rendered components will observe the older state, while the later-rendered components will observe the new state:

If components depend on some external state, and that state changes while rendering is paused, then components rendered before the pause will observe the old state, while components rendered after the pause will observe the new state.

This inconsistency could cause problems up to crashing the app.

Recoil now takes advantage of experimental APIs in React to handle this situation by restarting with a fresh tree when Recoil state has changed during a render. These APIs also make Recoil more efficient and remove the need to re-render components when they're first mounted.

Avoid using React experimental releases in production. And, no matter what release of React or state management library you use, avoid bugs by always keeping side-effects in a useEffect() hook, not during render! (@davidmccabe, @bvaughn)

Performance

Several changes improve Recoil's performance. Previously, Recoil sometimes had to re-render components in certain circumstances: when reading from selectors that had unknown dependencies and when reading from atoms that had been initialized from external storage. Now, Recoil never has to perform a second render in response to reading an atom or selector. (@davidmccabe)

Secondly, when used with the experimental release of React, Recoil no longer has to perform a second render when components are initially mounted. Recoil also no longer has to do any work to look up atom or selector values when it re-renders for reasons other than that atom or selector changing. (@davidmccabe, @bvaughn).

The basic hooks such as useRecoilValue() and useRecoilState() have been optimized and are now about 8× faster. They now typically take less than 2× as long as useState() to execute. This improves the performance of apps that use a large number of atoms in many components. (@davidmccabe)

Recoil recursively freezes the contents of atoms and selectors. This prevents bugs, but can be slow with large objects. It now happens only in the development build. (#361@drarmstr)

Recoil now avoids re-rendering components or re-evaluating selectors if you set an atom to its already-set value or reset it when it is already reset (#399, #386@drarmstr).

Finally, this release also fixes a memory leak introduced in the previous release. If you've been experiencing poor performance in apps that frequently update atoms, this was likely the cause. (#471@davidmccabe)

Types and Packaging

Flow types are now exported with the package in addition to TypeScript. Flow is the type system used at Facebook and that Recoil is actually written in. (#338, #468, #541@Brianzchen, @Komalov, @mondaychen)

TypeScript typing is also improved (#492, #545, #548, #568, #575@csantos42, @SergeyVolynkin, @drarmstr, @hachibeeDI).

In addition to NPM packages, we now provide Common JS and UMD modules via CDN (#413@mondaychen, @pocket7878).

Support for Multiple React Roots

You can now share state between multiple React roots. For example, if your app uses both React DOM and another renderer such as ThreeJS, you can now share Recoil state between them. As always when using multiple React roots, they may be momentarily out of sync. (#298, #516@drarmstr, @inlet)

Developer Tool APIs

This release includes experimental APIs intended for developer tools. We are creating a set of developer tools internally and there are also multiple open-source projects underway. We are releasing these APIs to help validate their design. (@drarmstr)

Other New APIs

You can now use a Promise as the default value of an atom. When read it will behave like an async selector. (@drarmstr)

Bug Fixes

This update has many fixes related to test infrastructure and differences between the open-source and Facebook-internal environments. (#368, #360, #362, #363, #392, #431, #402, #538, #539, #549, #561, #576@aaronabramov, @Komalov, @drarmstr, @jacques-blom, @mondaychen, @dsainati1, @csantos42, @behnammodi, @habond, @benhalverson).

It also fixes bugs when using multiple <RecoilRoot>s or pre-loading selectors in a snapshot (#534@davemccabe).

Breaking Changes

This update may break certain tests that don't use the act() function from react-test-utils to perform actions affecting React components. These tests sometimes worked anyway due to Recoil's extra renders. Use act() to fix any such tests.

Recoil will now throw an exception if a state updater function provided to Recoil causes another atom update within its own execution. State updater functions are supposed to be pure, so this has always been against the API contract. But it happened to work in some cases before, and now it doesn't. Code that does this can be changed to perform the effects with useRecoilCallback().

Future Work

In a future release, Recoil will automatically free the memory used by atoms and selectors that are no longer used, and will perform better with larger numbers of atoms. (@davidmccabe)

We are also working on APIs for synchronizing Recoil atoms with external data sources such as the URL, local storage, or a server. (@drarmstr)

Developer tools are in development. (@maxijb, @habond, @drarmstr)

Thanks for reading this far and for using Recoil! More releases are coming soon.

Recoil 0.0.10

Recoil 0.0.9 and 0.0.10 is being released with some bug fixes, TypeScript support, and a new API for Recoil Snapshots to observe, inspect, and manage global Recoil atom state. Thanks again to everyone who helped make this possible and stay tuned for more exciting developments coming soon!

Recoil 0.0.8 版本发布

今天我们发布了 Recoil 0.0.8 版本。它包含错误修复和新功能。非常感谢为此版本做出贡献的每个人!看到这么多人做出了贡献,真是太神奇了。

错误修复

  • Fixed a bug where atoms that stored self-referential structures would cause an infinite loop. (@n3tr in #153)
  • Fixed bugs affecting Server-Side Rendering. (@sbaudray in #53)
  • Fixed build system and repository syncing problems. Many people contributed to this, especially @mondaychen and including @claudiopro, @dustinsoftware, @jacques-blom, @jaredpalmer, @kentcdodds, @leushkin, and @tony-go. It remains to get Jest and Flow to behave the same between internal and OSS.

新功能

对 TypeScript 的支持

TypeScript definitions are now available via the DefinitelyTyped repository.

atomFamily and selectorFamily

These utilities help you create collections of related atoms or selectors, one for each value of some parameter. Instaed of manually creating a memoized function that returns an atom or selector, you can use atomFamily and selectorFamily. In the future, these utilities will also help with memory management.

The atomFamily function returns a function from some parameter to an atom, creating a new atom for each value of the parameter that is passed in. For example, suppose you wanted to store a set of coordinates {x: number, y: number} for every member of a collection identified by some ID. Then you could write:

const coordinatesForID = atomFamily<{x: number, y: number}, ID>({
key: 'coordinatesForID',
default: {x: 0, y: 0},
});

and then access that state as follows:

function MyComponent({id}) {
const [coordinates, setCoordinates] = useRecoilState(
coordinatesForID(id)
);
...
}

Each ID passed to coordinatesForID will get its own independent atom containing the coordinates. Each of these atoms has its own subscriptions, so a component that uses the state for a single ID will only be subscribed to changes to that one ID.

Similarly, selectorFamily lets you create a different selector for each value of some parameter. For example, suppose you wanted to take each of those coordinates and rotate them by 180 degrees:

const rotatedCoordinatesForID = selectorFamily<{x: number, y: number}, ID>({
key: 'rotatedCoordinatesForID',
get: id => ({get}) => {
const coordinates = get(coordinatesForID(id));
return {
x: -coordinates.x,
y: -coordinates.y,
};
}
});

Note that the get property of a selector family is a function that takes the parameter (in this case ID) and returns a function with a signature identical to the get property of a single selector. In general, this is how all of the options for atom families and selector families work.

并发助手

We've introduced selector families for controlling concurrency in async selectors:

  • waitForAll: requests all dependencies in parallel and waits for all of them to become available.
  • waitForAny: requests all dependencies in parallel and waits for any one of them to become available.
  • waitForNone: requests all dependencies in parallel but doesn't wait for any of them.
  • noWait requests a single dependency but doesn't wait for it to become available.

These can be used to retireve multiple dependencies in parallel and to write logic conditional on the status of an upstream dependency. For example, you can write a selector that conditionally provides a default value while an async process is in flight instead of propagating that loading state down to components.

constSelector and errorSelector

These selector families simply return a constant value or always throw a given error, respectively.

readOnlySelector

This simply wraps a read-write RecoilState in a read-only interface.

接下来的规划

我们正在努力改进观察(observation)和持久化(persistence)方面的 API,提高速度和内存管理以及对并发模式的支持。非常感谢您尝试使用 Recoil,我们希望您能继续用下去,一起期待未来的变化!