useSelector.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import { useReducer, useRef, useMemo, useContext } from 'react';
  2. import { useReduxContext as useDefaultReduxContext } from './useReduxContext';
  3. import Subscription from '../utils/Subscription';
  4. import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect';
  5. import { ReactReduxContext } from '../components/Context';
  6. var refEquality = function refEquality(a, b) {
  7. return a === b;
  8. };
  9. function useSelectorWithStoreAndSubscription(selector, equalityFn, store, contextSub) {
  10. var _useReducer = useReducer(function (s) {
  11. return s + 1;
  12. }, 0),
  13. forceRender = _useReducer[1];
  14. var subscription = useMemo(function () {
  15. return new Subscription(store, contextSub);
  16. }, [store, contextSub]);
  17. var latestSubscriptionCallbackError = useRef();
  18. var latestSelector = useRef();
  19. var latestSelectedState = useRef();
  20. var selectedState;
  21. try {
  22. if (selector !== latestSelector.current || latestSubscriptionCallbackError.current) {
  23. selectedState = selector(store.getState());
  24. } else {
  25. selectedState = latestSelectedState.current;
  26. }
  27. } catch (err) {
  28. if (latestSubscriptionCallbackError.current) {
  29. err.message += "\nThe error may be correlated with this previous error:\n" + latestSubscriptionCallbackError.current.stack + "\n\n";
  30. }
  31. throw err;
  32. }
  33. useIsomorphicLayoutEffect(function () {
  34. latestSelector.current = selector;
  35. latestSelectedState.current = selectedState;
  36. latestSubscriptionCallbackError.current = undefined;
  37. });
  38. useIsomorphicLayoutEffect(function () {
  39. function checkForUpdates() {
  40. try {
  41. var newSelectedState = latestSelector.current(store.getState());
  42. if (equalityFn(newSelectedState, latestSelectedState.current)) {
  43. return;
  44. }
  45. latestSelectedState.current = newSelectedState;
  46. } catch (err) {
  47. // we ignore all errors here, since when the component
  48. // is re-rendered, the selectors are called again, and
  49. // will throw again, if neither props nor store state
  50. // changed
  51. latestSubscriptionCallbackError.current = err;
  52. }
  53. forceRender({});
  54. }
  55. subscription.onStateChange = checkForUpdates;
  56. subscription.trySubscribe();
  57. checkForUpdates();
  58. return function () {
  59. return subscription.tryUnsubscribe();
  60. };
  61. }, [store, subscription]);
  62. return selectedState;
  63. }
  64. /**
  65. * Hook factory, which creates a `useSelector` hook bound to a given context.
  66. *
  67. * @param {React.Context} [context=ReactReduxContext] Context passed to your `<Provider>`.
  68. * @returns {Function} A `useSelector` hook bound to the specified context.
  69. */
  70. export function createSelectorHook(context) {
  71. if (context === void 0) {
  72. context = ReactReduxContext;
  73. }
  74. var useReduxContext = context === ReactReduxContext ? useDefaultReduxContext : function () {
  75. return useContext(context);
  76. };
  77. return function useSelector(selector, equalityFn) {
  78. if (equalityFn === void 0) {
  79. equalityFn = refEquality;
  80. }
  81. if (process.env.NODE_ENV !== 'production' && !selector) {
  82. throw new Error("You must pass a selector to useSelectors");
  83. }
  84. var _useReduxContext = useReduxContext(),
  85. store = _useReduxContext.store,
  86. contextSub = _useReduxContext.subscription;
  87. return useSelectorWithStoreAndSubscription(selector, equalityFn, store, contextSub);
  88. };
  89. }
  90. /**
  91. * A hook to access the redux store's state. This hook takes a selector function
  92. * as an argument. The selector is called with the store state.
  93. *
  94. * This hook takes an optional equality comparison function as the second parameter
  95. * that allows you to customize the way the selected state is compared to determine
  96. * whether the component needs to be re-rendered.
  97. *
  98. * @param {Function} selector the selector function
  99. * @param {Function=} equalityFn the function that will be used to determine equality
  100. *
  101. * @returns {any} the selected state
  102. *
  103. * @example
  104. *
  105. * import React from 'react'
  106. * import { useSelector } from 'react-redux'
  107. *
  108. * export const CounterComponent = () => {
  109. * const counter = useSelector(state => state.counter)
  110. * return <div>{counter}</div>
  111. * }
  112. */
  113. export var useSelector =
  114. /*#__PURE__*/
  115. createSelectorHook();