Skip to content

Commit

Permalink
Fix double render on selector change (facebookexperimental#825)
Browse files Browse the repository at this point in the history
Summary:
This pr just set the current value before store subscribe/resubscribe
This prevent double rendering when using selectorFamily in child nodes, controlled by parent props.

Issue CodeSandbox:
https://codesandbox.io/s/recoil-legacy-selector-change-double-render-issue-fhbxk

Fixed CodeSandbox:
https://codesandbox.io/s/recoil-legacy-selector-change-double-render-fixed-feub8

Pull Request resolved: facebookexperimental#825

Reviewed By: bezi

Differential Revision: D25873280

Pulled By: drarmstr

fbshipit-source-id: 07b1dc3a9eb970693fe46673781f2df150f6ef25
  • Loading branch information
salvoravida authored and facebook-github-bot committed Dec 16, 2021
1 parent b249bb3 commit 889f1c9
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 7 deletions.
12 changes: 6 additions & 6 deletions packages/recoil/hooks/Recoil_Hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,6 @@ function useRecoilValueLoadable_LEGACY<T>(
): Loadable<T> {
const storeRef = useStoreRef();
const [, forceUpdate] = useState([]);

const componentName = useComponentName();

const getLoadable = useCallback(() => {
Expand All @@ -478,6 +477,12 @@ function useRecoilValueLoadable_LEGACY<T>(
return getRecoilValueAsLoadable(store, recoilValue, treeState);
}, [storeRef, recoilValue]);

const loadable = getLoadable();
const prevLoadableRef = useRef(loadable);
useEffect(() => {
prevLoadableRef.current = loadable;
});

useEffect(() => {
const store = storeRef.current;
const storeState = store.getState();
Expand Down Expand Up @@ -532,11 +537,6 @@ function useRecoilValueLoadable_LEGACY<T>(
return subscription.release;
}, [componentName, getLoadable, recoilValue, storeRef]);

const loadable = getLoadable();
const prevLoadableRef = useRef(loadable);
useEffect(() => {
prevLoadableRef.current = loadable;
});
return loadable;
}

Expand Down
56 changes: 55 additions & 1 deletion packages/recoil/hooks/__tests__/Recoil_PublicHooks-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ let React,
atom,
errorSelector,
selector,
selectorFamily,
ReadsAtom,
asyncSelector,
errorThrowingAsyncSelector,
Expand Down Expand Up @@ -59,6 +60,7 @@ const testRecoil = getRecoilTestFn(() => {
atom = require('../../recoil_values/Recoil_atom');
errorSelector = require('../../recoil_values/Recoil_errorSelector');
selector = require('../../recoil_values/Recoil_selector');
selectorFamily = require('../../recoil_values/Recoil_selectorFamily');
({
ReadsAtom,
asyncSelector,
Expand Down Expand Up @@ -706,6 +708,57 @@ testRecoil(
},
);

testRecoil(
'Components re-render only one time if selectorFamily changed',
({gks, strictMode}) => {
const BASE_CALLS = baseRenderCount(gks);
const sm = strictMode ? 2 : 1;

const atomA = counterAtom();

const selectAFakeId = selectorFamily({
key: 'selectItem',
get:
_id =>
({get}) =>
get(atomA),
});

const Component = (jest.fn(function ReadFromSelector({id}) {
return useRecoilValue(selectAFakeId(id));
// $FlowFixMe[unclear-type]
}): Function);

let increment;

const App = () => {
const [state, setState] = useRecoilState(atomA);
increment = () => setState(s => s + 1);
return <Component id={state} />;
};

const container = renderElements(<App />);

let baseCalls = BASE_CALLS;

expect(container.textContent).toEqual('0');
expect(Component).toHaveBeenCalledTimes((baseCalls + 1) * sm);

act(() => increment());

if (
(reactMode().mode === 'LEGACY' &&
!gks.includes('recoil_suppress_rerender_in_callback')) ||
reactMode().mode === 'CONCURRENT_LEGACY'
) {
baseCalls += 1;
}

expect(container.textContent).toEqual('1');
expect(Component).toHaveBeenCalledTimes((baseCalls + 2) * sm);
},
);

testRecoil('Can subscribe to and also change an atom in the same batch', () => {
const anAtom = counterAtom();

Expand Down Expand Up @@ -806,7 +859,8 @@ testRecoil(
expect(Component).toHaveBeenCalledTimes((baseCalls + 2) * sm);

if (
reactMode().mode === 'LEGACY' ||
(reactMode().mode === 'LEGACY' &&
!gks.includes('recoil_suppress_rerender_in_callback')) ||
reactMode().mode === 'CONCURRENT_LEGACY'
) {
baseCalls += 1;
Expand Down

0 comments on commit 889f1c9

Please sign in to comment.