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.
useRecoilState_TRANSITION_SUPPORT_UNSTABLE()
useRecoilValue_TRANSITION_SUPPORT_UNSTABLE()
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 (
<div>
{inTransition ? <div>[Loading new results...]</div> : ''}
Results: <React.Suspense><QueryResults /></React.Suspense>
<button
onClick={() => {
startTransition(() => {
setQueryParams(...new params...);
});
}
>
Start New Query
</button>
</div>
);
}
New Features​
- Recoil Callbacks
useRecoilCallback()
can now also refresh selector caches, similar touseRecoilRefresher_UNSTABLE()
. (#1413)- Callbacks from selectors using
getCallback()
can now mutate, refresh, and transact state in addition to reading it, similar touseRecoilCallback()
. (#1498)
- Store IDs - A
StoreID
can now be obtained usinguseRecoilStoreID()
(#1417) or thestoreID
parameter in atom effects (#1414). RecoilLoadable.of()
andRecoilLoadable.all()
factories now accept either literal values, async Promises, or Loadables. This is comparable toPromise.resolve()
andPromise.all()
(#1455, #1442).- Add
.isRetained()
method for Snapshots and check if snapshot is already released when using.retain()
in development (#1546)
Breaking Changes​
- Atom Effects
- Rename option from
effects_UNSTABLE
to justeffects
, as the interface is mostly stabilizing. (#1520) - Atom effect initializations takes precedence over
<RecoilRoot initializeState={...}>
. (#1509)
- Rename option from
useGetRecoilValueInfo_UNSTABLE()
andSnapshot#getInfo_UNSTABLE()
always report the nodetype
. (#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
- 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 fromuseRecoilTransaction_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)
- Run atom effects when atoms are initialized from a
- Avoid extra re-renders in some cases when a component uses a different atom/selector. (#825)
<RecoilRoot>
will only callinitializeState()
once during the initial render. (#1372)- Lazily compute and memoize the results of lazy properties, such as from
useGetRecoilValueInfo()
orSnapshot#getInfo_UNSTABLE()
. (#1548, #1549)