Skip to content

Commit

Permalink
Run atom effect when first used by setting from a transaction
Browse files Browse the repository at this point in the history
Summary:
Run atom effects when they are first used by being set from a transaction from `useRecoilTransaction_UNSTABLE()`.

Resolves facebookexperimental#1466

Differential Revision: D32777543

fbshipit-source-id: 185ec0680a9aa28b7d6eadd8a35c7c8cd13c4ab1
  • Loading branch information
drarmstr authored and facebook-github-bot committed Dec 3, 2021
1 parent 9dfd37b commit ba9c02e
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Fix Recoil with React strict mode that re-executes effects multiple times.
- Add `refresh` to Recoil callback interface (#1413)
- Fix transitive selector refresh for some cases (#1409)
- Run atom effects when set from `useRecoilTransaction_UNSTABLE()` (#1466)
- `useRecoilStoreID()` hook to get an ID for the current <RecoilRoot> store. (#1417)
- `storeID` added to atom effects interface (#1414)
- `<RecoilRoot>` will only call `initializeState()` during initial render. (#1372)
Expand Down
13 changes: 9 additions & 4 deletions packages/recoil/core/Recoil_AtomicUpdates.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@
*/
'use strict';

import type {RecoilState, RecoilValue} from '../core/Recoil_RecoilValue';
import type {NodeKey, Store, TreeState} from '../core/Recoil_State';
import type {ValueOrUpdater} from '../recoil_values/Recoil_callbackTypes';
import type {RecoilState, RecoilValue} from './Recoil_RecoilValue';
import type {NodeKey, Store, TreeState} from './Recoil_State';

const {loadableWithValue} = require('../adt/Recoil_Loadable');
const {DEFAULT_VALUE, getNode} = require('../core/Recoil_Node');
const {initializeNode} = require('./Recoil_FunctionalCore');
const {DEFAULT_VALUE, getNode} = require('./Recoil_Node');
const {
copyTreeState,
getRecoilValueAsLoadable,
invalidateDownstreams,
writeLoadableToTreeState,
} = require('../core/Recoil_RecoilValueInterface');
} = require('./Recoil_RecoilValueInterface');
const err = require('recoil-shared/util/Recoil_err');

export interface TransactionInterface {
Expand Down Expand Up @@ -80,10 +81,14 @@ class TransactionInterfaceImpl {
if (!isAtom(recoilState)) {
throw err('Setting selectors within atomicUpdate is not supported');
}

if (typeof valueOrUpdater === 'function') {
const current = this.get(recoilState);
this._changes.set(recoilState.key, (valueOrUpdater: any)(current)); // flowlint-line unclear-type:off
} else {
// Initialize atom and run effects if not initialized yet
initializeNode(this._store, recoilState.key);

this._changes.set(recoilState.key, valueOrUpdater);
}
};
Expand Down
66 changes: 66 additions & 0 deletions packages/recoil/hooks/__tests__/Recoil_useTransaction-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,72 @@ describe('Atoms', () => {
});

describe('Atom Effects', () => {
testRecoil(
'Atom effects are run when first get from a transaction',
async () => {
let numTimesEffectInit = 0;

const atomWithEffect = atom({
key: 'atom effect first get transaction',
default: 'DEFAULT',
effects_UNSTABLE: [
({trigger}) => {
expect(trigger).toEqual('get');
numTimesEffectInit++;
},
],
});

let getAtomWithTransaction;
const Component = () => {
getAtomWithTransaction = useRecoilTransaction(({get}) => () => {
expect(get(atomWithEffect)).toEqual('DEFAULT');
});
return null;
};

renderElements(<Component />);

act(() => getAtomWithTransaction());

expect(numTimesEffectInit).toBe(1);
},
);

// TODO Unable to test setting from a transaction as Jest complains about
// updates not wrapped in act(...)...
// testRecoil(
// 'Atom effects are run when first set with a transaction',
// async () => {
// let numTimesEffectInit = 0;

// const atomWithEffect = atom({
// key: 'atom effect first set transaction',
// default: 'DEFAULT',
// effects_UNSTABLE: [
// ({trigger}) => {
// expect(trigger).toEqual('set');
// numTimesEffectInit++;
// },
// ],
// });

// let setAtomWithTransaction;
// const Component = () => {
// setAtomWithTransaction = useRecoilTransaction(({set}) => () => {
// set(atomWithEffect, 'SET');
// });
// return null;
// };

// renderElements(<Component />);

// act(() => setAtomWithTransaction());

// expect(numTimesEffectInit).toBe(1);
// },
// );

testRecoil('Atom effects can initialize for a transaction', async () => {
let numTimesEffectInit = 0;
const atomWithEffect = atom({
Expand Down

0 comments on commit ba9c02e

Please sign in to comment.