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!
- Fixes for Server Side Rendering, though we do not officially support it yet. (#233, #220, #284) - Thanks @fyber-LJX, @Chrischuck, and @aulneau
- Fix selectors recording dependency subscriptions in some cases (#296) - Thanks @drarmstr
- Fix updaters in
useRecoilCallback()getting current state (#260) - Thanks @drarmstr
- Fix error messages when throwing certain errors in the open-source build. (#199) - Thanks @jonthomp
- Reduce Flow errors for open-source builds (#308) - Thanks @Komalov
- Throw error with meaningful message if user doesn't use an atom or selector with most Recoil hooks (#205) - Thanks @alexandrzavalii
- Improve testing (#321, #318, #294, #262, #295) - Thanks @aaronabramov, @Komalov, @mondaychen, @drarmstr, and @tyler-mitchell
- Improve open-source build (#249, #203, #33) - Thanks to @tony-go, @acutmore, and @jaredpalmer
TypeScript support is being rolled into the Recoil GitHub repository instead of
DefinitelyTyped to help better keep it in sync with the API. (#292 & #339) - Thanks @csantos42
#312, #311, #310, #309, #260, #259, #258, #257, #256 - Thanks @drarmstr and the rest of the team
We are introducing the concept of a
Snapshot to Recoil. A
Snapshot is an immutable snapshot of the state of Recoil atoms. This is intended to standardize the API for observing, inspecting, and managing global Recoil state and derived state. It’s useful for dev tools, global state synchronization, history, and navigation.
Snapshot class exposes the following methods for getting the values of individual Recoil atoms and selectors:
Snapshots are read-only with respect to atom state. They can be used to read atom state and evaluate selector derived state. For asynchronous selectors, the
getPromise() method can be used to wait for the evaluated value so you can see what the selector value would be based on the static atom state.
There are cases where you may wish to mutate a snapshot. While snapshots are immutable, they have methods to map themselves with a set of transformations to a new immutable snapshot. The map methods take a callback that is passed a
MutableSnapshot, which is mutated throughout the callback and will ultimately become the new snapshot returned by the mapping operation.
reset() have the same signature as the callbacks provided to a writeable selector’s
Recoil has the following hooks for working with snapshots:
useRecoilSnapshot()- Synchronous access to snapshot
useRecoilCallback()- Asynchronous access to snapshot
useRecoilTransactionObserver()- Subscribe to snapshots of all state updates
useGotoRecoilSnapshot()- Update current state to match snapshot
You can use this hook to synchronously obtain a snapshot to the current state while rendering a component. While conceptually simple, this hook will subscribe any component that uses it to any Recoil state change so it always renders with a snapshot of the current state. Therefore, be careful using this hook. One example when you may want to use it is for supporting server-side rendering where you need to synchronously have the state with the first render. In the future, we may provide the ability to debounce for performance.
<LinkToNewState> component that renders an
<a> anchor with an
href based on the current state with a mutation applied. In this example
uriFromSnapshot() is some user-defined function which encodes the current state in the URI which can be restored when loading the page.
This is a simplified example. We have a helper like this for generating links in our browser history persistence library coming soon which is more extensible and optimized. For example, it will hijack the click handler to update local state without needing to go through the browser history.
useRecoilCallback() hook is similar to the React
useCallback() hook for producing a callback function. But, instead of just providing an input callback function you wrap it with a function providing a callback interface parameter that gives you access to a
reset() callbacks to update the current global state. The provided
Snapshot represents the state when the callback is called, not when the callback function was originally created.
NOTE: This is a slight breaking change in the API, but we are still on version
0.0.x of Recoil and haven’t fully started semantic versioning yet.
useRecoilCallback() also takes an optional
deps array parameter for controlling memoization. You can extend the
react-hooks/exhaustive-deps lint rule for ensuring this is properly used.
Some motivations for using
Asynchronously use Recoil state without subscribing a React component to re-render if the atom or selector is updated.
Deferring expensive lookups to an async action that you don't want to do at render-time.
Performing side-effects where you would like to also read or write to Recoil state.
Dynamically updating an atom or selector where we may not know at render-time which atom or selector we will want to update, so we can't use
Pre-fetching before rendering
Button component which will evaluate an expensive selector when clicked on.
This hook subscribes a callback to be executed every time there is a change to Recoil atom state. Multiple updates may be batched together in a single transaction. This hook is great for persisting state changes, dev tools, building history, &c. In the future, we may allow the ability to subscribe to specific conditions or provide debouncing for performance.
Debug Observer Example
This hook returns a callback which takes a
Snapshot as a parameter and will update the current
<RecoilRoot> state to match this atom state.
Time Travel Example
Example list of history of state changes with the ability to go back and restore previous state.
<RecoilRoot> component also has an
initializeState prop which can be used to initialize the atom state. This prop takes a function with a
MutableSnapshot parameter that can be used to setup the initial atom state. This can be helpful for loading persisted state when you know all atoms in advance. It can be useful for server-side rendering where the state should be setup synchronously for the first render.
Snapshots allow us to observe and synchronize the global state. But, what if we want a more granular and composable system to work with individual atoms? We’re working on the concept of Atom Effects for observing and dealing with side-effects at the atom level. This will make it easier to persist state or bi-directionally sync with mutable storage. Think of synchronizing state with the browser URI history, browser local storage, RESTful APIs, &c. Coming soon!
Snapshot API introduced here allows us to inspect the current state for individual atoms and selectors. We’ll be expanding the API to be able to inspect the set of available nodes and explore the data-flow graph structure. This will be powerful for building dev tools. Stay tuned!
And, of course, we still have exiciting support for React Concurrent Mode and improved speed, scalability, and memory management in the works.