useSelector.js 5.3 KB

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