useSelector.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import { useCallback, useDebugValue, useRef } from 'react';
  2. import { createReduxContextHook, useReduxContext as useDefaultReduxContext } from './useReduxContext';
  3. import { ReactReduxContext } from '../components/Context';
  4. import { notInitialized } from '../utils/useSyncExternalStore';
  5. let useSyncExternalStoreWithSelector = notInitialized;
  6. export const initializeUseSelector = fn => {
  7. useSyncExternalStoreWithSelector = fn;
  8. };
  9. const refEquality = (a, b) => a === b;
  10. /**
  11. * Hook factory, which creates a `useSelector` hook bound to a given context.
  12. *
  13. * @param {React.Context} [context=ReactReduxContext] Context passed to your `<Provider>`.
  14. * @returns {Function} A `useSelector` hook bound to the specified context.
  15. */
  16. export function createSelectorHook(context = ReactReduxContext) {
  17. const useReduxContext = context === ReactReduxContext ? useDefaultReduxContext : createReduxContextHook(context);
  18. return function useSelector(selector, equalityFnOrOptions = {}) {
  19. const {
  20. equalityFn = refEquality,
  21. stabilityCheck = undefined,
  22. noopCheck = undefined
  23. } = typeof equalityFnOrOptions === 'function' ? {
  24. equalityFn: equalityFnOrOptions
  25. } : equalityFnOrOptions;
  26. if (process.env.NODE_ENV !== 'production') {
  27. if (!selector) {
  28. throw new Error(`You must pass a selector to useSelector`);
  29. }
  30. if (typeof selector !== 'function') {
  31. throw new Error(`You must pass a function as a selector to useSelector`);
  32. }
  33. if (typeof equalityFn !== 'function') {
  34. throw new Error(`You must pass a function as an equality function to useSelector`);
  35. }
  36. }
  37. const {
  38. store,
  39. subscription,
  40. getServerState,
  41. stabilityCheck: globalStabilityCheck,
  42. noopCheck: globalNoopCheck
  43. } = useReduxContext();
  44. const firstRun = useRef(true);
  45. const wrappedSelector = useCallback({
  46. [selector.name](state) {
  47. const selected = selector(state);
  48. if (process.env.NODE_ENV !== 'production') {
  49. const finalStabilityCheck = typeof stabilityCheck === 'undefined' ? globalStabilityCheck : stabilityCheck;
  50. if (finalStabilityCheck === 'always' || finalStabilityCheck === 'once' && firstRun.current) {
  51. const toCompare = selector(state);
  52. if (!equalityFn(selected, toCompare)) {
  53. let stack = undefined;
  54. try {
  55. throw new Error();
  56. } catch (e) {
  57. ;
  58. ({
  59. stack
  60. } = e);
  61. }
  62. console.warn('Selector ' + (selector.name || 'unknown') + ' returned a different result when called with the same parameters. This can lead to unnecessary rerenders.' + '\nSelectors that return a new reference (such as an object or an array) should be memoized: https://redux.js.org/usage/deriving-data-selectors#optimizing-selectors-with-memoization', {
  63. state,
  64. selected,
  65. selected2: toCompare,
  66. stack
  67. });
  68. }
  69. }
  70. const finalNoopCheck = typeof noopCheck === 'undefined' ? globalNoopCheck : noopCheck;
  71. if (finalNoopCheck === 'always' || finalNoopCheck === 'once' && firstRun.current) {
  72. // @ts-ignore
  73. if (selected === state) {
  74. let stack = undefined;
  75. try {
  76. throw new Error();
  77. } catch (e) {
  78. ;
  79. ({
  80. stack
  81. } = e);
  82. }
  83. console.warn('Selector ' + (selector.name || 'unknown') + ' returned the root state when called. This can lead to unnecessary rerenders.' + '\nSelectors that return the entire state are almost certainly a mistake, as they will cause a rerender whenever *anything* in state changes.', {
  84. stack
  85. });
  86. }
  87. }
  88. if (firstRun.current) firstRun.current = false;
  89. }
  90. return selected;
  91. }
  92. }[selector.name], [selector, globalStabilityCheck, stabilityCheck]);
  93. const selectedState = useSyncExternalStoreWithSelector(subscription.addNestedSub, store.getState, getServerState || store.getState, wrappedSelector, equalityFn);
  94. useDebugValue(selectedState);
  95. return selectedState;
  96. };
  97. }
  98. /**
  99. * A hook to access the redux store's state. This hook takes a selector function
  100. * as an argument. The selector is called with the store state.
  101. *
  102. * This hook takes an optional equality comparison function as the second parameter
  103. * that allows you to customize the way the selected state is compared to determine
  104. * whether the component needs to be re-rendered.
  105. *
  106. * @param {Function} selector the selector function
  107. * @param {Function=} equalityFn the function that will be used to determine equality
  108. *
  109. * @returns {any} the selected state
  110. *
  111. * @example
  112. *
  113. * import React from 'react'
  114. * import { useSelector } from 'react-redux'
  115. *
  116. * export const CounterComponent = () => {
  117. * const counter = useSelector(state => state.counter)
  118. * return <div>{counter}</div>
  119. * }
  120. */
  121. export const useSelector = /*#__PURE__*/createSelectorHook();