diff --git a/src/hooks/useSelector.js b/src/hooks/useSelector.js index bfb2d70fe..4fd7af2f2 100644 --- a/src/hooks/useSelector.js +++ b/src/hooks/useSelector.js @@ -1,7 +1,6 @@ -import { useReducer, useRef, useMemo, useContext } from 'react' +import { useReducer, useEffect, useMemo, useContext } from 'react' import { useReduxContext as useDefaultReduxContext } from './useReduxContext' import Subscription from '../utils/Subscription' -import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect' import { ReactReduxContext } from '../components/Context' const refEquality = (a, b) => a === b @@ -12,63 +11,44 @@ function useSelectorWithStoreAndSubscription( store, contextSub ) { - const [, forceRender] = useReducer(s => s + 1, 0) + const [state, dispatch] = useReducer( + (prevState, storeState) => { + const nextSelectedState = selector(storeState) + if (equalityFn(nextSelectedState, prevState.selectedState)) { + // bail out + return prevState + } + return { + selector, + storeState, + selectedState: nextSelectedState + } + }, + store.getState(), + storeState => ({ + selector, + storeState, + selectedState: selector(storeState) + }) + ) const subscription = useMemo(() => new Subscription(store, contextSub), [ store, contextSub ]) - const latestSubscriptionCallbackError = useRef() - const latestSelector = useRef() - const latestSelectedState = useRef() - - let selectedState - - try { - if ( - selector !== latestSelector.current || - latestSubscriptionCallbackError.current - ) { - selectedState = selector(store.getState()) - } else { - selectedState = latestSelectedState.current - } - } catch (err) { - if (latestSubscriptionCallbackError.current) { - err.message += `\nThe error may be correlated with this previous error:\n${latestSubscriptionCallbackError.current.stack}\n\n` + let selectedState = state.selectedState + if (state.selector !== selector) { + const nextSelectedState = selector(state.storeState) + if (!equalityFn(nextSelectedState, state.selectedState)) { + selectedState = nextSelectedState + // schedule another update + dispatch(store.getState()) } - - throw err } - useIsomorphicLayoutEffect(() => { - latestSelector.current = selector - latestSelectedState.current = selectedState - latestSubscriptionCallbackError.current = undefined - }) - - useIsomorphicLayoutEffect(() => { - function checkForUpdates() { - try { - const newSelectedState = latestSelector.current(store.getState()) - - if (equalityFn(newSelectedState, latestSelectedState.current)) { - return - } - - latestSelectedState.current = newSelectedState - } catch (err) { - // we ignore all errors here, since when the component - // is re-rendered, the selectors are called again, and - // will throw again, if neither props nor store state - // changed - latestSubscriptionCallbackError.current = err - } - - forceRender({}) - } - + useEffect(() => { + const checkForUpdates = () => dispatch(store.getState()) subscription.onStateChange = checkForUpdates subscription.trySubscribe()