import { useReducer, useRef, useEffect, useMemo, useLayoutEffect } from 'react'; import invariant from 'invariant'; import { useReduxContext } from './useReduxContext'; import Subscription from '../utils/Subscription'; // React currently throws a warning when using useLayoutEffect on the server. // To get around it, we can conditionally useEffect on the server (no-op) and // useLayoutEffect in the browser. We need useLayoutEffect to ensure the store // subscription callback always has the selector from the latest render commit // available, otherwise a store update may happen between render and the effect, // which may cause missed updates; we also must ensure the store subscription // is created synchronously, otherwise a store update may occur before the // subscription is created and an inconsistent state may be observed var useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; var refEquality = function refEquality(a, b) { return a === b; }; /** * A hook to access the redux store's state. This hook takes a selector function * as an argument. The selector is called with the store state. * * This hook takes an optional equality comparison function as the second parameter * that allows you to customize the way the selected state is compared to determine * whether the component needs to be re-rendered. * * @param {Function} selector the selector function * @param {Function=} equalityFn the function that will be used to determine equality * * @returns {any} the selected state * * @example * * import React from 'react' * import { useSelector } from 'react-redux' * * export const CounterComponent = () => { * const counter = useSelector(state => state.counter) * return
{counter}
* } */ export function useSelector(selector, equalityFn) { if (equalityFn === void 0) { equalityFn = refEquality; } invariant(selector, "You must pass a selector to useSelectors"); var _useReduxContext = useReduxContext(), store = _useReduxContext.store, contextSub = _useReduxContext.subscription; var _useReducer = useReducer(function (s) { return s + 1; }, 0), forceRender = _useReducer[1]; var subscription = useMemo(function () { return new Subscription(store, contextSub); }, [store, contextSub]); var latestSubscriptionCallbackError = useRef(); var latestSelector = useRef(); var latestSelectedState = useRef(); var selectedState; try { if (selector !== latestSelector.current || latestSubscriptionCallbackError.current) { selectedState = selector(store.getState()); } else { selectedState = latestSelectedState.current; } } catch (err) { var errorMessage = "An error occured while selecting the store state: " + err.message + "."; if (latestSubscriptionCallbackError.current) { errorMessage += "\nThe error may be correlated with this previous error:\n" + latestSubscriptionCallbackError.current.stack + "\n\nOriginal stack trace:"; } throw new Error(errorMessage); } useIsomorphicLayoutEffect(function () { latestSelector.current = selector; latestSelectedState.current = selectedState; latestSubscriptionCallbackError.current = undefined; }); useIsomorphicLayoutEffect(function () { function checkForUpdates() { try { var 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({}); } subscription.onStateChange = checkForUpdates; subscription.trySubscribe(); checkForUpdates(); return function () { return subscription.tryUnsubscribe(); }; }, [store, subscription]); return selectedState; }