Skip to main content

Recoil 0.6

· 4 min read

Recoil 0.6 introduces improved support for React 18, including concurrent rendering and transitions, along with new APIs, fixes, and optimizations.

React 18

Recoil 0.6 uses the latest APIs from React 18 for improved safety and performance. This release is compatible with concurrent rendering and <React.StrictMode>, which is useful for testing and identifying potential issues for concurrent rendering. Making Recoil and React state changes in the same batch now stay in sync to provided a consistent view of state. Some of these improvements are also available while using previous versions of React. When experimenting with React 18 please use the latest RC build, as the original React 18.0.0-rc.0 package has a bug that has since been fixed.

Concurrent Rendering and Transitions

React 18 offers a new hook useTransition() for transitioning to a new state while having control over what to render before the new state is ready. Recoil should be compatible with this approach and provides a consistent view with React state. However, React 18 may fallback from concurrent updates and does not yet officially support initiating transitions based on state changes to external stores. This is something the React team is looking into supporting, but until then we have added experimental support for this through the following hooks. This API is considered experimental because there may be use cases we haven’t found which are not handled.

  • useRecoilValueLoadable_TRANSITION_SUPPORT_UNSTABLE()

Here's an example that displays the current results while a new result is loading:

function QueryResults() {
const queryParams = useRecoilValue_TRANSITION_SUPPORT_UNSTABLE(queryParamsAtom);
const results = useRecoilValue_TRANSITION_SUPPORT_UNSTABLE(myQuerySelector(queryParams));
return results;

function MyApp() {
const [queryParams, setQueryParams] = useRecoilState_TRANSITION_SUPPORT_UNSTABLE(queryParamsAtom);
const [inTransition, startTransition] = useTransition();
return (
{inTransition ? <div>[Loading new results...]</div> : ''}
Results: <React.Suspense><QueryResults /></React.Suspense>
onClick={() => {
startTransition(() => {
setQueryParams( params...);
Start New Query

New Features

Breaking Changes

  • Atom Effects
    • Rename option from effects_UNSTABLE to just effects, as the interface is mostly stabilizing. (#1520)
    • Atom effect initializations takes precedence over <RecoilRoot initializeState={...}>. (#1509)
  • useGetRecoilValueInfo_UNSTABLE() and Snapshot#getInfo_UNSTABLE() always report the node type. (#1547)
  • The 0.3 release introduced the need to retain Snapshots for later use, but it was mostly a warning. Now it is necessary to retain a Snapshot for asynchronous selectors to resolve. See the documentation here and here. Future releases will further enforce this as garbage collection is released.

Other Fixes and Optimizations

  • Reduce overhead of snapshot cloning
    • Only clone the current snapshot for callbacks if the callback actually uses it. (#1501)
    • Cache the cloned snapshots from callbacks unless there was a state change. (#1533)
  • Fix transitive selector refresh for some cases (#1409)
  • Fix some corner cases with async selectors and multiple stores (#1568)
  • Atom effects
    • Run atom effects when atoms are initialized from a set() during a transaction from useRecoilTransaction_UNSTABLE() (#1466, #1569)
    • Atom effects are cleaned up when initialized by a Snapshot which is released. (#1511, #1532)
    • Unsubscribe onSet() handlers in atom effects when atoms are cleaned up. (#1509)
    • Call onSet() when atoms are initialized with <RecoilRoot initializeState={...} > (#1519, #1511)
  • Avoid extra re-renders in some cases when a component uses a different atom/selector. (#825)
  • <RecoilRoot> will only call initializeState() once during the initial render. (#1372)
  • Lazily compute and memoize the results of lazy properties, such as from useGetRecoilValueInfo() or Snapshot#getInfo_UNSTABLE(). (#1548, #1549)