123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438 |
- "use strict";
- var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
- exports.__esModule = true;
- exports.default = exports.initializeConnect = void 0;
- var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
- var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
- var _hoistNonReactStatics = _interopRequireDefault(require("hoist-non-react-statics"));
- var React = _interopRequireWildcard(require("react"));
- var _reactIs = require("react-is");
- var _selectorFactory = _interopRequireDefault(require("../connect/selectorFactory"));
- var _mapDispatchToProps = require("../connect/mapDispatchToProps");
- var _mapStateToProps = require("../connect/mapStateToProps");
- var _mergeProps = require("../connect/mergeProps");
- var _Subscription = require("../utils/Subscription");
- var _useIsomorphicLayoutEffect = require("../utils/useIsomorphicLayoutEffect");
- var _shallowEqual = _interopRequireDefault(require("../utils/shallowEqual"));
- var _warning = _interopRequireDefault(require("../utils/warning"));
- var _Context = require("./Context");
- var _useSyncExternalStore = require("../utils/useSyncExternalStore");
- const _excluded = ["reactReduxForwardedRef"];
- function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
- function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
- let useSyncExternalStore = _useSyncExternalStore.notInitialized;
- const initializeConnect = fn => {
- useSyncExternalStore = fn;
- }; // Define some constant arrays just to avoid re-creating these
- exports.initializeConnect = initializeConnect;
- const EMPTY_ARRAY = [null, 0];
- const NO_SUBSCRIPTION_ARRAY = [null, null]; // Attempts to stringify whatever not-really-a-component value we were given
- // for logging in an error message
- const stringifyComponent = Comp => {
- try {
- return JSON.stringify(Comp);
- } catch (err) {
- return String(Comp);
- }
- };
- // This is "just" a `useLayoutEffect`, but with two modifications:
- // - we need to fall back to `useEffect` in SSR to avoid annoying warnings
- // - we extract this to a separate function to avoid closing over values
- // and causing memory leaks
- function useIsomorphicLayoutEffectWithArgs(effectFunc, effectArgs, dependencies) {
- (0, _useIsomorphicLayoutEffect.useIsomorphicLayoutEffect)(() => effectFunc(...effectArgs), dependencies);
- } // Effect callback, extracted: assign the latest props values to refs for later usage
- function captureWrapperProps(lastWrapperProps, lastChildProps, renderIsScheduled, wrapperProps, // actualChildProps: unknown,
- childPropsFromStoreUpdate, notifyNestedSubs) {
- // We want to capture the wrapper props and child props we used for later comparisons
- lastWrapperProps.current = wrapperProps;
- renderIsScheduled.current = false; // If the render was from a store update, clear out that reference and cascade the subscriber update
- if (childPropsFromStoreUpdate.current) {
- childPropsFromStoreUpdate.current = null;
- notifyNestedSubs();
- }
- } // Effect callback, extracted: subscribe to the Redux store or nearest connected ancestor,
- // check for updates after dispatched actions, and trigger re-renders.
- function subscribeUpdates(shouldHandleStateChanges, store, subscription, childPropsSelector, lastWrapperProps, lastChildProps, renderIsScheduled, isMounted, childPropsFromStoreUpdate, notifyNestedSubs, // forceComponentUpdateDispatch: React.Dispatch<any>,
- additionalSubscribeListener) {
- // If we're not subscribed to the store, nothing to do here
- if (!shouldHandleStateChanges) return () => {}; // Capture values for checking if and when this component unmounts
- let didUnsubscribe = false;
- let lastThrownError = null; // We'll run this callback every time a store subscription update propagates to this component
- const checkForUpdates = () => {
- if (didUnsubscribe || !isMounted.current) {
- // Don't run stale listeners.
- // Redux doesn't guarantee unsubscriptions happen until next dispatch.
- return;
- } // TODO We're currently calling getState ourselves here, rather than letting `uSES` do it
- const latestStoreState = store.getState();
- let newChildProps, error;
- try {
- // Actually run the selector with the most recent store state and wrapper props
- // to determine what the child props should be
- newChildProps = childPropsSelector(latestStoreState, lastWrapperProps.current);
- } catch (e) {
- error = e;
- lastThrownError = e;
- }
- if (!error) {
- lastThrownError = null;
- } // If the child props haven't changed, nothing to do here - cascade the subscription update
- if (newChildProps === lastChildProps.current) {
- if (!renderIsScheduled.current) {
- notifyNestedSubs();
- }
- } else {
- // Save references to the new child props. Note that we track the "child props from store update"
- // as a ref instead of a useState/useReducer because we need a way to determine if that value has
- // been processed. If this went into useState/useReducer, we couldn't clear out the value without
- // forcing another re-render, which we don't want.
- lastChildProps.current = newChildProps;
- childPropsFromStoreUpdate.current = newChildProps;
- renderIsScheduled.current = true; // TODO This is hacky and not how `uSES` is meant to be used
- // Trigger the React `useSyncExternalStore` subscriber
- additionalSubscribeListener();
- }
- }; // Actually subscribe to the nearest connected ancestor (or store)
- subscription.onStateChange = checkForUpdates;
- subscription.trySubscribe(); // Pull data from the store after first render in case the store has
- // changed since we began.
- checkForUpdates();
- const unsubscribeWrapper = () => {
- didUnsubscribe = true;
- subscription.tryUnsubscribe();
- subscription.onStateChange = null;
- if (lastThrownError) {
- // It's possible that we caught an error due to a bad mapState function, but the
- // parent re-rendered without this component and we're about to unmount.
- // This shouldn't happen as long as we do top-down subscriptions correctly, but
- // if we ever do those wrong, this throw will surface the error in our tests.
- // In that case, throw the error from here so it doesn't get lost.
- throw lastThrownError;
- }
- };
- return unsubscribeWrapper;
- } // Reducer initial state creation for our update reducer
- const initStateUpdates = () => EMPTY_ARRAY;
- function strictEqual(a, b) {
- return a === b;
- }
- /**
- * Infers the type of props that a connector will inject into a component.
- */
- let hasWarnedAboutDeprecatedPureOption = false;
- /**
- * Connects a React component to a Redux store.
- *
- * - Without arguments, just wraps the component, without changing the behavior / props
- *
- * - If 2 params are passed (3rd param, mergeProps, is skipped), default behavior
- * is to override ownProps (as stated in the docs), so what remains is everything that's
- * not a state or dispatch prop
- *
- * - When 3rd param is passed, we don't know if ownProps propagate and whether they
- * should be valid component props, because it depends on mergeProps implementation.
- * As such, it is the user's responsibility to extend ownProps interface from state or
- * dispatch props or both when applicable
- *
- * @param mapStateToProps A function that extracts values from state
- * @param mapDispatchToProps Setup for dispatching actions
- * @param mergeProps Optional callback to merge state and dispatch props together
- * @param options Options for configuring the connection
- *
- */
- function connect(mapStateToProps, mapDispatchToProps, mergeProps, {
- // The `pure` option has been removed, so TS doesn't like us destructuring this to check its existence.
- // @ts-ignore
- pure,
- areStatesEqual = strictEqual,
- areOwnPropsEqual = _shallowEqual.default,
- areStatePropsEqual = _shallowEqual.default,
- areMergedPropsEqual = _shallowEqual.default,
- // use React's forwardRef to expose a ref of the wrapped component
- forwardRef = false,
- // the context consumer to use
- context = _Context.ReactReduxContext
- } = {}) {
- if (process.env.NODE_ENV !== 'production') {
- if (pure !== undefined && !hasWarnedAboutDeprecatedPureOption) {
- hasWarnedAboutDeprecatedPureOption = true;
- (0, _warning.default)('The `pure` option has been removed. `connect` is now always a "pure/memoized" component');
- }
- }
- const Context = context;
- const initMapStateToProps = (0, _mapStateToProps.mapStateToPropsFactory)(mapStateToProps);
- const initMapDispatchToProps = (0, _mapDispatchToProps.mapDispatchToPropsFactory)(mapDispatchToProps);
- const initMergeProps = (0, _mergeProps.mergePropsFactory)(mergeProps);
- const shouldHandleStateChanges = Boolean(mapStateToProps);
- const wrapWithConnect = WrappedComponent => {
- if (process.env.NODE_ENV !== 'production' && !(0, _reactIs.isValidElementType)(WrappedComponent)) {
- throw new Error(`You must pass a component to the function returned by connect. Instead received ${stringifyComponent(WrappedComponent)}`);
- }
- const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
- const displayName = `Connect(${wrappedComponentName})`;
- const selectorFactoryOptions = {
- shouldHandleStateChanges,
- displayName,
- wrappedComponentName,
- WrappedComponent,
- // @ts-ignore
- initMapStateToProps,
- // @ts-ignore
- initMapDispatchToProps,
- initMergeProps,
- areStatesEqual,
- areStatePropsEqual,
- areOwnPropsEqual,
- areMergedPropsEqual
- };
- function ConnectFunction(props) {
- const [propsContext, reactReduxForwardedRef, wrapperProps] = React.useMemo(() => {
- // Distinguish between actual "data" props that were passed to the wrapper component,
- // and values needed to control behavior (forwarded refs, alternate context instances).
- // To maintain the wrapperProps object reference, memoize this destructuring.
- const {
- reactReduxForwardedRef
- } = props,
- wrapperProps = (0, _objectWithoutPropertiesLoose2.default)(props, _excluded);
- return [props.context, reactReduxForwardedRef, wrapperProps];
- }, [props]);
- const ContextToUse = React.useMemo(() => {
- // Users may optionally pass in a custom context instance to use instead of our ReactReduxContext.
- // Memoize the check that determines which context instance we should use.
- return propsContext && propsContext.Consumer && // @ts-ignore
- (0, _reactIs.isContextConsumer)( /*#__PURE__*/React.createElement(propsContext.Consumer, null)) ? propsContext : Context;
- }, [propsContext, Context]); // Retrieve the store and ancestor subscription via context, if available
- const contextValue = React.useContext(ContextToUse); // The store _must_ exist as either a prop or in context.
- // We'll check to see if it _looks_ like a Redux store first.
- // This allows us to pass through a `store` prop that is just a plain value.
- const didStoreComeFromProps = Boolean(props.store) && Boolean(props.store.getState) && Boolean(props.store.dispatch);
- const didStoreComeFromContext = Boolean(contextValue) && Boolean(contextValue.store);
- if (process.env.NODE_ENV !== 'production' && !didStoreComeFromProps && !didStoreComeFromContext) {
- throw new Error(`Could not find "store" in the context of ` + `"${displayName}". Either wrap the root component in a <Provider>, ` + `or pass a custom React context provider to <Provider> and the corresponding ` + `React context consumer to ${displayName} in connect options.`);
- } // Based on the previous check, one of these must be true
- const store = didStoreComeFromProps ? props.store : contextValue.store;
- const getServerState = didStoreComeFromContext ? contextValue.getServerState : store.getState;
- const childPropsSelector = React.useMemo(() => {
- // The child props selector needs the store reference as an input.
- // Re-create this selector whenever the store changes.
- return (0, _selectorFactory.default)(store.dispatch, selectorFactoryOptions);
- }, [store]);
- const [subscription, notifyNestedSubs] = React.useMemo(() => {
- if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY; // This Subscription's source should match where store came from: props vs. context. A component
- // connected to the store via props shouldn't use subscription from context, or vice versa.
- const subscription = (0, _Subscription.createSubscription)(store, didStoreComeFromProps ? undefined : contextValue.subscription); // `notifyNestedSubs` is duplicated to handle the case where the component is unmounted in
- // the middle of the notification loop, where `subscription` will then be null. This can
- // probably be avoided if Subscription's listeners logic is changed to not call listeners
- // that have been unsubscribed in the middle of the notification loop.
- const notifyNestedSubs = subscription.notifyNestedSubs.bind(subscription);
- return [subscription, notifyNestedSubs];
- }, [store, didStoreComeFromProps, contextValue]); // Determine what {store, subscription} value should be put into nested context, if necessary,
- // and memoize that value to avoid unnecessary context updates.
- const overriddenContextValue = React.useMemo(() => {
- if (didStoreComeFromProps) {
- // This component is directly subscribed to a store from props.
- // We don't want descendants reading from this store - pass down whatever
- // the existing context value is from the nearest connected ancestor.
- return contextValue;
- } // Otherwise, put this component's subscription instance into context, so that
- // connected descendants won't update until after this component is done
- return (0, _extends2.default)({}, contextValue, {
- subscription
- });
- }, [didStoreComeFromProps, contextValue, subscription]); // Set up refs to coordinate values between the subscription effect and the render logic
- const lastChildProps = React.useRef();
- const lastWrapperProps = React.useRef(wrapperProps);
- const childPropsFromStoreUpdate = React.useRef();
- const renderIsScheduled = React.useRef(false);
- const isProcessingDispatch = React.useRef(false);
- const isMounted = React.useRef(false);
- const latestSubscriptionCallbackError = React.useRef();
- (0, _useIsomorphicLayoutEffect.useIsomorphicLayoutEffect)(() => {
- isMounted.current = true;
- return () => {
- isMounted.current = false;
- };
- }, []);
- const actualChildPropsSelector = React.useMemo(() => {
- const selector = () => {
- // Tricky logic here:
- // - This render may have been triggered by a Redux store update that produced new child props
- // - However, we may have gotten new wrapper props after that
- // If we have new child props, and the same wrapper props, we know we should use the new child props as-is.
- // But, if we have new wrapper props, those might change the child props, so we have to recalculate things.
- // So, we'll use the child props from store update only if the wrapper props are the same as last time.
- if (childPropsFromStoreUpdate.current && wrapperProps === lastWrapperProps.current) {
- return childPropsFromStoreUpdate.current;
- } // TODO We're reading the store directly in render() here. Bad idea?
- // This will likely cause Bad Things (TM) to happen in Concurrent Mode.
- // Note that we do this because on renders _not_ caused by store updates, we need the latest store state
- // to determine what the child props should be.
- return childPropsSelector(store.getState(), wrapperProps);
- };
- return selector;
- }, [store, wrapperProps]); // We need this to execute synchronously every time we re-render. However, React warns
- // about useLayoutEffect in SSR, so we try to detect environment and fall back to
- // just useEffect instead to avoid the warning, since neither will run anyway.
- const subscribeForReact = React.useMemo(() => {
- const subscribe = reactListener => {
- if (!subscription) {
- return () => {};
- }
- return subscribeUpdates(shouldHandleStateChanges, store, subscription, // @ts-ignore
- childPropsSelector, lastWrapperProps, lastChildProps, renderIsScheduled, isMounted, childPropsFromStoreUpdate, notifyNestedSubs, reactListener);
- };
- return subscribe;
- }, [subscription]);
- useIsomorphicLayoutEffectWithArgs(captureWrapperProps, [lastWrapperProps, lastChildProps, renderIsScheduled, wrapperProps, childPropsFromStoreUpdate, notifyNestedSubs]);
- let actualChildProps;
- try {
- actualChildProps = useSyncExternalStore( // TODO We're passing through a big wrapper that does a bunch of extra side effects besides subscribing
- subscribeForReact, // TODO This is incredibly hacky. We've already processed the store update and calculated new child props,
- // TODO and we're just passing that through so it triggers a re-render for us rather than relying on `uSES`.
- actualChildPropsSelector, getServerState ? () => childPropsSelector(getServerState(), wrapperProps) : actualChildPropsSelector);
- } catch (err) {
- if (latestSubscriptionCallbackError.current) {
- ;
- err.message += `\nThe error may be correlated with this previous error:\n${latestSubscriptionCallbackError.current.stack}\n\n`;
- }
- throw err;
- }
- (0, _useIsomorphicLayoutEffect.useIsomorphicLayoutEffect)(() => {
- latestSubscriptionCallbackError.current = undefined;
- childPropsFromStoreUpdate.current = undefined;
- lastChildProps.current = actualChildProps;
- }); // Now that all that's done, we can finally try to actually render the child component.
- // We memoize the elements for the rendered child component as an optimization.
- const renderedWrappedComponent = React.useMemo(() => {
- return (
- /*#__PURE__*/
- // @ts-ignore
- React.createElement(WrappedComponent, (0, _extends2.default)({}, actualChildProps, {
- ref: reactReduxForwardedRef
- }))
- );
- }, [reactReduxForwardedRef, WrappedComponent, actualChildProps]); // If React sees the exact same element reference as last time, it bails out of re-rendering
- // that child, same as if it was wrapped in React.memo() or returned false from shouldComponentUpdate.
- const renderedChild = React.useMemo(() => {
- if (shouldHandleStateChanges) {
- // If this component is subscribed to store updates, we need to pass its own
- // subscription instance down to our descendants. That means rendering the same
- // Context instance, and putting a different value into the context.
- return /*#__PURE__*/React.createElement(ContextToUse.Provider, {
- value: overriddenContextValue
- }, renderedWrappedComponent);
- }
- return renderedWrappedComponent;
- }, [ContextToUse, renderedWrappedComponent, overriddenContextValue]);
- return renderedChild;
- }
- const _Connect = React.memo(ConnectFunction);
- // Add a hacky cast to get the right output type
- const Connect = _Connect;
- Connect.WrappedComponent = WrappedComponent;
- Connect.displayName = ConnectFunction.displayName = displayName;
- if (forwardRef) {
- const _forwarded = React.forwardRef(function forwardConnectRef(props, ref) {
- // @ts-ignore
- return /*#__PURE__*/React.createElement(Connect, (0, _extends2.default)({}, props, {
- reactReduxForwardedRef: ref
- }));
- });
- const forwarded = _forwarded;
- forwarded.displayName = displayName;
- forwarded.WrappedComponent = WrappedComponent;
- return (0, _hoistNonReactStatics.default)(forwarded, WrappedComponent);
- }
- return (0, _hoistNonReactStatics.default)(Connect, WrappedComponent);
- };
- return wrapWithConnect;
- }
- var _default = connect;
- exports.default = _default;
|