You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
158 lines
5.3 KiB
158 lines
5.3 KiB
import { useReducer, useRef, useMemo, useContext, useDebugValue } from 'react'; |
|
import { useReduxContext as useDefaultReduxContext } from './useReduxContext'; |
|
import { createSubscription } from '../utils/Subscription'; |
|
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'; |
|
import { ReactReduxContext } from '../components/Context'; |
|
|
|
var refEquality = function refEquality(a, b) { |
|
return a === b; |
|
}; |
|
|
|
function useSelectorWithStoreAndSubscription(selector, equalityFn, store, contextSub) { |
|
var _useReducer = useReducer(function (s) { |
|
return s + 1; |
|
}, 0), |
|
forceRender = _useReducer[1]; |
|
|
|
var subscription = useMemo(function () { |
|
return createSubscription(store, contextSub); |
|
}, [store, contextSub]); |
|
var latestSubscriptionCallbackError = useRef(); |
|
var latestSelector = useRef(); |
|
var latestStoreState = useRef(); |
|
var latestSelectedState = useRef(); |
|
var storeState = store.getState(); |
|
var selectedState; |
|
|
|
try { |
|
if (selector !== latestSelector.current || storeState !== latestStoreState.current || latestSubscriptionCallbackError.current) { |
|
var newSelectedState = selector(storeState); // ensure latest selected state is reused so that a custom equality function can result in identical references |
|
|
|
if (latestSelectedState.current === undefined || !equalityFn(newSelectedState, latestSelectedState.current)) { |
|
selectedState = newSelectedState; |
|
} else { |
|
selectedState = latestSelectedState.current; |
|
} |
|
} 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"; |
|
} |
|
|
|
throw err; |
|
} |
|
|
|
useIsomorphicLayoutEffect(function () { |
|
latestSelector.current = selector; |
|
latestStoreState.current = storeState; |
|
latestSelectedState.current = selectedState; |
|
latestSubscriptionCallbackError.current = undefined; |
|
}); |
|
useIsomorphicLayoutEffect(function () { |
|
function checkForUpdates() { |
|
try { |
|
var newStoreState = store.getState(); // Avoid calling selector multiple times if the store's state has not changed |
|
|
|
if (newStoreState === latestStoreState.current) { |
|
return; |
|
} |
|
|
|
var _newSelectedState = latestSelector.current(newStoreState); |
|
|
|
if (equalityFn(_newSelectedState, latestSelectedState.current)) { |
|
return; |
|
} |
|
|
|
latestSelectedState.current = _newSelectedState; |
|
latestStoreState.current = newStoreState; |
|
} 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; |
|
} |
|
/** |
|
* Hook factory, which creates a `useSelector` hook bound to a given context. |
|
* |
|
* @param {React.Context} [context=ReactReduxContext] Context passed to your `<Provider>`. |
|
* @returns {Function} A `useSelector` hook bound to the specified context. |
|
*/ |
|
|
|
|
|
export function createSelectorHook(context) { |
|
if (context === void 0) { |
|
context = ReactReduxContext; |
|
} |
|
|
|
var useReduxContext = context === ReactReduxContext ? useDefaultReduxContext : function () { |
|
return useContext(context); |
|
}; |
|
return function useSelector(selector, equalityFn) { |
|
if (equalityFn === void 0) { |
|
equalityFn = refEquality; |
|
} |
|
|
|
if (process.env.NODE_ENV !== 'production') { |
|
if (!selector) { |
|
throw new Error("You must pass a selector to useSelector"); |
|
} |
|
|
|
if (typeof selector !== 'function') { |
|
throw new Error("You must pass a function as a selector to useSelector"); |
|
} |
|
|
|
if (typeof equalityFn !== 'function') { |
|
throw new Error("You must pass a function as an equality function to useSelector"); |
|
} |
|
} |
|
|
|
var _useReduxContext = useReduxContext(), |
|
store = _useReduxContext.store, |
|
contextSub = _useReduxContext.subscription; |
|
|
|
var selectedState = useSelectorWithStoreAndSubscription(selector, equalityFn, store, contextSub); |
|
useDebugValue(selectedState); |
|
return selectedState; |
|
}; |
|
} |
|
/** |
|
* 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 <div>{counter}</div> |
|
* } |
|
*/ |
|
|
|
export var useSelector = /*#__PURE__*/createSelectorHook(); |