diff --git a/CHANGELOG.md b/CHANGELOG.md index a47d45d..296ecf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Change Log ## [Unreleased] +### Changed +- No useLayoutEffect for invoking listeners (which leads de-opt sync mode) ## [4.0.0] - 2019-06-22 ### Changed diff --git a/src/Provider.js b/src/Provider.js index e0b17e1..cdb0a1f 100644 --- a/src/Provider.js +++ b/src/Provider.js @@ -6,7 +6,7 @@ import { useRef, } from 'react'; -import { useIsomorphicLayoutEffect, useForceUpdate } from './utils'; +import { useForceUpdate } from './utils'; // ------------------------------------------------------- // context @@ -47,9 +47,11 @@ export const Provider = ({ const forceUpdate = useForceUpdate(); const state = store.getState(); const listeners = useRef([]); - useIsomorphicLayoutEffect(() => { - listeners.current.forEach(listener => listener(state)); - }, [state]); + // we call listeners in render intentionally. + // listeners are not technically pure, but + // otherwise we can't get benefits from concurrent mode. + // we make sure to work with double or more invocation of listeners. + listeners.current.forEach(listener => listener(state)); const subscribe = useCallback((listener) => { listeners.current.push(listener); const unsubscribe = () => { diff --git a/src/useSelector.js b/src/useSelector.js index aad53ba..81e7ce7 100644 --- a/src/useSelector.js +++ b/src/useSelector.js @@ -30,17 +30,16 @@ export const useSelector = (selector, eqlFn, opts) => { }); useEffect(() => { const callback = (nextState) => { - if (ref.current.state === nextState) return; - let changed; try { - changed = !ref.current.equalityFn(ref.current.selected, ref.current.selector(nextState)); + if (ref.current.state === nextState + || ref.current.equalityFn(ref.current.selected, ref.current.selector(nextState))) { + // not changed + return; + } } catch (e) { - changed = true; // stale props or some other reason - } - if (changed) { - ref.current.state = nextState; - forceUpdate(); + // ignored (stale props or some other reason) } + forceUpdate(); }; const unsubscribe = subscribe(callback); return unsubscribe; diff --git a/src/useTrackedSelectors.js b/src/useTrackedSelectors.js index 8875a88..b346be2 100644 --- a/src/useTrackedSelectors.js +++ b/src/useTrackedSelectors.js @@ -62,30 +62,31 @@ export const useTrackedSelectors = (selectorMap, opts = {}) => { // update ref const lastTracked = useRef(null); useIsomorphicLayoutEffect(() => { - lastTracked.current = { keys, mapped, trapped }; + lastTracked.current = { + state, + keys, + mapped, + trapped, + }; }); // subscription useEffect(() => { const callback = (nextState) => { + if (lastTracked.current.state === nextState) return; try { - let changed = false; - const nextMapped = createMap(lastTracked.current.keys, (key) => { + const changed = lastTracked.current.keys.some((key) => { + if (!lastTracked.current.trapped.usage.has(key)) return false; const lastResult = lastTracked.current.mapped[key]; - if (!lastTracked.current.trapped.usage.has(key)) return lastResult; const nextResult = runSelector(nextState, lastResult.selector); - if (nextResult.value !== lastResult.value) { - changed = true; - } - return nextResult; + return nextResult.value !== lastResult.value; }); - if (changed) { - lastTracked.current.mapped = nextMapped; - forceUpdate(); + if (!changed) { + return; } } catch (e) { - // detect erorr (probably stale props) - forceUpdate(); + // ignored (probably stale props) } + forceUpdate(); }; const unsubscribe = subscribe(callback); return unsubscribe; diff --git a/src/useTrackedState.js b/src/useTrackedState.js index 55ae2c1..675afbe 100644 --- a/src/useTrackedState.js +++ b/src/useTrackedState.js @@ -33,17 +33,18 @@ export const useTrackedState = (opts = {}) => { }); useEffect(() => { const callback = (nextState) => { - const changed = isDeepChanged( - lastTracked.current.state, - nextState, - lastTracked.current.affected, - lastTracked.current.cache, - lastTracked.current.assumeChangedIfNotAffected, - ); - if (changed) { - lastTracked.current.state = nextState; - forceUpdate(); + if (lastTracked.current.state === nextState + || !isDeepChanged( + lastTracked.current.state, + nextState, + lastTracked.current.affected, + lastTracked.current.cache, + lastTracked.current.assumeChangedIfNotAffected, + )) { + // not changed + return; } + forceUpdate(); }; const unsubscribe = subscribe(callback); return unsubscribe;