Recoil 0.7 offers some minor API improvements, selector optimizations, and other fixes.
New Features​
Atom Defaults are Optional​
It is now optional to provide a default value for an atom. If no default is provided the atom will be kept in a "pending" state (e.g. triggering React Suspense) until it is set. (#1639)
This can help avoid awkward typing (such as unecessarily making the type nullable) or awkward placeholder values for the default. An example minimal string atom might be:
atom<string>({key: 'MyString'});
This is also useful when atoms are initialized with atom effects and a default may not be necessary:
atom({
key: 'MyQuery',
effects: [
dbSyncEffect({query, variables}),
],
});
Other New Features​
- Add
.getStoreID()
method toSnapshot
(#1612) - Publish
RecoilLoadable.loading()
factory for making an asyncLoadable
which never resolves. (#1641)
Improvements / Optimizations​
Automatically retain snapshots for the duration of async callbacks.​
You now no longer need to manually retain snapshots for async callbacks from useRecoilCallback()
. (#1632)
const myCallback = useRecoilCallback(({snapshot}) => async () => {
// No longer necessary to retain() here
await something;
... use snapshot ...
});
If you want to save a Snapshot or reference it from closure state from some other scheduled handler, then you still need to manually retain it.
Other Improvements / Optimizations​
- Optimizations for scaling with more selector dependencies. 2x improvement with 100 dependencies, 4x with 1,000, and now able to support 10,000+ dependencies. (#1651, #1515, #914)
- Better error reporting when user selector implementations provide inconsistent results (#1696)
Breaking Changes​
Selector evaluation or atom defaults can use a Loadable object​
Now the selector get()
evaluation callback or atom default
property can use a Loadable
object. (#1640) This can allow them to more cleanly accept synchronous error states:
atom({
key: 'Key',
default: RecoilLoadable.error(new Error('ERROR')),
});
or mapped Loadables or placeholders:
selector({
key: 'Key',
get: ({get}) => {
const queryLoadable = get(noWait(myQuerySelector));
if (queryLoadable.state === 'loading') {
return PLACEHOLDER;
}
return queryLoadable; // Pass on the query results or error state.
}
})
If you wish to explicitly evaluate a selector value to a Promise
, Loadable
, or RecoilState
type object then you can now wrap them with selector.value()
or atom.value()
.
selector({
key: 'Key',
get: ({get}) => {
// Returns an immediate synchronous value
return selector.value(Promise.resolve('Promise as a value'));
},
});
This is only a minor change. It helps make the API more consisent for handling wrappers such as Promise
and Loadable
, though is not yet available across the entire API. But, it helps set the stage for future potential ability to explicitly set atoms and selectors to asynchronous values or error states.
Other Breaking Changes​
useRecoilCallback()
now provides a snapshot for the latest state instead of the latest rendered state, which had bugs (#1610, #1604)