Skip to content
This repository has been archived by the owner on Jan 1, 2025. It is now read-only.

Commit

Permalink
Enable atoms to be initialized to a Promise
Browse files Browse the repository at this point in the history
Summary:
Minor completeness for the API to allow atoms to be initialized to `Promise` types by wrapping them, similar to atom defaults.

To properly allow Promises and such as atom values we also need the ability to set atoms to Promises.  The point of this diff is to provide completeness for future work to support async atoms

Differential Revision: D34975767

fbshipit-source-id: 4c04892e81a70143368140b49aac985f6d3a37e6
  • Loading branch information
drarmstr authored and facebook-github-bot committed Mar 18, 2022
1 parent ebda834 commit f370d73
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 14 deletions.
39 changes: 27 additions & 12 deletions packages/recoil/recoil_values/Recoil_atom.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,17 @@ export type PersistenceSettings<Stored> = $ReadOnly<{
validator: (mixed, DefaultValue) => Stored | DefaultValue,
}>;

// TODO Support Loadable<T> and WrappedValue<T>
type NewValue<T> = T | DefaultValue | Promise<T | DefaultValue>;
// TODO Support Loadable<T>
type NewValue<T> =
| T
| DefaultValue
| Promise<T | DefaultValue>
| WrappedValue<T>;
type NewValueOrUpdater<T> =
| T
| DefaultValue
| Promise<T | DefaultValue>
| WrappedValue<T>
| ((T | DefaultValue) => T | DefaultValue);

// Effect is called the first time a node is used with a <RecoilRoot>
Expand Down Expand Up @@ -280,8 +285,8 @@ function baseAtom<T>(options: BaseAtomOptions<T>): RecoilState<T> {
const effects = options.effects ?? options.effects_UNSTABLE;
if (effects != null) {
// This state is scoped by Store, since this is in the initAtom() closure
let duringInit = true;
let initValue: NewValue<T> = DEFAULT_VALUE;
let isDuringInit = true;
let isInitError: boolean = false;
let pendingSetSelf: ?{
effect: AtomEffect<T>,
Expand All @@ -292,7 +297,7 @@ function baseAtom<T>(options: BaseAtomOptions<T>): RecoilState<T> {
// Normally we can just get the current value of another atom.
// But for our own value we need to check if there is a pending
// initialized value or get the fallback default value.
if (duringInit && recoilValue.key === key) {
if (isDuringInit && recoilValue.key === key) {
// Cast T to S
const retValue: NewValue<S> = (initValue: any); // flowlint-line unclear-type:off
return retValue instanceof DefaultValue
Expand Down Expand Up @@ -323,7 +328,7 @@ function baseAtom<T>(options: BaseAtomOptions<T>): RecoilState<T> {
store.getState().nextTree ?? store.getState().currentTree,
recoilValue.key,
);
return duringInit &&
return isDuringInit &&
recoilValue.key === key &&
!(initValue instanceof DefaultValue)
? {...info, isSet: true, loadable: getLoadable(recoilValue)}
Expand All @@ -332,7 +337,7 @@ function baseAtom<T>(options: BaseAtomOptions<T>): RecoilState<T> {

const setSelf =
(effect: AtomEffect<T>) => (valueOrUpdater: NewValueOrUpdater<T>) => {
if (duringInit) {
if (isDuringInit) {
const currentLoadable = getLoadable(node);
const currentValue: T | DefaultValue =
currentLoadable.state === 'hasValue'
Expand All @@ -356,7 +361,13 @@ function baseAtom<T>(options: BaseAtomOptions<T>): RecoilState<T> {
}

if (typeof valueOrUpdater !== 'function') {
pendingSetSelf = {effect, value: valueOrUpdater};
pendingSetSelf = {
effect,
value:
valueOrUpdater instanceof WrappedValue
? valueOrUpdater.value
: valueOrUpdater,
};
}

setRecoilValue(
Expand All @@ -370,6 +381,8 @@ function baseAtom<T>(options: BaseAtomOptions<T>): RecoilState<T> {
pendingSetSelf = {effect, value: newValue};
return newValue;
}
: valueOrUpdater instanceof WrappedValue
? valueOrUpdater.value
: valueOrUpdater,
);
}
Expand Down Expand Up @@ -447,17 +460,19 @@ function baseAtom<T>(options: BaseAtomOptions<T>): RecoilState<T> {
}
}

duringInit = false;
isDuringInit = false;

// Mutate initial state in place since we know there are no other subscribers
// since we are the ones initializing on first use.
if (!(initValue instanceof DefaultValue)) {
const frozenInitValue = maybeFreezeValueOrPromise(initValue);
const initLoadable = isInitError
? loadableWithError(initValue)
: isPromise(frozenInitValue)
? loadableWithPromise(wrapPendingPromise(store, frozenInitValue))
: loadableWithValue(frozenInitValue);
: isPromise(initValue)
? loadableWithPromise(wrapPendingPromise(store, initValue))
: loadableWithValue(
initValue instanceof WrappedValue ? initValue.value : initValue,
);
maybeFreezeValueOrPromise(initLoadable.contents);
initState.atomValues.set(key, initLoadable);

// If there is a pending transaction, then also mutate the next state tree.
Expand Down
25 changes: 24 additions & 1 deletion packages/recoil/recoil_values/__tests__/Recoil_atom-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ describe('Effects', () => {
testRecoil('initialization', () => {
let inited = false;
const myAtom = atom({
key: 'atom effect',
key: 'atom effect init',
default: 'DEFAULT',
effects: [
({node, trigger, setSelf}) => {
Expand Down Expand Up @@ -421,6 +421,29 @@ describe('Effects', () => {
expect(c.textContent).toEqual('"RESOLVE"');
});

testRecoil('init to Promise', async () => {
let setLater;
const myAtom = atom({
key: 'atom effect init promise',
default: 'DEFAULT',
effects: [
({setSelf}) => {
setSelf(atom.value(Promise.resolve('PROMISE')));
setLater = str => setSelf(atom.value(Promise.resolve(str)));
},
],
});
expect(getRecoilStateLoadable(myAtom).state).toBe('hasValue');
await expect(getRecoilStateLoadable(myAtom).contents).resolves.toBe(
'PROMISE',
);
act(() => setLater('LATER'));
expect(getRecoilStateLoadable(myAtom).state).toBe('hasValue');
await expect(getRecoilStateLoadable(myAtom).contents).resolves.toBe(
'LATER',
);
});

testRecoil('order of effects', () => {
const myAtom = atom({
key: 'atom effect order',
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/__test_utils__/Recoil_TestingUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const err = require('recoil-shared/util/Recoil_err');
const ReactDOM = require('react-dom'); // @oss-only
const StrictMode = React.StrictMode; // @oss-only

const QUICK_TEST = false;
const QUICK_TEST = true;

// @fb-only: const IS_INTERNAL = true;
const IS_INTERNAL = false; // @oss-only
Expand Down

0 comments on commit f370d73

Please sign in to comment.