diff --git a/.yalc/react-redux/LICENSE.md b/.yalc/react-redux/LICENSE.md old mode 100644 new mode 100755 index 55bc8df..c108bf3 --- a/.yalc/react-redux/LICENSE.md +++ b/.yalc/react-redux/LICENSE.md @@ -1,21 +1,21 @@ -The MIT License (MIT) - -Copyright (c) 2015-present Dan Abramov - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The MIT License (MIT) + +Copyright (c) 2015-present Dan Abramov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.yalc/react-redux/README.md b/.yalc/react-redux/README.md old mode 100644 new mode 100755 index ae991af..4a0390d --- a/.yalc/react-redux/README.md +++ b/.yalc/react-redux/README.md @@ -1,60 +1,60 @@ -# React Redux - -Official React bindings for [Redux](https://github.com/reduxjs/redux). -Performant and flexible. - -![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/reduxjs/react-redux/test.yml?style=flat-square) [![npm version](https://img.shields.io/npm/v/react-redux.svg?style=flat-square)](https://www.npmjs.com/package/react-redux) -[![npm downloads](https://img.shields.io/npm/dm/react-redux.svg?style=flat-square)](https://www.npmjs.com/package/react-redux) -[![#redux channel on Discord](https://img.shields.io/badge/discord-redux@reactiflux-61DAFB.svg?style=flat-square)](http://www.reactiflux.com) - -## Installation - -### Using Create React App - -The recommended way to start new apps with React Redux is by using the [official Redux+JS/TS templates](https://github.com/reduxjs/cra-template-redux) for [Create React App](https://github.com/facebook/create-react-app), which takes advantage of [Redux Toolkit](https://redux-toolkit.js.org/). - -```sh -# JS -npx create-react-app my-app --template redux - -# TS -npx create-react-app my-app --template redux-typescript -``` - -### An Existing React App - -React Redux 8.0 requires **React 16.8.3 or later** (or React Native 0.59 or later). - -To use React Redux with your React app, install it as a dependency: - -```bash -# If you use npm: -npm install react-redux - -# Or if you use Yarn: -yarn add react-redux -``` - -You'll also need to [install Redux](https://redux.js.org/introduction/installation) and [set up a Redux store](https://redux.js.org/recipes/configuring-your-store/) in your app. - -This assumes that you’re using [npm](http://npmjs.com/) package manager -with a module bundler like [Webpack](https://webpack.js.org/) or -[Browserify](http://browserify.org/) to consume [CommonJS -modules](https://webpack.js.org/api/module-methods/#commonjs). - -If you don’t yet use [npm](http://npmjs.com/) or a modern module bundler, and would rather prefer a single-file [UMD](https://github.com/umdjs/umd) build that makes `ReactRedux` available as a global object, you can grab a pre-built version from [cdnjs](https://cdnjs.com/libraries/react-redux). We _don’t_ recommend this approach for any serious application, as most of the libraries complementary to Redux are only available on [npm](http://npmjs.com/). - -## Documentation - -The React Redux docs are published at **https://react-redux.js.org** . - -## How Does It Work? - -The post [The History and Implementation of React-Redux](https://blog.isquaredsoftware.com/2018/11/react-redux-history-implementation/) -explains what it does, how it works, and how the API and implementation have evolved over time. - -There's also a [Deep Dive into React-Redux](https://blog.isquaredsoftware.com/2019/06/presentation-react-redux-deep-dive/) talk that covers some of the same material at a higher level. - -## License - -[MIT](LICENSE.md) +# React Redux + +Official React bindings for [Redux](https://github.com/reduxjs/redux). +Performant and flexible. + +![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/reduxjs/react-redux/test.yml?style=flat-square) [![npm version](https://img.shields.io/npm/v/react-redux.svg?style=flat-square)](https://www.npmjs.com/package/react-redux) +[![npm downloads](https://img.shields.io/npm/dm/react-redux.svg?style=flat-square)](https://www.npmjs.com/package/react-redux) +[![#redux channel on Discord](https://img.shields.io/badge/discord-redux@reactiflux-61DAFB.svg?style=flat-square)](http://www.reactiflux.com) + +## Installation + +### Using Create React App + +The recommended way to start new apps with React Redux is by using the [official Redux+JS/TS templates](https://github.com/reduxjs/cra-template-redux) for [Create React App](https://github.com/facebook/create-react-app), which takes advantage of [Redux Toolkit](https://redux-toolkit.js.org/). + +```sh +# JS +npx create-react-app my-app --template redux + +# TS +npx create-react-app my-app --template redux-typescript +``` + +### An Existing React App + +React Redux 8.0 requires **React 16.8.3 or later** (or React Native 0.59 or later). + +To use React Redux with your React app, install it as a dependency: + +```bash +# If you use npm: +npm install react-redux + +# Or if you use Yarn: +yarn add react-redux +``` + +You'll also need to [install Redux](https://redux.js.org/introduction/installation) and [set up a Redux store](https://redux.js.org/recipes/configuring-your-store/) in your app. + +This assumes that you’re using [npm](http://npmjs.com/) package manager +with a module bundler like [Webpack](https://webpack.js.org/) or +[Browserify](http://browserify.org/) to consume [CommonJS +modules](https://webpack.js.org/api/module-methods/#commonjs). + +If you don’t yet use [npm](http://npmjs.com/) or a modern module bundler, and would rather prefer a single-file [UMD](https://github.com/umdjs/umd) build that makes `ReactRedux` available as a global object, you can grab a pre-built version from [cdnjs](https://cdnjs.com/libraries/react-redux). We _don’t_ recommend this approach for any serious application, as most of the libraries complementary to Redux are only available on [npm](http://npmjs.com/). + +## Documentation + +The React Redux docs are published at **https://react-redux.js.org** . + +## How Does It Work? + +The post [The History and Implementation of React-Redux](https://blog.isquaredsoftware.com/2018/11/react-redux-history-implementation/) +explains what it does, how it works, and how the API and implementation have evolved over time. + +There's also a [Deep Dive into React-Redux](https://blog.isquaredsoftware.com/2019/06/presentation-react-redux-deep-dive/) talk that covers some of the same material at a higher level. + +## License + +[MIT](LICENSE.md) diff --git a/.yalc/react-redux/dist/react-redux.js b/.yalc/react-redux/dist/react-redux.js old mode 100644 new mode 100755 index 8418747..1c33172 --- a/.yalc/react-redux/dist/react-redux.js +++ b/.yalc/react-redux/dist/react-redux.js @@ -464,12 +464,12 @@ })); - /** - * Hook factory, which creates a `useReduxContext` hook bound to a given context. This is a low-level - * hook that you should usually not need to call directly. - * - * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. - * @returns {Function} A `useReduxContext` hook bound to the specified context. + /** + * Hook factory, which creates a `useReduxContext` hook bound to a given context. This is a low-level + * hook that you should usually not need to call directly. + * + * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. + * @returns {Function} A `useReduxContext` hook bound to the specified context. */ function createReduxContextHook(context = ReactReduxContext) { return function useReduxContext() { @@ -482,21 +482,21 @@ return contextValue; }; } - /** - * A hook to access the value of the `ReactReduxContext`. This is a low-level - * hook that you should usually not need to call directly. - * - * @returns {any} the value of the `ReactReduxContext` - * - * @example - * - * import React from 'react' - * import { useReduxContext } from 'react-redux' - * - * export const CounterComponent = () => { - * const { store } = useReduxContext() - * return
{store.getState()}
- * } + /** + * A hook to access the value of the `ReactReduxContext`. This is a low-level + * hook that you should usually not need to call directly. + * + * @returns {any} the value of the `ReactReduxContext` + * + * @example + * + * import React from 'react' + * import { useReduxContext } from 'react-redux' + * + * export const CounterComponent = () => { + * const { store } = useReduxContext() + * return
{store.getState()}
+ * } */ const useReduxContext = /*#__PURE__*/createReduxContextHook(); @@ -612,42 +612,42 @@ return this._needsRecalculation; } - /* - getWithArgs = (...args: any[]) => { - // console.log( - // `TrackingCache value: revision = ${this.revision}, cachedRevision = ${this._cachedRevision}, value = ${this._cachedValue}` - // ) - // When getting the value for a Cache, first we check all the dependencies of - // the cache to see what their current revision is. If the current revision is - // greater than the cached revision, then something has changed. - //if (this.revision > this._cachedRevision) { - if (this.needsRecalculation()) { - const { fn } = this - // We create a new dependency tracker for this cache. As the cache runs - // its function, any Storage or Cache instances which are used while - // computing will be added to this tracker. In the end, it will be the - // full list of dependencies that this Cache depends on. - const currentTracker = new Set>() - const prevTracker = CURRENT_TRACKER - CURRENT_TRACKER = currentTracker - // try { - this._cachedValue = fn.apply(null, args) - // } finally { - CURRENT_TRACKER = prevTracker - this.hits++ - this._deps = Array.from(currentTracker) - // Set the cached revision. This is the current clock count of all the - // dependencies. If any dependency changes, this number will be less - // than the new revision. - this._cachedRevision = this.revision - // } - } - // If there is a current tracker, it means another Cache is computing and - // using this one, so we add this one to the tracker. - CURRENT_TRACKER?.add(this) - // Always return the cached value. - return this._cachedValue - } + /* + getWithArgs = (...args: any[]) => { + // console.log( + // `TrackingCache value: revision = ${this.revision}, cachedRevision = ${this._cachedRevision}, value = ${this._cachedValue}` + // ) + // When getting the value for a Cache, first we check all the dependencies of + // the cache to see what their current revision is. If the current revision is + // greater than the cached revision, then something has changed. + //if (this.revision > this._cachedRevision) { + if (this.needsRecalculation()) { + const { fn } = this + // We create a new dependency tracker for this cache. As the cache runs + // its function, any Storage or Cache instances which are used while + // computing will be added to this tracker. In the end, it will be the + // full list of dependencies that this Cache depends on. + const currentTracker = new Set>() + const prevTracker = CURRENT_TRACKER + CURRENT_TRACKER = currentTracker + // try { + this._cachedValue = fn.apply(null, args) + // } finally { + CURRENT_TRACKER = prevTracker + this.hits++ + this._deps = Array.from(currentTracker) + // Set the cached revision. This is the current clock count of all the + // dependencies. If any dependency changes, this number will be less + // than the new revision. + this._cachedRevision = this.revision + // } + } + // If there is a current tracker, it means another Cache is computing and + // using this one, so we add this one to the tracker. + CURRENT_TRACKER?.add(this) + // Always return the cached value. + return this._cachedValue + } */ @@ -727,11 +727,11 @@ }; const refEquality = (a, b) => a === b; - /** - * Hook factory, which creates a `useSelector` hook bound to a given context. - * - * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. - * @returns {Function} A `useSelector` hook bound to the specified context. + /** + * Hook factory, which creates a `useSelector` hook bound to a given context. + * + * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. + * @returns {Function} A `useSelector` hook bound to the specified context. */ @@ -848,28 +848,28 @@ 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
{counter}
- * } + /** + * 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
{counter}
+ * } */ const useSelector = /*#__PURE__*/createSelectorHook(); @@ -1401,11 +1401,11 @@ } }); - /** - * Prints a warning in the console if it exists. - * - * @param {String} message The warning message. - * @returns {void} + /** + * Prints a warning in the console if it exists. + * + * @param {String} message The warning message. + * @returns {void} */ function warning(message) { /* eslint-disable no-console */ @@ -1539,9 +1539,9 @@ return boundActionCreators; } - /** - * @param {any} obj The object to inspect. - * @returns {boolean} True if the argument appears to be a plain object. + /** + * @param {any} obj The object to inspect. + * @returns {boolean} True if the argument appears to be a plain object. */ function isPlainObject(obj) { if (typeof obj !== 'object' || obj === null) return false; @@ -1894,7 +1894,7 @@ if (childValue === newChildValue) { continue; } else if (typeof newChildValue === 'object' && newChildValue !== null) { - console.log('Updating node key: ', key); + // console.log('Updating node key: ', key) updateNode(childNode, newChildValue); } else { deleteNode(childNode); @@ -1934,6 +1934,8 @@ notify() { //console.log('Notifying subscribers') + let numCalled = 0; + let numSkipped = 0; batch(() => { let listener = first; @@ -1946,7 +1948,13 @@ // 'Calling subscriber due to recalc. Revision before: ', // $REVISION // ) + numCalled++; listener.callback(); //console.log('Revision after: ', $REVISION) + } else { + numSkipped++; // console.log( + // 'Skipping subscriber, no recalc: ', + // listener.selectorCache + // ) } } else { listener.callback(); @@ -1955,6 +1963,11 @@ listener = listener.next; } }); + const result = { + numCalled, + numSkipped + }; + return result; }, get() { @@ -2037,10 +2050,16 @@ function notifyNestedSubs() { if (store && trackingNode) { //console.log('Updating node in notifyNestedSubs') + performance.now(); + updateNode(trackingNode, store.getState()); + + performance.now(); } + performance.now(); listeners.notify(); + performance.now(); } function handleChangeWrapper() { @@ -2230,31 +2249,31 @@ function strictEqual(a, b) { return a === b; } - /** - * Infers the type of props that a connector will inject into a component. + /** + * 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 - * + /** + * 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, { @@ -2543,11 +2562,11 @@ }, children); } - /** - * Hook factory, which creates a `useStore` hook bound to a given context. - * - * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. - * @returns {Function} A `useStore` hook bound to the specified context. + /** + * Hook factory, which creates a `useStore` hook bound to a given context. + * + * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. + * @returns {Function} A `useStore` hook bound to the specified context. */ function createStoreHook(context = ReactReduxContext) { @@ -2562,29 +2581,29 @@ return store; }; } - /** - * A hook to access the redux store. - * - * @returns {any} the redux store - * - * @example - * - * import React from 'react' - * import { useStore } from 'react-redux' - * - * export const ExampleComponent = () => { - * const store = useStore() - * return
{store.getState()}
- * } + /** + * A hook to access the redux store. + * + * @returns {any} the redux store + * + * @example + * + * import React from 'react' + * import { useStore } from 'react-redux' + * + * export const ExampleComponent = () => { + * const store = useStore() + * return
{store.getState()}
+ * } */ const useStore = /*#__PURE__*/createStoreHook(); - /** - * Hook factory, which creates a `useDispatch` hook bound to a given context. - * - * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. - * @returns {Function} A `useDispatch` hook bound to the specified context. + /** + * Hook factory, which creates a `useDispatch` hook bound to a given context. + * + * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. + * @returns {Function} A `useDispatch` hook bound to the specified context. */ function createDispatchHook(context = ReactReduxContext) { @@ -2596,26 +2615,26 @@ return store.dispatch; }; } - /** - * A hook to access the redux `dispatch` function. - * - * @returns {any|function} redux store's `dispatch` function - * - * @example - * - * import React, { useCallback } from 'react' - * import { useDispatch } from 'react-redux' - * - * export const CounterComponent = ({ value }) => { - * const dispatch = useDispatch() - * const increaseCounter = useCallback(() => dispatch({ type: 'increase-counter' }), []) - * return ( - *
- * {value} - * - *
- * ) - * } + /** + * A hook to access the redux `dispatch` function. + * + * @returns {any|function} redux store's `dispatch` function + * + * @example + * + * import React, { useCallback } from 'react' + * import { useDispatch } from 'react-redux' + * + * export const CounterComponent = ({ value }) => { + * const dispatch = useDispatch() + * const increaseCounter = useCallback(() => dispatch({ type: 'increase-counter' }), []) + * return ( + *
+ * {value} + * + *
+ * ) + * } */ const useDispatch = /*#__PURE__*/createDispatchHook(); diff --git a/.yalc/react-redux/dist/react-redux.min.js b/.yalc/react-redux/dist/react-redux.min.js old mode 100644 new mode 100755 index 91cd3c6..50a2a6d --- a/.yalc/react-redux/dist/react-redux.min.js +++ b/.yalc/react-redux/dist/react-redux.min.js @@ -1,4 +1,4 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react"),require("react-dom")):"function"==typeof define&&define.amd?define(["exports","react","react-dom"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).ReactRedux={},e.React,e.ReactDOM)}(this,(function(e,t,n){"use strict";function r(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var o=r(t);function u(e,t,n){return e(n={path:t,exports:{},require:function(e,t){return function(){throw Error("Dynamic requires are not currently supported by @rollup/plugin-commonjs")}()}},n.exports),n.exports}var c="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t},s=o.default.useState,i=o.default.useEffect,a=o.default.useLayoutEffect,l=o.default.useDebugValue;function f(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!c(e,n)}catch(e){return!0}}var p="undefined"==typeof window||void 0===window.document||void 0===window.document.createElement?function(e,t){return t()}:function(e,t){var n=t(),r=s({inst:{value:n,getSnapshot:t}}),o=r[0].inst,u=r[1];return a((function(){o.value=n,o.getSnapshot=t,f(o)&&u({inst:o})}),[e,n,t]),i((function(){return f(o)&&u({inst:o}),e((function(){f(o)&&u({inst:o})}))}),[e]),l(n),n},d={useSyncExternalStore:void 0!==o.default.useSyncExternalStore?o.default.useSyncExternalStore:p},y=u((function(e){e.exports=d}));var h="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t},m=y.useSyncExternalStore,v=o.default.useRef,g=o.default.useEffect,b=o.default.useMemo,S=o.default.useDebugValue,w={useSyncExternalStoreWithSelector:function(e,t,n,r,o){var u=v(null);if(null===u.current){var c={hasValue:!1,value:null};u.current=c}else c=u.current;u=b((function(){function e(e){if(!i){if(i=!0,u=e,e=r(e),void 0!==o&&c.hasValue){var t=c.value;if(o(t,e))return s=t}return s=e}if(t=s,h(u,e))return t;var n=r(e);return void 0!==o&&o(t,n)?t:(u=e,s=n)}var u,s,i=!1,a=void 0===n?null:n;return[function(){return e(t())},null===a?void 0:function(){return e(a())}]}),[t,n,r,o]);var s=m(e,u[0],u[1]);return g((function(){c.hasValue=!0,c.value=s}),[s]),S(s),s}},P=u((function(e){e.exports=w}));let x=function(e){e()};const O=()=>x,$=Symbol.for("react-redux-context-"+t.version),C=globalThis;const R=new Proxy({},new Proxy({},{get(e,n){const r=function(){let e=C[$];return e||(e=t.createContext(null),C[$]=e),e}();return(e,...t)=>Reflect[n](r,...t)}}));function E(e=R){return function(){return t.useContext(e)}}const M=E(),_=()=>{throw Error("uSES not initialized!")},j=!("undefined"==typeof window||void 0===window.document||void 0===window.document.createElement)?t.useLayoutEffect:t.useEffect;function T(e,t="Assertion failed!"){if(!e)throw console.error(t),Error(t)}let k=0,q=null;class N{constructor(e,t=D,n){this.revision=k,this._value=void 0,this._lastValue=void 0,this._isEqual=D,this._name=void 0,this._value=this._lastValue=e,this._isEqual=t,this._name=n}get value(){var e;return null==(e=q)||e.add(this),this._value}set value(e){this.value!==e&&(this._value=e,this.revision=++k)}}function D(e,t){return e===t}class V{constructor(e){this._cachedValue=void 0,this._cachedRevision=-1,this._deps=[],this.hits=0,this._needsRecalculation=!1,this.fn=void 0,this.getValue=()=>this.value,this.fn=e}clear(){this._cachedValue=void 0,this._cachedRevision=-1,this._deps=[],this.hits=0,this._needsRecalculation=!1}needsRecalculation(){return this._needsRecalculation||(this._needsRecalculation=this.revision>this._cachedRevision),this._needsRecalculation}get value(){var e;if(this.needsRecalculation()){const{fn:e}=this,t=new Set,n=q;q=t,this._cachedValue=e(),q=n,this.hits++,this._deps=Array.from(t),this._cachedRevision=this.revision,this._needsRecalculation=!1}return null==(e=q)||e.add(this),this._cachedValue}get revision(){return Math.max(...this._deps.map((e=>e.revision)),0)}}function F(e){return e instanceof N||console.warn("Not a valid cell! ",e),e.value}let A=_;const L=(e,t)=>e===t;function z(e=R){const n=e===R?M:E(e);return function(e,r={}){const{equalityFn:o=L,stabilityCheck:u}="function"==typeof r?{equalityFn:r}:r,{store:c,subscription:s,getServerState:i,stabilityCheck:a,trackingNode:l}=n();t.useRef(!0);const f=t.useCallback({[e.name]:t=>e(t)}[e.name],[e,a,u]);t.useRef(f).current=f;const p=t.useMemo((()=>{var e;return T("function"==typeof(e=()=>f(l.proxy)),"the first parameter to `createCache` must be a function"),new V(e)}),[l,f]),d=t.useRef({cache:p});j((()=>{d.current.cache=p}));const y=t.useMemo((()=>e=>s.addNestedSub((()=>e()),{trigger:"tracked",cache:d.current})),[s]),h=A(y,c.getState,i||c.getState,p.getValue,o);return t.useDebugValue(h),h}}const U=z();function W(){return(W=Object.assign||function(e){for(var t=1;arguments.length>t;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function H(e,t){if(null==e)return{};var n,r,o={},u=Object.keys(e);for(r=0;u.length>r;r++)0>t.indexOf(n=u[r])&&(o[n]=e[n]);return o} +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react"),require("react-dom")):"function"==typeof define&&define.amd?define(["exports","react","react-dom"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).ReactRedux={},e.React,e.ReactDOM)}(this,(function(e,t,n){"use strict";function r(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var o=r(t);function u(e,t,n){return e(n={path:t,exports:{},require:function(e,t){return function(){throw Error("Dynamic requires are not currently supported by @rollup/plugin-commonjs")}()}},n.exports),n.exports}var c="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t},s=o.default.useState,i=o.default.useEffect,a=o.default.useLayoutEffect,l=o.default.useDebugValue;function f(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!c(e,n)}catch(e){return!0}}var p="undefined"==typeof window||void 0===window.document||void 0===window.document.createElement?function(e,t){return t()}:function(e,t){var n=t(),r=s({inst:{value:n,getSnapshot:t}}),o=r[0].inst,u=r[1];return a((function(){o.value=n,o.getSnapshot=t,f(o)&&u({inst:o})}),[e,n,t]),i((function(){return f(o)&&u({inst:o}),e((function(){f(o)&&u({inst:o})}))}),[e]),l(n),n},d={useSyncExternalStore:void 0!==o.default.useSyncExternalStore?o.default.useSyncExternalStore:p},y=u((function(e){e.exports=d}));var h="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t},m=y.useSyncExternalStore,v=o.default.useRef,b=o.default.useEffect,g=o.default.useMemo,S=o.default.useDebugValue,w={useSyncExternalStoreWithSelector:function(e,t,n,r,o){var u=v(null);if(null===u.current){var c={hasValue:!1,value:null};u.current=c}else c=u.current;u=g((function(){function e(e){if(!i){if(i=!0,u=e,e=r(e),void 0!==o&&c.hasValue){var t=c.value;if(o(t,e))return s=t}return s=e}if(t=s,h(u,e))return t;var n=r(e);return void 0!==o&&o(t,n)?t:(u=e,s=n)}var u,s,i=!1,a=void 0===n?null:n;return[function(){return e(t())},null===a?void 0:function(){return e(a())}]}),[t,n,r,o]);var s=m(e,u[0],u[1]);return b((function(){c.hasValue=!0,c.value=s}),[s]),S(s),s}},P=u((function(e){e.exports=w}));let x=function(e){e()};const O=()=>x,$=Symbol.for("react-redux-context-"+t.version),C=globalThis;const R=new Proxy({},new Proxy({},{get(e,n){const r=function(){let e=C[$];return e||(e=t.createContext(null),C[$]=e),e}();return(e,...t)=>Reflect[n](r,...t)}}));function E(e=R){return function(){return t.useContext(e)}}const M=E(),_=()=>{throw Error("uSES not initialized!")},j=!("undefined"==typeof window||void 0===window.document||void 0===window.document.createElement)?t.useLayoutEffect:t.useEffect;function T(e,t="Assertion failed!"){if(!e)throw console.error(t),Error(t)}let k=0,q=null;class N{constructor(e,t=D,n){this.revision=k,this._value=void 0,this._lastValue=void 0,this._isEqual=D,this._name=void 0,this._value=this._lastValue=e,this._isEqual=t,this._name=n}get value(){var e;return null==(e=q)||e.add(this),this._value}set value(e){this.value!==e&&(this._value=e,this.revision=++k)}}function D(e,t){return e===t}class V{constructor(e){this._cachedValue=void 0,this._cachedRevision=-1,this._deps=[],this.hits=0,this._needsRecalculation=!1,this.fn=void 0,this.getValue=()=>this.value,this.fn=e}clear(){this._cachedValue=void 0,this._cachedRevision=-1,this._deps=[],this.hits=0,this._needsRecalculation=!1}needsRecalculation(){return this._needsRecalculation||(this._needsRecalculation=this.revision>this._cachedRevision),this._needsRecalculation}get value(){var e;if(this.needsRecalculation()){const{fn:e}=this,t=new Set,n=q;q=t,this._cachedValue=e(),q=n,this.hits++,this._deps=Array.from(t),this._cachedRevision=this.revision,this._needsRecalculation=!1}return null==(e=q)||e.add(this),this._cachedValue}get revision(){return Math.max(...this._deps.map((e=>e.revision)),0)}}function F(e){return e instanceof N||console.warn("Not a valid cell! ",e),e.value}let A=_;const L=(e,t)=>e===t;function z(e=R){const n=e===R?M:E(e);return function(e,r={}){const{equalityFn:o=L,stabilityCheck:u}="function"==typeof r?{equalityFn:r}:r,{store:c,subscription:s,getServerState:i,stabilityCheck:a,trackingNode:l}=n();t.useRef(!0);const f=t.useCallback({[e.name]:t=>e(t)}[e.name],[e,a,u]);t.useRef(f).current=f;const p=t.useMemo((()=>{var e;return T("function"==typeof(e=()=>f(l.proxy)),"the first parameter to `createCache` must be a function"),new V(e)}),[l,f]),d=t.useRef({cache:p});j((()=>{d.current.cache=p}));const y=t.useMemo((()=>e=>s.addNestedSub((()=>e()),{trigger:"tracked",cache:d.current})),[s]),h=A(y,c.getState,i||c.getState,p.getValue,o);return t.useDebugValue(h),h}}const U=z();function W(){return(W=Object.assign||function(e){for(var t=1;arguments.length>t;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function H(e,t){if(null==e)return{};var n,r,o={},u=Object.keys(e);for(r=0;u.length>r;r++)0>t.indexOf(n=u[r])&&(o[n]=e[n]);return o} /** @license React v16.13.1 * react-is.production.min.js * @@ -6,7 +6,7 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var K="function"==typeof Symbol&&Symbol.for,I=K?Symbol.for("react.element"):60103,B=K?Symbol.for("react.portal"):60106,G=K?Symbol.for("react.fragment"):60107,J=K?Symbol.for("react.strict_mode"):60108,Q=K?Symbol.for("react.profiler"):60114,X=K?Symbol.for("react.provider"):60109,Y=K?Symbol.for("react.context"):60110,Z=K?Symbol.for("react.async_mode"):60111,ee=K?Symbol.for("react.concurrent_mode"):60111,te=K?Symbol.for("react.forward_ref"):60112,ne=K?Symbol.for("react.suspense"):60113,re=K?Symbol.for("react.suspense_list"):60120,oe=K?Symbol.for("react.memo"):60115,ue=K?Symbol.for("react.lazy"):60116,ce=K?Symbol.for("react.block"):60121,se=K?Symbol.for("react.fundamental"):60117,ie=K?Symbol.for("react.responder"):60118,ae=K?Symbol.for("react.scope"):60119;function le(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case I:switch(e=e.type){case Z:case ee:case G:case Q:case J:case ne:return e;default:switch(e=e&&e.$$typeof){case Y:case te:case ue:case oe:case X:return e;default:return t}}case B:return t}}}function fe(e){return le(e)===ee}var pe={AsyncMode:Z,ConcurrentMode:ee,ContextConsumer:Y,ContextProvider:X,Element:I,ForwardRef:te,Fragment:G,Lazy:ue,Memo:oe,Portal:B,Profiler:Q,StrictMode:J,Suspense:ne,isAsyncMode:function(e){return fe(e)||le(e)===Z},isConcurrentMode:fe,isContextConsumer:function(e){return le(e)===Y},isContextProvider:function(e){return le(e)===X},isElement:function(e){return"object"==typeof e&&null!==e&&e.$$typeof===I},isForwardRef:function(e){return le(e)===te},isFragment:function(e){return le(e)===G},isLazy:function(e){return le(e)===ue},isMemo:function(e){return le(e)===oe},isPortal:function(e){return le(e)===B},isProfiler:function(e){return le(e)===Q},isStrictMode:function(e){return le(e)===J},isSuspense:function(e){return le(e)===ne},isValidElementType:function(e){return"string"==typeof e||"function"==typeof e||e===G||e===ee||e===Q||e===J||e===ne||e===re||"object"==typeof e&&null!==e&&(e.$$typeof===ue||e.$$typeof===oe||e.$$typeof===X||e.$$typeof===Y||e.$$typeof===te||e.$$typeof===se||e.$$typeof===ie||e.$$typeof===ae||e.$$typeof===ce)},typeOf:le},de=u((function(e){e.exports=pe})),ye={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},he={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},me={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},ve={};function ge(e){return de.isMemo(e)?me:ve[e.$$typeof]||ye}ve[de.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},ve[de.Memo]=me;var be=Object.defineProperty,Se=Object.getOwnPropertyNames,we=Object.getOwnPropertySymbols,Pe=Object.getOwnPropertyDescriptor,xe=Object.getPrototypeOf,Oe=Object.prototype;var $e=function e(t,n,r){if("string"!=typeof n){if(Oe){var o=xe(n);o&&o!==Oe&&e(t,o,r)}var u=Se(n);we&&(u=u.concat(we(n)));for(var c=ge(t),s=ge(n),i=0;u.length>i;++i){var a=u[i];if(!(he[a]||r&&r[a]||s&&s[a]||c&&c[a])){var l=Pe(n,a);try{be(t,a,l)}catch(e){}}}}return t},Ce=Symbol.for("react.element"),Re=Symbol.for("react.portal"),Ee=Symbol.for("react.fragment"),Me=Symbol.for("react.strict_mode"),_e=Symbol.for("react.profiler"),je=Symbol.for("react.provider"),Te=Symbol.for("react.context"),ke=Symbol.for("react.server_context"),qe=Symbol.for("react.forward_ref"),Ne=Symbol.for("react.suspense"),De=Symbol.for("react.suspense_list"),Ve=Symbol.for("react.memo"),Fe=Symbol.for("react.lazy"),Ae=Symbol.for("react.offscreen"),Le=Symbol.for("react.module.reference"); + */var K="function"==typeof Symbol&&Symbol.for,I=K?Symbol.for("react.element"):60103,B=K?Symbol.for("react.portal"):60106,G=K?Symbol.for("react.fragment"):60107,J=K?Symbol.for("react.strict_mode"):60108,Q=K?Symbol.for("react.profiler"):60114,X=K?Symbol.for("react.provider"):60109,Y=K?Symbol.for("react.context"):60110,Z=K?Symbol.for("react.async_mode"):60111,ee=K?Symbol.for("react.concurrent_mode"):60111,te=K?Symbol.for("react.forward_ref"):60112,ne=K?Symbol.for("react.suspense"):60113,re=K?Symbol.for("react.suspense_list"):60120,oe=K?Symbol.for("react.memo"):60115,ue=K?Symbol.for("react.lazy"):60116,ce=K?Symbol.for("react.block"):60121,se=K?Symbol.for("react.fundamental"):60117,ie=K?Symbol.for("react.responder"):60118,ae=K?Symbol.for("react.scope"):60119;function le(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case I:switch(e=e.type){case Z:case ee:case G:case Q:case J:case ne:return e;default:switch(e=e&&e.$$typeof){case Y:case te:case ue:case oe:case X:return e;default:return t}}case B:return t}}}function fe(e){return le(e)===ee}var pe={AsyncMode:Z,ConcurrentMode:ee,ContextConsumer:Y,ContextProvider:X,Element:I,ForwardRef:te,Fragment:G,Lazy:ue,Memo:oe,Portal:B,Profiler:Q,StrictMode:J,Suspense:ne,isAsyncMode:function(e){return fe(e)||le(e)===Z},isConcurrentMode:fe,isContextConsumer:function(e){return le(e)===Y},isContextProvider:function(e){return le(e)===X},isElement:function(e){return"object"==typeof e&&null!==e&&e.$$typeof===I},isForwardRef:function(e){return le(e)===te},isFragment:function(e){return le(e)===G},isLazy:function(e){return le(e)===ue},isMemo:function(e){return le(e)===oe},isPortal:function(e){return le(e)===B},isProfiler:function(e){return le(e)===Q},isStrictMode:function(e){return le(e)===J},isSuspense:function(e){return le(e)===ne},isValidElementType:function(e){return"string"==typeof e||"function"==typeof e||e===G||e===ee||e===Q||e===J||e===ne||e===re||"object"==typeof e&&null!==e&&(e.$$typeof===ue||e.$$typeof===oe||e.$$typeof===X||e.$$typeof===Y||e.$$typeof===te||e.$$typeof===se||e.$$typeof===ie||e.$$typeof===ae||e.$$typeof===ce)},typeOf:le},de=u((function(e){e.exports=pe})),ye={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},he={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},me={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},ve={};function be(e){return de.isMemo(e)?me:ve[e.$$typeof]||ye}ve[de.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},ve[de.Memo]=me;var ge=Object.defineProperty,Se=Object.getOwnPropertyNames,we=Object.getOwnPropertySymbols,Pe=Object.getOwnPropertyDescriptor,xe=Object.getPrototypeOf,Oe=Object.prototype;var $e=function e(t,n,r){if("string"!=typeof n){if(Oe){var o=xe(n);o&&o!==Oe&&e(t,o,r)}var u=Se(n);we&&(u=u.concat(we(n)));for(var c=be(t),s=be(n),i=0;u.length>i;++i){var a=u[i];if(!(he[a]||r&&r[a]||s&&s[a]||c&&c[a])){var l=Pe(n,a);try{ge(t,a,l)}catch(e){}}}}return t},Ce=Symbol.for("react.element"),Re=Symbol.for("react.portal"),Ee=Symbol.for("react.fragment"),Me=Symbol.for("react.strict_mode"),_e=Symbol.for("react.profiler"),je=Symbol.for("react.provider"),Te=Symbol.for("react.context"),ke=Symbol.for("react.server_context"),qe=Symbol.for("react.forward_ref"),Ne=Symbol.for("react.suspense"),De=Symbol.for("react.suspense_list"),Ve=Symbol.for("react.memo"),Fe=Symbol.for("react.lazy"),Ae=Symbol.for("react.offscreen"),Le=Symbol.for("react.module.reference"); /** * @license React * react-is.production.min.js @@ -15,4 +15,4 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */function ze(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case Ce:switch(e=e.type){case Ee:case _e:case Me:case Ne:case De:return e;default:switch(e=e&&e.$$typeof){case ke:case Te:case qe:case Fe:case Ve:case je:return e;default:return t}}case Re:return t}}}var Ue={ContextConsumer:Te,ContextProvider:je,Element:Ce,ForwardRef:qe,Fragment:Ee,Lazy:Fe,Memo:Ve,Portal:Re,Profiler:_e,StrictMode:Me,Suspense:Ne,SuspenseList:De,isAsyncMode:function(){return!1},isConcurrentMode:function(){return!1},isContextConsumer:function(e){return ze(e)===Te},isContextProvider:function(e){return ze(e)===je},isElement:function(e){return"object"==typeof e&&null!==e&&e.$$typeof===Ce},isForwardRef:function(e){return ze(e)===qe},isFragment:function(e){return ze(e)===Ee},isLazy:function(e){return ze(e)===Fe},isMemo:function(e){return ze(e)===Ve},isPortal:function(e){return ze(e)===Re},isProfiler:function(e){return ze(e)===_e},isStrictMode:function(e){return ze(e)===Me},isSuspense:function(e){return ze(e)===Ne},isSuspenseList:function(e){return ze(e)===De},isValidElementType:function(e){return"string"==typeof e||"function"==typeof e||e===Ee||e===_e||e===Me||e===Ne||e===De||e===Ae||"object"==typeof e&&null!==e&&(e.$$typeof===Fe||e.$$typeof===Ve||e.$$typeof===je||e.$$typeof===Te||e.$$typeof===qe||e.$$typeof===Le||void 0!==e.getModuleId)},typeOf:ze},We=u((function(e){e.exports=Ue}));const He=["initMapStateToProps","initMapDispatchToProps","initMergeProps"];function Ke(e,t,n,r,{areStatesEqual:o,areOwnPropsEqual:u,areStatePropsEqual:c}){let s,i,a,l,f,p=!1;function d(p,d){const y=!u(d,i),h=!o(p,s,d,i);return s=p,i=d,y&&h?(a=e(s,i),t.dependsOnOwnProps&&(l=t(r,i)),f=n(a,l,i),f):y?(e.dependsOnOwnProps&&(a=e(s,i)),t.dependsOnOwnProps&&(l=t(r,i)),f=n(a,l,i),f):h?function(){const t=e(s,i),r=!c(t,a);return a=t,r&&(f=n(a,l,i)),f}():f}return function(o,u){return p?d(o,u):(s=o,i=u,a=e(s,i),l=t(r,i),f=n(a,l,i),p=!0,f)}}function Ie(e){return function(t){const n=e(t);function r(){return n}return r.dependsOnOwnProps=!1,r}}function Be(e){return e.dependsOnOwnProps?!!e.dependsOnOwnProps:1!==e.length}function Ge(e,t){return function(t,{}){const n=function(e,t){return n.mapToProps(e,n.dependsOnOwnProps?t:void 0)};return n.dependsOnOwnProps=!0,n.mapToProps=function(t,r){n.mapToProps=e,n.dependsOnOwnProps=Be(e);let o=n(t,r);return"function"==typeof o&&(n.mapToProps=o,n.dependsOnOwnProps=Be(o),o=n(t,r)),o},n}}function Je(e,t){return(n,r)=>{throw Error(`Invalid value of type ${typeof e} for ${t} argument when connecting component ${r.wrappedComponentName}.`)}}function Qe(e,t,n){return W({},n,e,t)}const Xe=(e,t)=>!1;function Ye(e){return function(e,t=D,n){return new N(e,t,n)}(null,Xe,e)}function Ze(e,t){!function(e,t){T(e instanceof N,"setValue must be passed a tracked store created with `createStorage`."),e.value=e._lastValue=t}(e,t)}const et=e=>{let t=e.collectionTag;var n;null===t&&(t=e.collectionTag=Ye((null==(n=e.collectionTag)?void 0:n._name)||"Unknown"));F(t)},tt=e=>{const t=e.collectionTag;null!==t&&Ze(t,null)};let nt=0;const rt=Object.getPrototypeOf({});class ot{constructor(e){this.value=e,this.proxy=new Proxy(this,ut),this.tag=Ye("object"),this.tags={},this.children={},this.collectionTag=null,this.id=nt++,this.value=e,this.tag.value=e}}const ut={get:(e,t)=>function(){const{value:n}=e,r=Reflect.get(n,t);if("symbol"==typeof t)return r;if(t in rt)return r;if("object"==typeof r&&null!==r){let n=e.children[t];return void 0===n&&(n=e.children[t]=it(r)),n.tag&&F(n.tag),n.proxy}{let n=e.tags[t];return void 0===n&&(n=e.tags[t]=Ye(t),n.value=r),F(n),r}}(),ownKeys:e=>(et(e),Reflect.ownKeys(e.value)),getOwnPropertyDescriptor:(e,t)=>Reflect.getOwnPropertyDescriptor(e.value,t),has:(e,t)=>Reflect.has(e.value,t)};class ct{constructor(e){this.value=e,this.proxy=new Proxy([this],st),this.tag=Ye("array"),this.tags={},this.children={},this.collectionTag=null,this.id=nt++,this.value=e,this.tag.value=e}}const st={get:([e],t)=>("length"===t&&et(e),ut.get(e,t)),ownKeys:([e])=>ut.ownKeys(e),getOwnPropertyDescriptor:([e],t)=>ut.getOwnPropertyDescriptor(e,t),has:([e],t)=>ut.has(e,t)};function it(e){return Array.isArray(e)?new ct(e):new ot(e)}function at(e,t){const{value:n,tags:r,children:o}=e;if(e.value=t,Array.isArray(n)&&Array.isArray(t)&&n.length!==t.length)tt(e);else if(n!==t){let r=0,o=0,u=!1;for(const e in n)r++;for(const e in t)if(o++,!(e in n)){u=!0;break}(u||r!==o)&&tt(e)}for(const o in r){const u=t[o];n[o]!==u&&(tt(e),Ze(r[o],u)),"object"==typeof u&&null!==u&&delete r[o]}for(const e in o){const n=o[e],r=t[e];n.value!==r&&("object"==typeof r&&null!==r?(console.log("Updating node key: ",e),at(n,r)):(lt(n),delete o[e]))}}function lt(e){e.tag&&Ze(e.tag,null),tt(e);for(const t in e.tags)Ze(e.tags[t],null);for(const t in e.children)lt(e.children[t])}const ft={notify(){},get:()=>[]};function pt(e,t,n){let r,o=ft;function u(){s.onStateChange&&s.onStateChange()}function c(n={trigger:"always"}){r||(r=t?t.addNestedSub(u,n):e.subscribe(u),o=function(){const e=O();let t=null,n=null;return{clear(){t=null,n=null},notify(){e((()=>{let e=t;for(;e;)"tracked"==e.trigger?e.selectorCache.cache.needsRecalculation()&&e.callback():e.callback(),e=e.next}))},get(){let e=[],n=t;for(;n;)e.push(n),n=n.next;return e},subscribe(e,r={trigger:"always"}){let o=!0,u=n={callback:e,next:null,prev:n,trigger:r.trigger,selectorCache:"tracked"===r.trigger?r.cache:void 0};return u.prev?u.prev.next=u:t=u,function(){o&&null!==t&&(o=!1,u.next?u.next.prev=u.prev:n=u.prev,u.prev?u.prev.next=u.next:t=u.next)}}}}())}const s={addNestedSub:function(e,t={trigger:"always"}){return c(t),o.subscribe(e,t)},notifyNestedSubs:function(){e&&n&&at(n,e.getState()),o.notify()},handleChangeWrapper:u,isSubscribed:function(){return!!r},trySubscribe:c,tryUnsubscribe:function(){r&&(r(),r=void 0,o.clear(),o=ft)},getListeners:()=>o};return s}function dt(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}function yt(e,t){if(dt(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;const n=Object.keys(e);if(n.length!==Object.keys(t).length)return!1;for(let r=0;n.length>r;r++)if(!Object.prototype.hasOwnProperty.call(t,n[r])||!dt(e[n[r]],t[n[r]]))return!1;return!0}const ht=["reactReduxForwardedRef"];let mt=_;const vt=[null,null];function gt(e,t,n,r,o,u){e.current=r,n.current=!1,o.current&&(o.current=null,u())}function bt(e,t){return e===t}function St(e=R){const t=e===R?M:E(e);return function(){const{store:e}=t();return e}}const wt=St();function Pt(e=R){const t=e===R?wt:St(e);return function(){return t().dispatch}}const xt=Pt();A=P.useSyncExternalStoreWithSelector,(e=>{mt=e})(y.useSyncExternalStore),x=n.unstable_batchedUpdates,Object.defineProperty(e,"batch",{enumerable:!0,get:function(){return n.unstable_batchedUpdates}}),e.Provider=function({store:e,context:n,children:r,serverState:u,stabilityCheck:c="once",noopCheck:s="once"}){const i=t.useMemo((()=>{const t=it(e.getState()),n=pt(e,void 0,t);return{store:e,subscription:n,getServerState:u?()=>u:void 0,stabilityCheck:c,noopCheck:s,trackingNode:t}}),[e,u,c,s]),a=t.useMemo((()=>e.getState()),[e]);return j((()=>{const{subscription:t}=i;return t.onStateChange=t.notifyNestedSubs,t.trySubscribe(),a!==e.getState()&&t.notifyNestedSubs(),()=>{t.tryUnsubscribe(),t.onStateChange=void 0}}),[i,a]),o.default.createElement((n||R).Provider,{value:i},r)},e.ReactReduxContext=R,e.connect=function(e,n,r,{areStatesEqual:u=bt,areOwnPropsEqual:c=yt,areStatePropsEqual:s=yt,areMergedPropsEqual:i=yt,forwardRef:a=!1,context:l=R}={}){const f=l,p=function(e){return e?"function"==typeof e?Ge(e):Je(e,"mapStateToProps"):Ie((()=>({})))}(e),d=function(e){return e&&"object"==typeof e?Ie((t=>function(e,t){const n={};for(const r in e){const o=e[r];"function"==typeof o&&(n[r]=(...e)=>t(o(...e)))}return n}(e,t))):e?"function"==typeof e?Ge(e):Je(e,"mapDispatchToProps"):Ie((e=>({dispatch:e})))}(n),y=function(e){return e?"function"==typeof e?function(e){return function(t,{areMergedPropsEqual:n}){let r,o=!1;return function(t,u,c){const s=e(t,u,c);return o?n(s,r)||(r=s):(o=!0,r=s),r}}}(e):Je(e,"mergeProps"):()=>Qe}(r),h=!!e;return e=>{const n=e.displayName||e.name||"Component",r=`Connect(${n})`,l={shouldHandleStateChanges:h,displayName:r,wrappedComponentName:n,WrappedComponent:e,initMapStateToProps:p,initMapDispatchToProps:d,initMergeProps:y,areStatesEqual:u,areStatePropsEqual:s,areOwnPropsEqual:c,areMergedPropsEqual:i};function m(n){const[r,u,c]=t.useMemo((()=>{const{reactReduxForwardedRef:e}=n,t=H(n,ht);return[n.context,e,t]}),[n]),s=t.useMemo((()=>r&&r.Consumer&&We.isContextConsumer(o.default.createElement(r.Consumer,null))?r:f),[r,f]),i=t.useContext(s),a=!!n.store&&!!n.store.getState&&!!n.store.dispatch,p=a?n.store:i.store,d=!!i&&!!i.store?i.getServerState:p.getState,y=t.useMemo((()=>function(e,t){let{initMapStateToProps:n,initMapDispatchToProps:r,initMergeProps:o}=t,u=H(t,He);return Ke(n(e,u),r(e,u),o(e,u),e,u)}(p.dispatch,l)),[p]),[m,v]=t.useMemo((()=>{if(!h)return vt;const e=pt(p,a?void 0:i.subscription),t=e.notifyNestedSubs.bind(e);return[e,t]}),[p,a,i]),g=t.useMemo((()=>a?i:W({},i,{subscription:m})),[a,i,m]),b=t.useRef(),S=t.useRef(c),w=t.useRef(),P=t.useRef(!1);t.useRef(!1);const x=t.useRef(!1),O=t.useRef();j((()=>(x.current=!0,()=>{x.current=!1})),[]);const $=t.useMemo((()=>()=>w.current&&c===S.current?w.current:y(p.getState(),c)),[p,c]),C=t.useMemo((()=>e=>m?function(e,t,n,r,o,u,c,s,i,a,l){if(!e)return()=>{};let f=!1,p=null;const d=()=>{if(f||!s.current)return;const e=t.getState();let n,d;try{n=r(e,o.current)}catch(e){d=e,p=e}d||(p=null),n===u.current?c.current||a():(u.current=n,i.current=n,c.current=!0,l())};return n.onStateChange=d,n.trySubscribe(),d(),()=>{if(f=!0,n.tryUnsubscribe(),n.onStateChange=null,p)throw p}}(h,p,m,y,S,b,P,x,w,v,e):()=>{}),[m]);var R,E,M;let _;R=gt,E=[S,b,P,c,w,v],j((()=>R(...E)),M);try{_=mt(C,$,d?()=>y(d(),c):$)}catch(e){throw O.current&&(e.message+=`\nThe error may be correlated with this previous error:\n${O.current.stack}\n\n`),e}j((()=>{O.current=void 0,w.current=void 0,b.current=_}));const T=t.useMemo((()=>o.default.createElement(e,W({},_,{ref:u}))),[u,e,_]);return t.useMemo((()=>h?o.default.createElement(s.Provider,{value:g},T):T),[s,T,g])}const v=o.default.memo(m);if(v.WrappedComponent=e,v.displayName=m.displayName=r,a){const t=o.default.forwardRef((function(e,t){return o.default.createElement(v,W({},e,{reactReduxForwardedRef:t}))}));return t.displayName=r,t.WrappedComponent=e,$e(t,e)}return $e(v,e)}},e.createDispatchHook=Pt,e.createSelectorHook=z,e.createStoreHook=St,e.shallowEqual=yt,e.useDispatch=xt,e.useSelector=U,e.useStore=wt,Object.defineProperty(e,"__esModule",{value:!0})})); + */function ze(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case Ce:switch(e=e.type){case Ee:case _e:case Me:case Ne:case De:return e;default:switch(e=e&&e.$$typeof){case ke:case Te:case qe:case Fe:case Ve:case je:return e;default:return t}}case Re:return t}}}var Ue={ContextConsumer:Te,ContextProvider:je,Element:Ce,ForwardRef:qe,Fragment:Ee,Lazy:Fe,Memo:Ve,Portal:Re,Profiler:_e,StrictMode:Me,Suspense:Ne,SuspenseList:De,isAsyncMode:function(){return!1},isConcurrentMode:function(){return!1},isContextConsumer:function(e){return ze(e)===Te},isContextProvider:function(e){return ze(e)===je},isElement:function(e){return"object"==typeof e&&null!==e&&e.$$typeof===Ce},isForwardRef:function(e){return ze(e)===qe},isFragment:function(e){return ze(e)===Ee},isLazy:function(e){return ze(e)===Fe},isMemo:function(e){return ze(e)===Ve},isPortal:function(e){return ze(e)===Re},isProfiler:function(e){return ze(e)===_e},isStrictMode:function(e){return ze(e)===Me},isSuspense:function(e){return ze(e)===Ne},isSuspenseList:function(e){return ze(e)===De},isValidElementType:function(e){return"string"==typeof e||"function"==typeof e||e===Ee||e===_e||e===Me||e===Ne||e===De||e===Ae||"object"==typeof e&&null!==e&&(e.$$typeof===Fe||e.$$typeof===Ve||e.$$typeof===je||e.$$typeof===Te||e.$$typeof===qe||e.$$typeof===Le||void 0!==e.getModuleId)},typeOf:ze},We=u((function(e){e.exports=Ue}));const He=["initMapStateToProps","initMapDispatchToProps","initMergeProps"];function Ke(e,t,n,r,{areStatesEqual:o,areOwnPropsEqual:u,areStatePropsEqual:c}){let s,i,a,l,f,p=!1;function d(p,d){const y=!u(d,i),h=!o(p,s,d,i);return s=p,i=d,y&&h?(a=e(s,i),t.dependsOnOwnProps&&(l=t(r,i)),f=n(a,l,i),f):y?(e.dependsOnOwnProps&&(a=e(s,i)),t.dependsOnOwnProps&&(l=t(r,i)),f=n(a,l,i),f):h?function(){const t=e(s,i),r=!c(t,a);return a=t,r&&(f=n(a,l,i)),f}():f}return function(o,u){return p?d(o,u):(s=o,i=u,a=e(s,i),l=t(r,i),f=n(a,l,i),p=!0,f)}}function Ie(e){return function(t){const n=e(t);function r(){return n}return r.dependsOnOwnProps=!1,r}}function Be(e){return e.dependsOnOwnProps?!!e.dependsOnOwnProps:1!==e.length}function Ge(e,t){return function(t,{}){const n=function(e,t){return n.mapToProps(e,n.dependsOnOwnProps?t:void 0)};return n.dependsOnOwnProps=!0,n.mapToProps=function(t,r){n.mapToProps=e,n.dependsOnOwnProps=Be(e);let o=n(t,r);return"function"==typeof o&&(n.mapToProps=o,n.dependsOnOwnProps=Be(o),o=n(t,r)),o},n}}function Je(e,t){return(n,r)=>{throw Error(`Invalid value of type ${typeof e} for ${t} argument when connecting component ${r.wrappedComponentName}.`)}}function Qe(e,t,n){return W({},n,e,t)}const Xe=(e,t)=>!1;function Ye(e){return function(e,t=D,n){return new N(e,t,n)}(null,Xe,e)}function Ze(e,t){!function(e,t){T(e instanceof N,"setValue must be passed a tracked store created with `createStorage`."),e.value=e._lastValue=t}(e,t)}const et=e=>{let t=e.collectionTag;var n;null===t&&(t=e.collectionTag=Ye((null==(n=e.collectionTag)?void 0:n._name)||"Unknown"));F(t)},tt=e=>{const t=e.collectionTag;null!==t&&Ze(t,null)};let nt=0;const rt=Object.getPrototypeOf({});class ot{constructor(e){this.value=e,this.proxy=new Proxy(this,ut),this.tag=Ye("object"),this.tags={},this.children={},this.collectionTag=null,this.id=nt++,this.value=e,this.tag.value=e}}const ut={get:(e,t)=>function(){const{value:n}=e,r=Reflect.get(n,t);if("symbol"==typeof t)return r;if(t in rt)return r;if("object"==typeof r&&null!==r){let n=e.children[t];return void 0===n&&(n=e.children[t]=it(r)),n.tag&&F(n.tag),n.proxy}{let n=e.tags[t];return void 0===n&&(n=e.tags[t]=Ye(t),n.value=r),F(n),r}}(),ownKeys:e=>(et(e),Reflect.ownKeys(e.value)),getOwnPropertyDescriptor:(e,t)=>Reflect.getOwnPropertyDescriptor(e.value,t),has:(e,t)=>Reflect.has(e.value,t)};class ct{constructor(e){this.value=e,this.proxy=new Proxy([this],st),this.tag=Ye("array"),this.tags={},this.children={},this.collectionTag=null,this.id=nt++,this.value=e,this.tag.value=e}}const st={get:([e],t)=>("length"===t&&et(e),ut.get(e,t)),ownKeys:([e])=>ut.ownKeys(e),getOwnPropertyDescriptor:([e],t)=>ut.getOwnPropertyDescriptor(e,t),has:([e],t)=>ut.has(e,t)};function it(e){return Array.isArray(e)?new ct(e):new ot(e)}function at(e,t){const{value:n,tags:r,children:o}=e;if(e.value=t,Array.isArray(n)&&Array.isArray(t)&&n.length!==t.length)tt(e);else if(n!==t){let r=0,o=0,u=!1;for(const e in n)r++;for(const e in t)if(o++,!(e in n)){u=!0;break}(u||r!==o)&&tt(e)}for(const o in r){const u=t[o];n[o]!==u&&(tt(e),Ze(r[o],u)),"object"==typeof u&&null!==u&&delete r[o]}for(const e in o){const n=o[e],r=t[e];n.value!==r&&("object"==typeof r&&null!==r?at(n,r):(lt(n),delete o[e]))}}function lt(e){e.tag&&Ze(e.tag,null),tt(e);for(const t in e.tags)Ze(e.tags[t],null);for(const t in e.children)lt(e.children[t])}const ft={notify(){},get:()=>[]};function pt(e,t,n){let r,o=ft;function u(){s.onStateChange&&s.onStateChange()}function c(n={trigger:"always"}){r||(r=t?t.addNestedSub(u,n):e.subscribe(u),o=function(){const e=O();let t=null,n=null;return{clear(){t=null,n=null},notify(){let n=0,r=0;return e((()=>{let e=t;for(;e;)"tracked"==e.trigger?e.selectorCache.cache.needsRecalculation()?(n++,e.callback()):r++:e.callback(),e=e.next})),{numCalled:n,numSkipped:r}},get(){let e=[],n=t;for(;n;)e.push(n),n=n.next;return e},subscribe(e,r={trigger:"always"}){let o=!0,u=n={callback:e,next:null,prev:n,trigger:r.trigger,selectorCache:"tracked"===r.trigger?r.cache:void 0};return u.prev?u.prev.next=u:t=u,function(){o&&null!==t&&(o=!1,u.next?u.next.prev=u.prev:n=u.prev,u.prev?u.prev.next=u.next:t=u.next)}}}}())}const s={addNestedSub:function(e,t={trigger:"always"}){return c(t),o.subscribe(e,t)},notifyNestedSubs:function(){e&&n&&(performance.now(),at(n,e.getState()),performance.now()),performance.now(),o.notify(),performance.now()},handleChangeWrapper:u,isSubscribed:function(){return!!r},trySubscribe:c,tryUnsubscribe:function(){r&&(r(),r=void 0,o.clear(),o=ft)},getListeners:()=>o};return s}function dt(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}function yt(e,t){if(dt(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;const n=Object.keys(e);if(n.length!==Object.keys(t).length)return!1;for(let r=0;n.length>r;r++)if(!Object.prototype.hasOwnProperty.call(t,n[r])||!dt(e[n[r]],t[n[r]]))return!1;return!0}const ht=["reactReduxForwardedRef"];let mt=_;const vt=[null,null];function bt(e,t,n,r,o,u){e.current=r,n.current=!1,o.current&&(o.current=null,u())}function gt(e,t){return e===t}function St(e=R){const t=e===R?M:E(e);return function(){const{store:e}=t();return e}}const wt=St();function Pt(e=R){const t=e===R?wt:St(e);return function(){return t().dispatch}}const xt=Pt();A=P.useSyncExternalStoreWithSelector,(e=>{mt=e})(y.useSyncExternalStore),x=n.unstable_batchedUpdates,Object.defineProperty(e,"batch",{enumerable:!0,get:function(){return n.unstable_batchedUpdates}}),e.Provider=function({store:e,context:n,children:r,serverState:u,stabilityCheck:c="once",noopCheck:s="once"}){const i=t.useMemo((()=>{const t=it(e.getState()),n=pt(e,void 0,t);return{store:e,subscription:n,getServerState:u?()=>u:void 0,stabilityCheck:c,noopCheck:s,trackingNode:t}}),[e,u,c,s]),a=t.useMemo((()=>e.getState()),[e]);return j((()=>{const{subscription:t}=i;return t.onStateChange=t.notifyNestedSubs,t.trySubscribe(),a!==e.getState()&&t.notifyNestedSubs(),()=>{t.tryUnsubscribe(),t.onStateChange=void 0}}),[i,a]),o.default.createElement((n||R).Provider,{value:i},r)},e.ReactReduxContext=R,e.connect=function(e,n,r,{areStatesEqual:u=gt,areOwnPropsEqual:c=yt,areStatePropsEqual:s=yt,areMergedPropsEqual:i=yt,forwardRef:a=!1,context:l=R}={}){const f=l,p=function(e){return e?"function"==typeof e?Ge(e):Je(e,"mapStateToProps"):Ie((()=>({})))}(e),d=function(e){return e&&"object"==typeof e?Ie((t=>function(e,t){const n={};for(const r in e){const o=e[r];"function"==typeof o&&(n[r]=(...e)=>t(o(...e)))}return n}(e,t))):e?"function"==typeof e?Ge(e):Je(e,"mapDispatchToProps"):Ie((e=>({dispatch:e})))}(n),y=function(e){return e?"function"==typeof e?function(e){return function(t,{areMergedPropsEqual:n}){let r,o=!1;return function(t,u,c){const s=e(t,u,c);return o?n(s,r)||(r=s):(o=!0,r=s),r}}}(e):Je(e,"mergeProps"):()=>Qe}(r),h=!!e;return e=>{const n=e.displayName||e.name||"Component",r=`Connect(${n})`,l={shouldHandleStateChanges:h,displayName:r,wrappedComponentName:n,WrappedComponent:e,initMapStateToProps:p,initMapDispatchToProps:d,initMergeProps:y,areStatesEqual:u,areStatePropsEqual:s,areOwnPropsEqual:c,areMergedPropsEqual:i};function m(n){const[r,u,c]=t.useMemo((()=>{const{reactReduxForwardedRef:e}=n,t=H(n,ht);return[n.context,e,t]}),[n]),s=t.useMemo((()=>r&&r.Consumer&&We.isContextConsumer(o.default.createElement(r.Consumer,null))?r:f),[r,f]),i=t.useContext(s),a=!!n.store&&!!n.store.getState&&!!n.store.dispatch,p=a?n.store:i.store,d=!!i&&!!i.store?i.getServerState:p.getState,y=t.useMemo((()=>function(e,t){let{initMapStateToProps:n,initMapDispatchToProps:r,initMergeProps:o}=t,u=H(t,He);return Ke(n(e,u),r(e,u),o(e,u),e,u)}(p.dispatch,l)),[p]),[m,v]=t.useMemo((()=>{if(!h)return vt;const e=pt(p,a?void 0:i.subscription),t=e.notifyNestedSubs.bind(e);return[e,t]}),[p,a,i]),b=t.useMemo((()=>a?i:W({},i,{subscription:m})),[a,i,m]),g=t.useRef(),S=t.useRef(c),w=t.useRef(),P=t.useRef(!1);t.useRef(!1);const x=t.useRef(!1),O=t.useRef();j((()=>(x.current=!0,()=>{x.current=!1})),[]);const $=t.useMemo((()=>()=>w.current&&c===S.current?w.current:y(p.getState(),c)),[p,c]),C=t.useMemo((()=>e=>m?function(e,t,n,r,o,u,c,s,i,a,l){if(!e)return()=>{};let f=!1,p=null;const d=()=>{if(f||!s.current)return;const e=t.getState();let n,d;try{n=r(e,o.current)}catch(e){d=e,p=e}d||(p=null),n===u.current?c.current||a():(u.current=n,i.current=n,c.current=!0,l())};return n.onStateChange=d,n.trySubscribe(),d(),()=>{if(f=!0,n.tryUnsubscribe(),n.onStateChange=null,p)throw p}}(h,p,m,y,S,g,P,x,w,v,e):()=>{}),[m]);var R,E,M;let _;R=bt,E=[S,g,P,c,w,v],j((()=>R(...E)),M);try{_=mt(C,$,d?()=>y(d(),c):$)}catch(e){throw O.current&&(e.message+=`\nThe error may be correlated with this previous error:\n${O.current.stack}\n\n`),e}j((()=>{O.current=void 0,w.current=void 0,g.current=_}));const T=t.useMemo((()=>o.default.createElement(e,W({},_,{ref:u}))),[u,e,_]);return t.useMemo((()=>h?o.default.createElement(s.Provider,{value:b},T):T),[s,T,b])}const v=o.default.memo(m);if(v.WrappedComponent=e,v.displayName=m.displayName=r,a){const t=o.default.forwardRef((function(e,t){return o.default.createElement(v,W({},e,{reactReduxForwardedRef:t}))}));return t.displayName=r,t.WrappedComponent=e,$e(t,e)}return $e(v,e)}},e.createDispatchHook=Pt,e.createSelectorHook=z,e.createStoreHook=St,e.shallowEqual=yt,e.useDispatch=xt,e.useSelector=U,e.useStore=wt,Object.defineProperty(e,"__esModule",{value:!0})})); diff --git a/.yalc/react-redux/es/components/connect.js b/.yalc/react-redux/es/components/connect.js old mode 100644 new mode 100755 index 4e98525..812a57c --- a/.yalc/react-redux/es/components/connect.js +++ b/.yalc/react-redux/es/components/connect.js @@ -138,31 +138,31 @@ const initStateUpdates = () => EMPTY_ARRAY; function strictEqual(a, b) { return a === b; } -/** - * Infers the type of props that a connector will inject into a component. +/** + * 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 - * +/** + * 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, { diff --git a/.yalc/react-redux/es/hooks/useDispatch.js b/.yalc/react-redux/es/hooks/useDispatch.js old mode 100644 new mode 100755 index 707338f..6aceef9 --- a/.yalc/react-redux/es/hooks/useDispatch.js +++ b/.yalc/react-redux/es/hooks/useDispatch.js @@ -1,10 +1,10 @@ import { ReactReduxContext } from '../components/Context'; import { useStore as useDefaultStore, createStoreHook } from './useStore'; -/** - * Hook factory, which creates a `useDispatch` hook bound to a given context. - * - * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. - * @returns {Function} A `useDispatch` hook bound to the specified context. +/** + * Hook factory, which creates a `useDispatch` hook bound to a given context. + * + * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. + * @returns {Function} A `useDispatch` hook bound to the specified context. */ export function createDispatchHook(context = ReactReduxContext) { @@ -16,26 +16,26 @@ export function createDispatchHook(context = ReactReduxContext) { return store.dispatch; }; } -/** - * A hook to access the redux `dispatch` function. - * - * @returns {any|function} redux store's `dispatch` function - * - * @example - * - * import React, { useCallback } from 'react' - * import { useDispatch } from 'react-redux' - * - * export const CounterComponent = ({ value }) => { - * const dispatch = useDispatch() - * const increaseCounter = useCallback(() => dispatch({ type: 'increase-counter' }), []) - * return ( - *
- * {value} - * - *
- * ) - * } +/** + * A hook to access the redux `dispatch` function. + * + * @returns {any|function} redux store's `dispatch` function + * + * @example + * + * import React, { useCallback } from 'react' + * import { useDispatch } from 'react-redux' + * + * export const CounterComponent = ({ value }) => { + * const dispatch = useDispatch() + * const increaseCounter = useCallback(() => dispatch({ type: 'increase-counter' }), []) + * return ( + *
+ * {value} + * + *
+ * ) + * } */ export const useDispatch = /*#__PURE__*/createDispatchHook(); \ No newline at end of file diff --git a/.yalc/react-redux/es/hooks/useReduxContext.js b/.yalc/react-redux/es/hooks/useReduxContext.js old mode 100644 new mode 100755 index 770430b..29b0ac2 --- a/.yalc/react-redux/es/hooks/useReduxContext.js +++ b/.yalc/react-redux/es/hooks/useReduxContext.js @@ -1,12 +1,12 @@ import { useContext } from 'react'; import { ReactReduxContext } from '../components/Context'; -/** - * Hook factory, which creates a `useReduxContext` hook bound to a given context. This is a low-level - * hook that you should usually not need to call directly. - * - * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. - * @returns {Function} A `useReduxContext` hook bound to the specified context. +/** + * Hook factory, which creates a `useReduxContext` hook bound to a given context. This is a low-level + * hook that you should usually not need to call directly. + * + * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. + * @returns {Function} A `useReduxContext` hook bound to the specified context. */ export function createReduxContextHook(context = ReactReduxContext) { return function useReduxContext() { @@ -19,21 +19,21 @@ export function createReduxContextHook(context = ReactReduxContext) { return contextValue; }; } -/** - * A hook to access the value of the `ReactReduxContext`. This is a low-level - * hook that you should usually not need to call directly. - * - * @returns {any} the value of the `ReactReduxContext` - * - * @example - * - * import React from 'react' - * import { useReduxContext } from 'react-redux' - * - * export const CounterComponent = () => { - * const { store } = useReduxContext() - * return
{store.getState()}
- * } +/** + * A hook to access the value of the `ReactReduxContext`. This is a low-level + * hook that you should usually not need to call directly. + * + * @returns {any} the value of the `ReactReduxContext` + * + * @example + * + * import React from 'react' + * import { useReduxContext } from 'react-redux' + * + * export const CounterComponent = () => { + * const { store } = useReduxContext() + * return
{store.getState()}
+ * } */ export const useReduxContext = /*#__PURE__*/createReduxContextHook(); \ No newline at end of file diff --git a/.yalc/react-redux/es/hooks/useSelector.js b/.yalc/react-redux/es/hooks/useSelector.js old mode 100644 new mode 100755 index 92742e5..eafc4a4 --- a/.yalc/react-redux/es/hooks/useSelector.js +++ b/.yalc/react-redux/es/hooks/useSelector.js @@ -10,11 +10,11 @@ export const initializeUseSelector = fn => { }; const refEquality = (a, b) => a === b; -/** - * Hook factory, which creates a `useSelector` hook bound to a given context. - * - * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. - * @returns {Function} A `useSelector` hook bound to the specified context. +/** + * Hook factory, which creates a `useSelector` hook bound to a given context. + * + * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. + * @returns {Function} A `useSelector` hook bound to the specified context. */ @@ -131,28 +131,28 @@ export function createSelectorHook(context = ReactReduxContext) { 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
{counter}
- * } +/** + * 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
{counter}
+ * } */ export const useSelector = /*#__PURE__*/createSelectorHook(); \ No newline at end of file diff --git a/.yalc/react-redux/es/hooks/useStore.js b/.yalc/react-redux/es/hooks/useStore.js old mode 100644 new mode 100755 index 6a5eaf2..0ed8021 --- a/.yalc/react-redux/es/hooks/useStore.js +++ b/.yalc/react-redux/es/hooks/useStore.js @@ -1,10 +1,10 @@ import { ReactReduxContext } from '../components/Context'; import { useReduxContext as useDefaultReduxContext, createReduxContextHook } from './useReduxContext'; -/** - * Hook factory, which creates a `useStore` hook bound to a given context. - * - * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. - * @returns {Function} A `useStore` hook bound to the specified context. +/** + * Hook factory, which creates a `useStore` hook bound to a given context. + * + * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. + * @returns {Function} A `useStore` hook bound to the specified context. */ export function createStoreHook(context = ReactReduxContext) { @@ -19,20 +19,20 @@ export function createStoreHook(context = ReactReduxContext) { return store; }; } -/** - * A hook to access the redux store. - * - * @returns {any} the redux store - * - * @example - * - * import React from 'react' - * import { useStore } from 'react-redux' - * - * export const ExampleComponent = () => { - * const store = useStore() - * return
{store.getState()}
- * } +/** + * A hook to access the redux store. + * + * @returns {any} the redux store + * + * @example + * + * import React from 'react' + * import { useStore } from 'react-redux' + * + * export const ExampleComponent = () => { + * const store = useStore() + * return
{store.getState()}
+ * } */ export const useStore = /*#__PURE__*/createStoreHook(); \ No newline at end of file diff --git a/.yalc/react-redux/es/utils/Subscription.d.ts b/.yalc/react-redux/es/utils/Subscription.d.ts old mode 100644 new mode 100755 index 6bbf094..5a21ea7 --- a/.yalc/react-redux/es/utils/Subscription.d.ts +++ b/.yalc/react-redux/es/utils/Subscription.d.ts @@ -14,7 +14,10 @@ declare type Listener = { }; declare function createListenerCollection(): { clear(): void; - notify(): void; + notify(): { + numCalled: number; + numSkipped: number; + }; get(): Listener[]; subscribe(callback: () => void, options?: AddNestedSubOptions): () => void; }; diff --git a/.yalc/react-redux/es/utils/Subscription.js b/.yalc/react-redux/es/utils/Subscription.js old mode 100644 new mode 100755 index beb2e3f..d9bf86a --- a/.yalc/react-redux/es/utils/Subscription.js +++ b/.yalc/react-redux/es/utils/Subscription.js @@ -15,6 +15,8 @@ function createListenerCollection() { notify() { //console.log('Notifying subscribers') + let numCalled = 0; + let numSkipped = 0; batch(() => { let listener = first; @@ -27,8 +29,10 @@ function createListenerCollection() { // 'Calling subscriber due to recalc. Revision before: ', // $REVISION // ) + numCalled++; listener.callback(); //console.log('Revision after: ', $REVISION) - } else {// console.log( + } else { + numSkipped++; // console.log( // 'Skipping subscriber, no recalc: ', // listener.selectorCache // ) @@ -40,6 +44,11 @@ function createListenerCollection() { listener = listener.next; } }); + const result = { + numCalled, + numSkipped + }; + return result; }, get() { @@ -110,6 +119,9 @@ const nullListeners = { export function createSubscription(store, parentSub, trackingNode) { let unsubscribe; let listeners = nullListeners; + const updateNodeTimes = []; + const notifyTimes = []; + const resultCounts = []; function addNestedSub(listener, options = { trigger: 'always' @@ -122,10 +134,28 @@ export function createSubscription(store, parentSub, trackingNode) { function notifyNestedSubs() { if (store && trackingNode) { //console.log('Updating node in notifyNestedSubs') + const _start = performance.now(); + updateNode(trackingNode, store.getState()); + + const _end = performance.now(); + + updateNodeTimes.push({ + start: _start, + end: _end, + duration: _end - _start + }); } - listeners.notify(); + const start = performance.now(); + const results = listeners.notify(); + const end = performance.now(); + notifyTimes.push({ + start, + end, + duration: end - start + }); + resultCounts.push(results); } function handleChangeWrapper() { diff --git a/.yalc/react-redux/es/utils/autotracking/autotracking.js b/.yalc/react-redux/es/utils/autotracking/autotracking.js old mode 100644 new mode 100755 index fc04520..d912efc --- a/.yalc/react-redux/es/utils/autotracking/autotracking.js +++ b/.yalc/react-redux/es/utils/autotracking/autotracking.js @@ -91,42 +91,42 @@ export class TrackingCache { return this._needsRecalculation; } - /* - getWithArgs = (...args: any[]) => { - // console.log( - // `TrackingCache value: revision = ${this.revision}, cachedRevision = ${this._cachedRevision}, value = ${this._cachedValue}` - // ) - // When getting the value for a Cache, first we check all the dependencies of - // the cache to see what their current revision is. If the current revision is - // greater than the cached revision, then something has changed. - //if (this.revision > this._cachedRevision) { - if (this.needsRecalculation()) { - const { fn } = this - // We create a new dependency tracker for this cache. As the cache runs - // its function, any Storage or Cache instances which are used while - // computing will be added to this tracker. In the end, it will be the - // full list of dependencies that this Cache depends on. - const currentTracker = new Set>() - const prevTracker = CURRENT_TRACKER - CURRENT_TRACKER = currentTracker - // try { - this._cachedValue = fn.apply(null, args) - // } finally { - CURRENT_TRACKER = prevTracker - this.hits++ - this._deps = Array.from(currentTracker) - // Set the cached revision. This is the current clock count of all the - // dependencies. If any dependency changes, this number will be less - // than the new revision. - this._cachedRevision = this.revision - // } - } - // If there is a current tracker, it means another Cache is computing and - // using this one, so we add this one to the tracker. - CURRENT_TRACKER?.add(this) - // Always return the cached value. - return this._cachedValue - } + /* + getWithArgs = (...args: any[]) => { + // console.log( + // `TrackingCache value: revision = ${this.revision}, cachedRevision = ${this._cachedRevision}, value = ${this._cachedValue}` + // ) + // When getting the value for a Cache, first we check all the dependencies of + // the cache to see what their current revision is. If the current revision is + // greater than the cached revision, then something has changed. + //if (this.revision > this._cachedRevision) { + if (this.needsRecalculation()) { + const { fn } = this + // We create a new dependency tracker for this cache. As the cache runs + // its function, any Storage or Cache instances which are used while + // computing will be added to this tracker. In the end, it will be the + // full list of dependencies that this Cache depends on. + const currentTracker = new Set>() + const prevTracker = CURRENT_TRACKER + CURRENT_TRACKER = currentTracker + // try { + this._cachedValue = fn.apply(null, args) + // } finally { + CURRENT_TRACKER = prevTracker + this.hits++ + this._deps = Array.from(currentTracker) + // Set the cached revision. This is the current clock count of all the + // dependencies. If any dependency changes, this number will be less + // than the new revision. + this._cachedRevision = this.revision + // } + } + // If there is a current tracker, it means another Cache is computing and + // using this one, so we add this one to the tracker. + CURRENT_TRACKER?.add(this) + // Always return the cached value. + return this._cachedValue + } */ diff --git a/.yalc/react-redux/es/utils/autotracking/proxy.js b/.yalc/react-redux/es/utils/autotracking/proxy.js old mode 100644 new mode 100755 index c455ee9..0a4fdd2 --- a/.yalc/react-redux/es/utils/autotracking/proxy.js +++ b/.yalc/react-redux/es/utils/autotracking/proxy.js @@ -188,7 +188,7 @@ export function updateNode(node, newValue) { if (childValue === newChildValue) { continue; } else if (typeof newChildValue === 'object' && newChildValue !== null) { - console.log('Updating node key: ', key); + // console.log('Updating node key: ', key) updateNode(childNode, newChildValue); } else { deleteNode(childNode); diff --git a/.yalc/react-redux/es/utils/isPlainObject.js b/.yalc/react-redux/es/utils/isPlainObject.js old mode 100644 new mode 100755 index 192032d..b7f563b --- a/.yalc/react-redux/es/utils/isPlainObject.js +++ b/.yalc/react-redux/es/utils/isPlainObject.js @@ -1,6 +1,6 @@ -/** - * @param {any} obj The object to inspect. - * @returns {boolean} True if the argument appears to be a plain object. +/** + * @param {any} obj The object to inspect. + * @returns {boolean} True if the argument appears to be a plain object. */ export default function isPlainObject(obj) { if (typeof obj !== 'object' || obj === null) return false; diff --git a/.yalc/react-redux/es/utils/warning.js b/.yalc/react-redux/es/utils/warning.js old mode 100644 new mode 100755 index 4b540f5..ea8d7f9 --- a/.yalc/react-redux/es/utils/warning.js +++ b/.yalc/react-redux/es/utils/warning.js @@ -1,8 +1,8 @@ -/** - * Prints a warning in the console if it exists. - * - * @param {String} message The warning message. - * @returns {void} +/** + * Prints a warning in the console if it exists. + * + * @param {String} message The warning message. + * @returns {void} */ export default function warning(message) { /* eslint-disable no-console */ diff --git a/.yalc/react-redux/lib/components/connect.js b/.yalc/react-redux/lib/components/connect.js old mode 100644 new mode 100755 index 6ed2ce6..85fb3cf --- a/.yalc/react-redux/lib/components/connect.js +++ b/.yalc/react-redux/lib/components/connect.js @@ -166,31 +166,31 @@ const initStateUpdates = () => EMPTY_ARRAY; function strictEqual(a, b) { return a === b; } -/** - * Infers the type of props that a connector will inject into a component. +/** + * 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 - * +/** + * 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, { diff --git a/.yalc/react-redux/lib/hooks/useDispatch.js b/.yalc/react-redux/lib/hooks/useDispatch.js old mode 100644 new mode 100755 index 57ce8df..c8e772f --- a/.yalc/react-redux/lib/hooks/useDispatch.js +++ b/.yalc/react-redux/lib/hooks/useDispatch.js @@ -8,11 +8,11 @@ var _Context = require("../components/Context"); var _useStore = require("./useStore"); -/** - * Hook factory, which creates a `useDispatch` hook bound to a given context. - * - * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. - * @returns {Function} A `useDispatch` hook bound to the specified context. +/** + * Hook factory, which creates a `useDispatch` hook bound to a given context. + * + * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. + * @returns {Function} A `useDispatch` hook bound to the specified context. */ function createDispatchHook(context = _Context.ReactReduxContext) { const useStore = // @ts-ignore @@ -23,26 +23,26 @@ function createDispatchHook(context = _Context.ReactReduxContext) { return store.dispatch; }; } -/** - * A hook to access the redux `dispatch` function. - * - * @returns {any|function} redux store's `dispatch` function - * - * @example - * - * import React, { useCallback } from 'react' - * import { useDispatch } from 'react-redux' - * - * export const CounterComponent = ({ value }) => { - * const dispatch = useDispatch() - * const increaseCounter = useCallback(() => dispatch({ type: 'increase-counter' }), []) - * return ( - *
- * {value} - * - *
- * ) - * } +/** + * A hook to access the redux `dispatch` function. + * + * @returns {any|function} redux store's `dispatch` function + * + * @example + * + * import React, { useCallback } from 'react' + * import { useDispatch } from 'react-redux' + * + * export const CounterComponent = ({ value }) => { + * const dispatch = useDispatch() + * const increaseCounter = useCallback(() => dispatch({ type: 'increase-counter' }), []) + * return ( + *
+ * {value} + * + *
+ * ) + * } */ diff --git a/.yalc/react-redux/lib/hooks/useReduxContext.js b/.yalc/react-redux/lib/hooks/useReduxContext.js old mode 100644 new mode 100755 index 671ed8e..c30ee29 --- a/.yalc/react-redux/lib/hooks/useReduxContext.js +++ b/.yalc/react-redux/lib/hooks/useReduxContext.js @@ -8,12 +8,12 @@ var _react = require("react"); var _Context = require("../components/Context"); -/** - * Hook factory, which creates a `useReduxContext` hook bound to a given context. This is a low-level - * hook that you should usually not need to call directly. - * - * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. - * @returns {Function} A `useReduxContext` hook bound to the specified context. +/** + * Hook factory, which creates a `useReduxContext` hook bound to a given context. This is a low-level + * hook that you should usually not need to call directly. + * + * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. + * @returns {Function} A `useReduxContext` hook bound to the specified context. */ function createReduxContextHook(context = _Context.ReactReduxContext) { return function useReduxContext() { @@ -26,21 +26,21 @@ function createReduxContextHook(context = _Context.ReactReduxContext) { return contextValue; }; } -/** - * A hook to access the value of the `ReactReduxContext`. This is a low-level - * hook that you should usually not need to call directly. - * - * @returns {any} the value of the `ReactReduxContext` - * - * @example - * - * import React from 'react' - * import { useReduxContext } from 'react-redux' - * - * export const CounterComponent = () => { - * const { store } = useReduxContext() - * return
{store.getState()}
- * } +/** + * A hook to access the value of the `ReactReduxContext`. This is a low-level + * hook that you should usually not need to call directly. + * + * @returns {any} the value of the `ReactReduxContext` + * + * @example + * + * import React from 'react' + * import { useReduxContext } from 'react-redux' + * + * export const CounterComponent = () => { + * const { store } = useReduxContext() + * return
{store.getState()}
+ * } */ diff --git a/.yalc/react-redux/lib/hooks/useSelector.js b/.yalc/react-redux/lib/hooks/useSelector.js old mode 100644 new mode 100755 index ef2387e..bfd043b --- a/.yalc/react-redux/lib/hooks/useSelector.js +++ b/.yalc/react-redux/lib/hooks/useSelector.js @@ -25,11 +25,11 @@ const initializeUseSelector = fn => { exports.initializeUseSelector = initializeUseSelector; const refEquality = (a, b) => a === b; -/** - * Hook factory, which creates a `useSelector` hook bound to a given context. - * - * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. - * @returns {Function} A `useSelector` hook bound to the specified context. +/** + * Hook factory, which creates a `useSelector` hook bound to a given context. + * + * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. + * @returns {Function} A `useSelector` hook bound to the specified context. */ @@ -146,28 +146,28 @@ function createSelectorHook(context = _Context.ReactReduxContext) { 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
{counter}
- * } +/** + * 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
{counter}
+ * } */ diff --git a/.yalc/react-redux/lib/hooks/useStore.js b/.yalc/react-redux/lib/hooks/useStore.js old mode 100644 new mode 100755 index 30323c1..43622ed --- a/.yalc/react-redux/lib/hooks/useStore.js +++ b/.yalc/react-redux/lib/hooks/useStore.js @@ -8,11 +8,11 @@ var _Context = require("../components/Context"); var _useReduxContext = require("./useReduxContext"); -/** - * Hook factory, which creates a `useStore` hook bound to a given context. - * - * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. - * @returns {Function} A `useStore` hook bound to the specified context. +/** + * Hook factory, which creates a `useStore` hook bound to a given context. + * + * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. + * @returns {Function} A `useStore` hook bound to the specified context. */ function createStoreHook(context = _Context.ReactReduxContext) { const useReduxContext = // @ts-ignore @@ -26,20 +26,20 @@ function createStoreHook(context = _Context.ReactReduxContext) { return store; }; } -/** - * A hook to access the redux store. - * - * @returns {any} the redux store - * - * @example - * - * import React from 'react' - * import { useStore } from 'react-redux' - * - * export const ExampleComponent = () => { - * const store = useStore() - * return
{store.getState()}
- * } +/** + * A hook to access the redux store. + * + * @returns {any} the redux store + * + * @example + * + * import React from 'react' + * import { useStore } from 'react-redux' + * + * export const ExampleComponent = () => { + * const store = useStore() + * return
{store.getState()}
+ * } */ diff --git a/.yalc/react-redux/lib/utils/Subscription.js b/.yalc/react-redux/lib/utils/Subscription.js old mode 100644 new mode 100755 index 5ceb067..d72eb57 --- a/.yalc/react-redux/lib/utils/Subscription.js +++ b/.yalc/react-redux/lib/utils/Subscription.js @@ -19,6 +19,8 @@ function createListenerCollection() { notify() { //console.log('Notifying subscribers') + let numCalled = 0; + let numSkipped = 0; batch(() => { let listener = first; @@ -31,8 +33,10 @@ function createListenerCollection() { // 'Calling subscriber due to recalc. Revision before: ', // $REVISION // ) + numCalled++; listener.callback(); //console.log('Revision after: ', $REVISION) - } else {// console.log( + } else { + numSkipped++; // console.log( // 'Skipping subscriber, no recalc: ', // listener.selectorCache // ) @@ -44,6 +48,11 @@ function createListenerCollection() { listener = listener.next; } }); + const result = { + numCalled, + numSkipped + }; + return result; }, get() { @@ -115,6 +124,9 @@ const nullListeners = { function createSubscription(store, parentSub, trackingNode) { let unsubscribe; let listeners = nullListeners; + const updateNodeTimes = []; + const notifyTimes = []; + const resultCounts = []; function addNestedSub(listener, options = { trigger: 'always' @@ -127,10 +139,28 @@ function createSubscription(store, parentSub, trackingNode) { function notifyNestedSubs() { if (store && trackingNode) { //console.log('Updating node in notifyNestedSubs') + const _start = performance.now(); + (0, _proxy.updateNode)(trackingNode, store.getState()); + + const _end = performance.now(); + + updateNodeTimes.push({ + start: _start, + end: _end, + duration: _end - _start + }); } - listeners.notify(); + const start = performance.now(); + const results = listeners.notify(); + const end = performance.now(); + notifyTimes.push({ + start, + end, + duration: end - start + }); + resultCounts.push(results); } function handleChangeWrapper() { diff --git a/.yalc/react-redux/lib/utils/autotracking/autotracking.js b/.yalc/react-redux/lib/utils/autotracking/autotracking.js old mode 100644 new mode 100755 index 18ba4fc..d60d057 --- a/.yalc/react-redux/lib/utils/autotracking/autotracking.js +++ b/.yalc/react-redux/lib/utils/autotracking/autotracking.js @@ -105,42 +105,42 @@ class TrackingCache { return this._needsRecalculation; } - /* - getWithArgs = (...args: any[]) => { - // console.log( - // `TrackingCache value: revision = ${this.revision}, cachedRevision = ${this._cachedRevision}, value = ${this._cachedValue}` - // ) - // When getting the value for a Cache, first we check all the dependencies of - // the cache to see what their current revision is. If the current revision is - // greater than the cached revision, then something has changed. - //if (this.revision > this._cachedRevision) { - if (this.needsRecalculation()) { - const { fn } = this - // We create a new dependency tracker for this cache. As the cache runs - // its function, any Storage or Cache instances which are used while - // computing will be added to this tracker. In the end, it will be the - // full list of dependencies that this Cache depends on. - const currentTracker = new Set>() - const prevTracker = CURRENT_TRACKER - CURRENT_TRACKER = currentTracker - // try { - this._cachedValue = fn.apply(null, args) - // } finally { - CURRENT_TRACKER = prevTracker - this.hits++ - this._deps = Array.from(currentTracker) - // Set the cached revision. This is the current clock count of all the - // dependencies. If any dependency changes, this number will be less - // than the new revision. - this._cachedRevision = this.revision - // } - } - // If there is a current tracker, it means another Cache is computing and - // using this one, so we add this one to the tracker. - CURRENT_TRACKER?.add(this) - // Always return the cached value. - return this._cachedValue - } + /* + getWithArgs = (...args: any[]) => { + // console.log( + // `TrackingCache value: revision = ${this.revision}, cachedRevision = ${this._cachedRevision}, value = ${this._cachedValue}` + // ) + // When getting the value for a Cache, first we check all the dependencies of + // the cache to see what their current revision is. If the current revision is + // greater than the cached revision, then something has changed. + //if (this.revision > this._cachedRevision) { + if (this.needsRecalculation()) { + const { fn } = this + // We create a new dependency tracker for this cache. As the cache runs + // its function, any Storage or Cache instances which are used while + // computing will be added to this tracker. In the end, it will be the + // full list of dependencies that this Cache depends on. + const currentTracker = new Set>() + const prevTracker = CURRENT_TRACKER + CURRENT_TRACKER = currentTracker + // try { + this._cachedValue = fn.apply(null, args) + // } finally { + CURRENT_TRACKER = prevTracker + this.hits++ + this._deps = Array.from(currentTracker) + // Set the cached revision. This is the current clock count of all the + // dependencies. If any dependency changes, this number will be less + // than the new revision. + this._cachedRevision = this.revision + // } + } + // If there is a current tracker, it means another Cache is computing and + // using this one, so we add this one to the tracker. + CURRENT_TRACKER?.add(this) + // Always return the cached value. + return this._cachedValue + } */ diff --git a/.yalc/react-redux/lib/utils/autotracking/proxy.js b/.yalc/react-redux/lib/utils/autotracking/proxy.js old mode 100644 new mode 100755 index 3e551f3..b354acb --- a/.yalc/react-redux/lib/utils/autotracking/proxy.js +++ b/.yalc/react-redux/lib/utils/autotracking/proxy.js @@ -200,7 +200,7 @@ function updateNode(node, newValue) { if (childValue === newChildValue) { continue; } else if (typeof newChildValue === 'object' && newChildValue !== null) { - console.log('Updating node key: ', key); + // console.log('Updating node key: ', key) updateNode(childNode, newChildValue); } else { deleteNode(childNode); diff --git a/.yalc/react-redux/lib/utils/isPlainObject.js b/.yalc/react-redux/lib/utils/isPlainObject.js old mode 100644 new mode 100755 index e01e755..b22aa89 --- a/.yalc/react-redux/lib/utils/isPlainObject.js +++ b/.yalc/react-redux/lib/utils/isPlainObject.js @@ -3,9 +3,9 @@ exports.__esModule = true; exports.default = isPlainObject; -/** - * @param {any} obj The object to inspect. - * @returns {boolean} True if the argument appears to be a plain object. +/** + * @param {any} obj The object to inspect. + * @returns {boolean} True if the argument appears to be a plain object. */ function isPlainObject(obj) { if (typeof obj !== 'object' || obj === null) return false; diff --git a/.yalc/react-redux/lib/utils/warning.js b/.yalc/react-redux/lib/utils/warning.js old mode 100644 new mode 100755 index 00de372..1a9f653 --- a/.yalc/react-redux/lib/utils/warning.js +++ b/.yalc/react-redux/lib/utils/warning.js @@ -3,11 +3,11 @@ exports.__esModule = true; exports.default = warning; -/** - * Prints a warning in the console if it exists. - * - * @param {String} message The warning message. - * @returns {void} +/** + * Prints a warning in the console if it exists. + * + * @param {String} message The warning message. + * @returns {void} */ function warning(message) { /* eslint-disable no-console */ diff --git a/.yalc/react-redux/package.json b/.yalc/react-redux/package.json old mode 100644 new mode 100755 index 6d88b4c..14b8859 --- a/.yalc/react-redux/package.json +++ b/.yalc/react-redux/package.json @@ -71,5 +71,5 @@ "react-is": "^18.0.0", "use-sync-external-store": "^1.0.0" }, - "yalcSig": "ea764a3365bc9f3c88f6e77bbb637f94" + "yalcSig": "b9f845048d76117633fd4e318d90540d" } diff --git a/.yalc/react-redux/src/alternate-renderers.ts b/.yalc/react-redux/src/alternate-renderers.ts old mode 100644 new mode 100755 index 96df1ef..0c0f6f4 --- a/.yalc/react-redux/src/alternate-renderers.ts +++ b/.yalc/react-redux/src/alternate-renderers.ts @@ -1,23 +1,23 @@ -// The "alternate renderers" entry point is primarily here to fall back on a no-op -// version of `unstable_batchedUpdates`, for use with renderers other than ReactDOM/RN. -// Examples include React-Three-Fiber, Ink, etc. -// Because of that, we'll also assume the useSyncExternalStore compat shim is needed. - -import { useSyncExternalStore } from 'use-sync-external-store/shim' -import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector' - -import { initializeUseSelector } from './hooks/useSelector' -import { initializeConnect } from './components/connect' - -initializeUseSelector(useSyncExternalStoreWithSelector) -initializeConnect(useSyncExternalStore) - -import { getBatch } from './utils/batch' - -// For other renderers besides ReactDOM and React Native, -// use the default noop batch function -const batch = getBatch() - -export { batch } - -export * from './exports' +// The "alternate renderers" entry point is primarily here to fall back on a no-op +// version of `unstable_batchedUpdates`, for use with renderers other than ReactDOM/RN. +// Examples include React-Three-Fiber, Ink, etc. +// Because of that, we'll also assume the useSyncExternalStore compat shim is needed. + +import { useSyncExternalStore } from 'use-sync-external-store/shim' +import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector' + +import { initializeUseSelector } from './hooks/useSelector' +import { initializeConnect } from './components/connect' + +initializeUseSelector(useSyncExternalStoreWithSelector) +initializeConnect(useSyncExternalStore) + +import { getBatch } from './utils/batch' + +// For other renderers besides ReactDOM and React Native, +// use the default noop batch function +const batch = getBatch() + +export { batch } + +export * from './exports' diff --git a/.yalc/react-redux/src/components/Context.ts b/.yalc/react-redux/src/components/Context.ts old mode 100644 new mode 100755 index 9ac11c4..f4e102b --- a/.yalc/react-redux/src/components/Context.ts +++ b/.yalc/react-redux/src/components/Context.ts @@ -1,51 +1,51 @@ -import { createContext, version as ReactVersion } from 'react' -import type { Context } from 'react' -import type { Action, AnyAction, Store } from 'redux' -import type { Subscription } from '../utils/Subscription' -import type { CheckFrequency } from '../hooks/useSelector' -import type { Node } from '../utils/autotracking/tracking' - -export interface ReactReduxContextValue< - SS = any, - A extends Action = AnyAction -> { - store: Store - subscription: Subscription - getServerState?: () => SS - stabilityCheck: CheckFrequency - noopCheck: CheckFrequency - trackingNode: Node> -} - -const ContextKey = Symbol.for(`react-redux-context-${ReactVersion}`) -const gT = globalThis as { [ContextKey]?: Context } - -function getContext() { - let realContext = gT[ContextKey] - if (!realContext) { - realContext = createContext(null as any) - if (process.env.NODE_ENV !== 'production') { - realContext.displayName = 'ReactRedux' - } - gT[ContextKey] = realContext - } - return realContext -} - -export const ReactReduxContext = /*#__PURE__*/ new Proxy( - {} as Context, - /*#__PURE__*/ new Proxy>>( - {}, - { - get(_, handler) { - const target = getContext() - // @ts-ignore - return (_target, ...args) => Reflect[handler](target, ...args) - }, - } - ) -) - -export type ReactReduxContextInstance = typeof ReactReduxContext - -export default ReactReduxContext +import { createContext, version as ReactVersion } from 'react' +import type { Context } from 'react' +import type { Action, AnyAction, Store } from 'redux' +import type { Subscription } from '../utils/Subscription' +import type { CheckFrequency } from '../hooks/useSelector' +import type { Node } from '../utils/autotracking/tracking' + +export interface ReactReduxContextValue< + SS = any, + A extends Action = AnyAction +> { + store: Store + subscription: Subscription + getServerState?: () => SS + stabilityCheck: CheckFrequency + noopCheck: CheckFrequency + trackingNode: Node> +} + +const ContextKey = Symbol.for(`react-redux-context-${ReactVersion}`) +const gT = globalThis as { [ContextKey]?: Context } + +function getContext() { + let realContext = gT[ContextKey] + if (!realContext) { + realContext = createContext(null as any) + if (process.env.NODE_ENV !== 'production') { + realContext.displayName = 'ReactRedux' + } + gT[ContextKey] = realContext + } + return realContext +} + +export const ReactReduxContext = /*#__PURE__*/ new Proxy( + {} as Context, + /*#__PURE__*/ new Proxy>>( + {}, + { + get(_, handler) { + const target = getContext() + // @ts-ignore + return (_target, ...args) => Reflect[handler](target, ...args) + }, + } + ) +) + +export type ReactReduxContextInstance = typeof ReactReduxContext + +export default ReactReduxContext diff --git a/.yalc/react-redux/src/components/Provider.tsx b/.yalc/react-redux/src/components/Provider.tsx old mode 100644 new mode 100755 index 35fe463..63284fe --- a/.yalc/react-redux/src/components/Provider.tsx +++ b/.yalc/react-redux/src/components/Provider.tsx @@ -1,86 +1,86 @@ -import type { Context, ReactNode } from 'react' -import React, { useMemo } from 'react' -import type { ReactReduxContextValue } from './Context' -import { ReactReduxContext } from './Context' -import { createSubscription } from '../utils/Subscription' -import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect' -import type { Action, AnyAction, Store } from 'redux' -import type { CheckFrequency } from '../hooks/useSelector' -import { createNode, updateNode } from '../utils/autotracking/proxy' - -export interface ProviderProps { - /** - * The single Redux store in your application. - */ - store: Store - - /** - * An optional server state snapshot. Will be used during initial hydration render if available, to ensure that the UI output is consistent with the HTML generated on the server. - */ - serverState?: S - - /** - * Optional context to be used internally in react-redux. Use React.createContext() to create a context to be used. - * If this is used, you'll need to customize `connect` by supplying the same context provided to the Provider. - * Initial value doesn't matter, as it is overwritten with the internal state of Provider. - */ - context?: Context> - - /** Global configuration for the `useSelector` stability check */ - stabilityCheck?: CheckFrequency - - /** Global configuration for the `useSelector` no-op check */ - noopCheck?: CheckFrequency - - children: ReactNode -} - -function Provider({ - store, - context, - children, - serverState, - stabilityCheck = 'once', - noopCheck = 'once', -}: ProviderProps) { - const contextValue: ReactReduxContextValue = useMemo(() => { - const trackingNode = createNode(store.getState() as any) - //console.log('Created tracking node: ', trackingNode) - const subscription = createSubscription( - store as any, - undefined, - trackingNode - ) - return { - store: store as any, - subscription, - getServerState: serverState ? () => serverState : undefined, - stabilityCheck, - noopCheck, - trackingNode, - } - }, [store, serverState, stabilityCheck, noopCheck]) - - const previousState = useMemo(() => store.getState(), [store]) - - useIsomorphicLayoutEffect(() => { - const { subscription } = contextValue - subscription.onStateChange = subscription.notifyNestedSubs - subscription.trySubscribe() - - if (previousState !== store.getState()) { - subscription.notifyNestedSubs() - } - return () => { - subscription.tryUnsubscribe() - subscription.onStateChange = undefined - } - }, [contextValue, previousState]) - - const Context = context || ReactReduxContext - - // @ts-ignore 'AnyAction' is assignable to the constraint of type 'A', but 'A' could be instantiated with a different subtype - return {children} -} - -export default Provider +import type { Context, ReactNode } from 'react' +import React, { useMemo } from 'react' +import type { ReactReduxContextValue } from './Context' +import { ReactReduxContext } from './Context' +import { createSubscription } from '../utils/Subscription' +import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect' +import type { Action, AnyAction, Store } from 'redux' +import type { CheckFrequency } from '../hooks/useSelector' +import { createNode, updateNode } from '../utils/autotracking/proxy' + +export interface ProviderProps { + /** + * The single Redux store in your application. + */ + store: Store + + /** + * An optional server state snapshot. Will be used during initial hydration render if available, to ensure that the UI output is consistent with the HTML generated on the server. + */ + serverState?: S + + /** + * Optional context to be used internally in react-redux. Use React.createContext() to create a context to be used. + * If this is used, you'll need to customize `connect` by supplying the same context provided to the Provider. + * Initial value doesn't matter, as it is overwritten with the internal state of Provider. + */ + context?: Context> + + /** Global configuration for the `useSelector` stability check */ + stabilityCheck?: CheckFrequency + + /** Global configuration for the `useSelector` no-op check */ + noopCheck?: CheckFrequency + + children: ReactNode +} + +function Provider({ + store, + context, + children, + serverState, + stabilityCheck = 'once', + noopCheck = 'once', +}: ProviderProps) { + const contextValue: ReactReduxContextValue = useMemo(() => { + const trackingNode = createNode(store.getState() as any) + //console.log('Created tracking node: ', trackingNode) + const subscription = createSubscription( + store as any, + undefined, + trackingNode + ) + return { + store: store as any, + subscription, + getServerState: serverState ? () => serverState : undefined, + stabilityCheck, + noopCheck, + trackingNode, + } + }, [store, serverState, stabilityCheck, noopCheck]) + + const previousState = useMemo(() => store.getState(), [store]) + + useIsomorphicLayoutEffect(() => { + const { subscription } = contextValue + subscription.onStateChange = subscription.notifyNestedSubs + subscription.trySubscribe() + + if (previousState !== store.getState()) { + subscription.notifyNestedSubs() + } + return () => { + subscription.tryUnsubscribe() + subscription.onStateChange = undefined + } + }, [contextValue, previousState]) + + const Context = context || ReactReduxContext + + // @ts-ignore 'AnyAction' is assignable to the constraint of type 'A', but 'A' could be instantiated with a different subtype + return {children} +} + +export default Provider diff --git a/.yalc/react-redux/src/components/connect.tsx b/.yalc/react-redux/src/components/connect.tsx old mode 100644 new mode 100755 index 77fb273..5e26d27 --- a/.yalc/react-redux/src/components/connect.tsx +++ b/.yalc/react-redux/src/components/connect.tsx @@ -1,809 +1,809 @@ -/* eslint-disable valid-jsdoc, @typescript-eslint/no-unused-vars */ -import hoistStatics from 'hoist-non-react-statics' -import type { ComponentType } from 'react' -import React, { useContext, useMemo, useRef } from 'react' -import { isValidElementType, isContextConsumer } from 'react-is' - -import type { Store } from 'redux' - -import type { - ConnectedComponent, - InferableComponentEnhancer, - InferableComponentEnhancerWithProps, - ResolveThunks, - DispatchProp, - ConnectPropsMaybeWithoutContext, -} from '../types' - -import type { - MapStateToPropsParam, - MapDispatchToPropsParam, - MergeProps, - MapDispatchToPropsNonObject, - SelectorFactoryOptions, -} from '../connect/selectorFactory' -import defaultSelectorFactory from '../connect/selectorFactory' -import { mapDispatchToPropsFactory } from '../connect/mapDispatchToProps' -import { mapStateToPropsFactory } from '../connect/mapStateToProps' -import { mergePropsFactory } from '../connect/mergeProps' - -import type { Subscription } from '../utils/Subscription' -import { createSubscription } from '../utils/Subscription' -import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect' -import shallowEqual from '../utils/shallowEqual' -import warning from '../utils/warning' - -import type { - ReactReduxContextValue, - ReactReduxContextInstance, -} from './Context' -import { ReactReduxContext } from './Context' - -import type { uSES } from '../utils/useSyncExternalStore' -import { notInitialized } from '../utils/useSyncExternalStore' - -let useSyncExternalStore = notInitialized as uSES -export const initializeConnect = (fn: uSES) => { - useSyncExternalStore = fn -} - -// Define some constant arrays just to avoid re-creating these -const EMPTY_ARRAY: [unknown, number] = [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: unknown) => { - try { - return JSON.stringify(Comp) - } catch (err) { - return String(Comp) - } -} - -type EffectFunc = (...args: any[]) => void | ReturnType - -// 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: EffectFunc, - effectArgs: any[], - dependencies?: React.DependencyList -) { - useIsomorphicLayoutEffect(() => effectFunc(...effectArgs), dependencies) -} - -// Effect callback, extracted: assign the latest props values to refs for later usage -function captureWrapperProps( - lastWrapperProps: React.MutableRefObject, - lastChildProps: React.MutableRefObject, - renderIsScheduled: React.MutableRefObject, - wrapperProps: unknown, - // actualChildProps: unknown, - childPropsFromStoreUpdate: React.MutableRefObject, - notifyNestedSubs: () => void -) { - // 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: boolean, - store: Store, - subscription: Subscription, - childPropsSelector: (state: unknown, props: unknown) => unknown, - lastWrapperProps: React.MutableRefObject, - lastChildProps: React.MutableRefObject, - renderIsScheduled: React.MutableRefObject, - isMounted: React.MutableRefObject, - childPropsFromStoreUpdate: React.MutableRefObject, - notifyNestedSubs: () => void, - // forceComponentUpdateDispatch: React.Dispatch, - additionalSubscribeListener: () => void -) { - // 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: Error | null = 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 as Error | null - } - - 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 - -export interface ConnectProps { - /** A custom Context instance that the component can use to access the store from an alternate Provider using that same Context instance */ - context?: ReactReduxContextInstance - /** A Redux store instance to be used for subscriptions instead of the store from a Provider */ - store?: Store -} - -interface InternalConnectProps extends ConnectProps { - reactReduxForwardedRef?: React.ForwardedRef -} - -function strictEqual(a: unknown, b: unknown) { - return a === b -} - -/** - * Infers the type of props that a connector will inject into a component. - */ -export type ConnectedProps = - TConnector extends InferableComponentEnhancerWithProps< - infer TInjectedProps, - any - > - ? unknown extends TInjectedProps - ? TConnector extends InferableComponentEnhancer - ? TInjectedProps - : never - : TInjectedProps - : never - -export interface ConnectOptions< - State = unknown, - TStateProps = {}, - TOwnProps = {}, - TMergedProps = {} -> { - forwardRef?: boolean - context?: typeof ReactReduxContext - areStatesEqual?: ( - nextState: State, - prevState: State, - nextOwnProps: TOwnProps, - prevOwnProps: TOwnProps - ) => boolean - - areOwnPropsEqual?: ( - nextOwnProps: TOwnProps, - prevOwnProps: TOwnProps - ) => boolean - - areStatePropsEqual?: ( - nextStateProps: TStateProps, - prevStateProps: TStateProps - ) => boolean - areMergedPropsEqual?: ( - nextMergedProps: TMergedProps, - prevMergedProps: TMergedProps - ) => boolean -} - -/** - * 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 - * @param mapDispatchToProps - * @param mergeProps - * @param options - */ -export interface Connect { - // tslint:disable:no-unnecessary-generics - (): InferableComponentEnhancer - - /** mapState only */ - ( - mapStateToProps: MapStateToPropsParam - ): InferableComponentEnhancerWithProps - - /** mapDispatch only (as a function) */ - ( - mapStateToProps: null | undefined, - mapDispatchToProps: MapDispatchToPropsNonObject - ): InferableComponentEnhancerWithProps - - /** mapDispatch only (as an object) */ - ( - mapStateToProps: null | undefined, - mapDispatchToProps: MapDispatchToPropsParam - ): InferableComponentEnhancerWithProps< - ResolveThunks, - TOwnProps - > - - /** mapState and mapDispatch (as a function)*/ - ( - mapStateToProps: MapStateToPropsParam, - mapDispatchToProps: MapDispatchToPropsNonObject - ): InferableComponentEnhancerWithProps< - TStateProps & TDispatchProps, - TOwnProps - > - - /** mapState and mapDispatch (nullish) */ - ( - mapStateToProps: MapStateToPropsParam, - mapDispatchToProps: null | undefined - ): InferableComponentEnhancerWithProps - - /** mapState and mapDispatch (as an object) */ - ( - mapStateToProps: MapStateToPropsParam, - mapDispatchToProps: MapDispatchToPropsParam - ): InferableComponentEnhancerWithProps< - TStateProps & ResolveThunks, - TOwnProps - > - - /** mergeProps only */ - ( - mapStateToProps: null | undefined, - mapDispatchToProps: null | undefined, - mergeProps: MergeProps - ): InferableComponentEnhancerWithProps - - /** mapState and mergeProps */ - < - TStateProps = {}, - no_dispatch = {}, - TOwnProps = {}, - TMergedProps = {}, - State = DefaultState - >( - mapStateToProps: MapStateToPropsParam, - mapDispatchToProps: null | undefined, - mergeProps: MergeProps - ): InferableComponentEnhancerWithProps - - /** mapDispatch (as a object) and mergeProps */ - ( - mapStateToProps: null | undefined, - mapDispatchToProps: MapDispatchToPropsParam, - mergeProps: MergeProps - ): InferableComponentEnhancerWithProps - - /** mapState and options */ - ( - mapStateToProps: MapStateToPropsParam, - mapDispatchToProps: null | undefined, - mergeProps: null | undefined, - options: ConnectOptions - ): InferableComponentEnhancerWithProps - - /** mapDispatch (as a function) and options */ - ( - mapStateToProps: null | undefined, - mapDispatchToProps: MapDispatchToPropsNonObject, - mergeProps: null | undefined, - options: ConnectOptions<{}, TStateProps, TOwnProps> - ): InferableComponentEnhancerWithProps - - /** mapDispatch (as an object) and options*/ - ( - mapStateToProps: null | undefined, - mapDispatchToProps: MapDispatchToPropsParam, - mergeProps: null | undefined, - options: ConnectOptions<{}, TStateProps, TOwnProps> - ): InferableComponentEnhancerWithProps< - ResolveThunks, - TOwnProps - > - - /** mapState, mapDispatch (as a function), and options */ - ( - mapStateToProps: MapStateToPropsParam, - mapDispatchToProps: MapDispatchToPropsNonObject, - mergeProps: null | undefined, - options: ConnectOptions - ): InferableComponentEnhancerWithProps< - TStateProps & TDispatchProps, - TOwnProps - > - - /** mapState, mapDispatch (as an object), and options */ - ( - mapStateToProps: MapStateToPropsParam, - mapDispatchToProps: MapDispatchToPropsParam, - mergeProps: null | undefined, - options: ConnectOptions - ): InferableComponentEnhancerWithProps< - TStateProps & ResolveThunks, - TOwnProps - > - - /** mapState, mapDispatch, mergeProps, and options */ - < - TStateProps = {}, - TDispatchProps = {}, - TOwnProps = {}, - TMergedProps = {}, - State = DefaultState - >( - mapStateToProps: MapStateToPropsParam, - mapDispatchToProps: MapDispatchToPropsParam, - mergeProps: MergeProps< - TStateProps, - TDispatchProps, - TOwnProps, - TMergedProps - >, - options?: ConnectOptions - ): InferableComponentEnhancerWithProps - // tslint:enable:no-unnecessary-generics -} - -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< - TStateProps = {}, - TDispatchProps = {}, - TOwnProps = {}, - TMergedProps = {}, - State = unknown ->( - mapStateToProps?: MapStateToPropsParam, - mapDispatchToProps?: MapDispatchToPropsParam, - mergeProps?: 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, - areStatePropsEqual = shallowEqual, - areMergedPropsEqual = shallowEqual, - - // use React's forwardRef to expose a ref of the wrapped component - forwardRef = false, - - // the context consumer to use - context = ReactReduxContext, - }: ConnectOptions = {} -): unknown { - if (process.env.NODE_ENV !== 'production') { - if (pure !== undefined && !hasWarnedAboutDeprecatedPureOption) { - hasWarnedAboutDeprecatedPureOption = true - warning( - 'The `pure` option has been removed. `connect` is now always a "pure/memoized" component' - ) - } - } - - const Context = context - - const initMapStateToProps = mapStateToPropsFactory(mapStateToProps) - const initMapDispatchToProps = mapDispatchToPropsFactory(mapDispatchToProps) - const initMergeProps = mergePropsFactory(mergeProps) - - const shouldHandleStateChanges = Boolean(mapStateToProps) - - const wrapWithConnect = ( - WrappedComponent: ComponentType - ) => { - type WrappedComponentProps = TProps & - ConnectPropsMaybeWithoutContext - - if ( - process.env.NODE_ENV !== 'production' && - !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: SelectorFactoryOptions< - any, - any, - any, - any, - State - > = { - shouldHandleStateChanges, - displayName, - wrappedComponentName, - WrappedComponent, - // @ts-ignore - initMapStateToProps, - // @ts-ignore - initMapDispatchToProps, - initMergeProps, - areStatesEqual, - areStatePropsEqual, - areOwnPropsEqual, - areMergedPropsEqual, - } - - function ConnectFunction( - props: InternalConnectProps & TOwnProps - ) { - const [propsContext, reactReduxForwardedRef, wrapperProps] = - 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, ...wrapperProps } = props - return [props.context, reactReduxForwardedRef, wrapperProps] - }, [props]) - - const ContextToUse: ReactReduxContextInstance = 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 - isContextConsumer() - ? propsContext - : Context - }, [propsContext, Context]) - - // Retrieve the store and ancestor subscription via context, if available - const contextValue = 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 , ` + - `or pass a custom React context provider to and the corresponding ` + - `React context consumer to ${displayName} in connect options.` - ) - } - - // Based on the previous check, one of these must be true - const store: Store = didStoreComeFromProps - ? props.store! - : contextValue!.store - - const getServerState = didStoreComeFromContext - ? contextValue.getServerState - : store.getState - - const childPropsSelector = useMemo(() => { - // The child props selector needs the store reference as an input. - // Re-create this selector whenever the store changes. - return defaultSelectorFactory(store.dispatch, selectorFactoryOptions) - }, [store]) - - const [subscription, notifyNestedSubs] = 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 = 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 = 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 { - ...contextValue, - subscription, - } as ReactReduxContextValue - }, [didStoreComeFromProps, contextValue, subscription]) - - // Set up refs to coordinate values between the subscription effect and the render logic - const lastChildProps = useRef() - const lastWrapperProps = useRef(wrapperProps) - const childPropsFromStoreUpdate = useRef() - const renderIsScheduled = useRef(false) - const isProcessingDispatch = useRef(false) - const isMounted = useRef(false) - - const latestSubscriptionCallbackError = useRef() - - useIsomorphicLayoutEffect(() => { - isMounted.current = true - return () => { - isMounted.current = false - } - }, []) - - const actualChildPropsSelector = 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 = useMemo(() => { - const subscribe = (reactListener: () => void) => { - 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: Record - - 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 as Error - ).message += `\nThe error may be correlated with this previous error:\n${latestSubscriptionCallbackError.current.stack}\n\n` - } - - throw err - } - - 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 = useMemo(() => { - return ( - // @ts-ignore - - ) - }, [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 = 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 ( - - {renderedWrappedComponent} - - ) - } - - return renderedWrappedComponent - }, [ContextToUse, renderedWrappedComponent, overriddenContextValue]) - - return renderedChild - } - - const _Connect = React.memo(ConnectFunction) - - type ConnectedWrapperComponent = typeof _Connect & { - WrappedComponent: typeof WrappedComponent - } - - // Add a hacky cast to get the right output type - const Connect = _Connect as unknown as ConnectedComponent< - typeof WrappedComponent, - WrappedComponentProps - > - Connect.WrappedComponent = WrappedComponent - Connect.displayName = ConnectFunction.displayName = displayName - - if (forwardRef) { - const _forwarded = React.forwardRef(function forwardConnectRef( - props, - ref - ) { - // @ts-ignore - return - }) - - const forwarded = _forwarded as ConnectedWrapperComponent - forwarded.displayName = displayName - forwarded.WrappedComponent = WrappedComponent - return hoistStatics(forwarded, WrappedComponent) - } - - return hoistStatics(Connect, WrappedComponent) - } - - return wrapWithConnect -} - -export default connect as Connect +/* eslint-disable valid-jsdoc, @typescript-eslint/no-unused-vars */ +import hoistStatics from 'hoist-non-react-statics' +import type { ComponentType } from 'react' +import React, { useContext, useMemo, useRef } from 'react' +import { isValidElementType, isContextConsumer } from 'react-is' + +import type { Store } from 'redux' + +import type { + ConnectedComponent, + InferableComponentEnhancer, + InferableComponentEnhancerWithProps, + ResolveThunks, + DispatchProp, + ConnectPropsMaybeWithoutContext, +} from '../types' + +import type { + MapStateToPropsParam, + MapDispatchToPropsParam, + MergeProps, + MapDispatchToPropsNonObject, + SelectorFactoryOptions, +} from '../connect/selectorFactory' +import defaultSelectorFactory from '../connect/selectorFactory' +import { mapDispatchToPropsFactory } from '../connect/mapDispatchToProps' +import { mapStateToPropsFactory } from '../connect/mapStateToProps' +import { mergePropsFactory } from '../connect/mergeProps' + +import type { Subscription } from '../utils/Subscription' +import { createSubscription } from '../utils/Subscription' +import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect' +import shallowEqual from '../utils/shallowEqual' +import warning from '../utils/warning' + +import type { + ReactReduxContextValue, + ReactReduxContextInstance, +} from './Context' +import { ReactReduxContext } from './Context' + +import type { uSES } from '../utils/useSyncExternalStore' +import { notInitialized } from '../utils/useSyncExternalStore' + +let useSyncExternalStore = notInitialized as uSES +export const initializeConnect = (fn: uSES) => { + useSyncExternalStore = fn +} + +// Define some constant arrays just to avoid re-creating these +const EMPTY_ARRAY: [unknown, number] = [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: unknown) => { + try { + return JSON.stringify(Comp) + } catch (err) { + return String(Comp) + } +} + +type EffectFunc = (...args: any[]) => void | ReturnType + +// 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: EffectFunc, + effectArgs: any[], + dependencies?: React.DependencyList +) { + useIsomorphicLayoutEffect(() => effectFunc(...effectArgs), dependencies) +} + +// Effect callback, extracted: assign the latest props values to refs for later usage +function captureWrapperProps( + lastWrapperProps: React.MutableRefObject, + lastChildProps: React.MutableRefObject, + renderIsScheduled: React.MutableRefObject, + wrapperProps: unknown, + // actualChildProps: unknown, + childPropsFromStoreUpdate: React.MutableRefObject, + notifyNestedSubs: () => void +) { + // 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: boolean, + store: Store, + subscription: Subscription, + childPropsSelector: (state: unknown, props: unknown) => unknown, + lastWrapperProps: React.MutableRefObject, + lastChildProps: React.MutableRefObject, + renderIsScheduled: React.MutableRefObject, + isMounted: React.MutableRefObject, + childPropsFromStoreUpdate: React.MutableRefObject, + notifyNestedSubs: () => void, + // forceComponentUpdateDispatch: React.Dispatch, + additionalSubscribeListener: () => void +) { + // 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: Error | null = 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 as Error | null + } + + 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 + +export interface ConnectProps { + /** A custom Context instance that the component can use to access the store from an alternate Provider using that same Context instance */ + context?: ReactReduxContextInstance + /** A Redux store instance to be used for subscriptions instead of the store from a Provider */ + store?: Store +} + +interface InternalConnectProps extends ConnectProps { + reactReduxForwardedRef?: React.ForwardedRef +} + +function strictEqual(a: unknown, b: unknown) { + return a === b +} + +/** + * Infers the type of props that a connector will inject into a component. + */ +export type ConnectedProps = + TConnector extends InferableComponentEnhancerWithProps< + infer TInjectedProps, + any + > + ? unknown extends TInjectedProps + ? TConnector extends InferableComponentEnhancer + ? TInjectedProps + : never + : TInjectedProps + : never + +export interface ConnectOptions< + State = unknown, + TStateProps = {}, + TOwnProps = {}, + TMergedProps = {} +> { + forwardRef?: boolean + context?: typeof ReactReduxContext + areStatesEqual?: ( + nextState: State, + prevState: State, + nextOwnProps: TOwnProps, + prevOwnProps: TOwnProps + ) => boolean + + areOwnPropsEqual?: ( + nextOwnProps: TOwnProps, + prevOwnProps: TOwnProps + ) => boolean + + areStatePropsEqual?: ( + nextStateProps: TStateProps, + prevStateProps: TStateProps + ) => boolean + areMergedPropsEqual?: ( + nextMergedProps: TMergedProps, + prevMergedProps: TMergedProps + ) => boolean +} + +/** + * 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 + * @param mapDispatchToProps + * @param mergeProps + * @param options + */ +export interface Connect { + // tslint:disable:no-unnecessary-generics + (): InferableComponentEnhancer + + /** mapState only */ + ( + mapStateToProps: MapStateToPropsParam + ): InferableComponentEnhancerWithProps + + /** mapDispatch only (as a function) */ + ( + mapStateToProps: null | undefined, + mapDispatchToProps: MapDispatchToPropsNonObject + ): InferableComponentEnhancerWithProps + + /** mapDispatch only (as an object) */ + ( + mapStateToProps: null | undefined, + mapDispatchToProps: MapDispatchToPropsParam + ): InferableComponentEnhancerWithProps< + ResolveThunks, + TOwnProps + > + + /** mapState and mapDispatch (as a function)*/ + ( + mapStateToProps: MapStateToPropsParam, + mapDispatchToProps: MapDispatchToPropsNonObject + ): InferableComponentEnhancerWithProps< + TStateProps & TDispatchProps, + TOwnProps + > + + /** mapState and mapDispatch (nullish) */ + ( + mapStateToProps: MapStateToPropsParam, + mapDispatchToProps: null | undefined + ): InferableComponentEnhancerWithProps + + /** mapState and mapDispatch (as an object) */ + ( + mapStateToProps: MapStateToPropsParam, + mapDispatchToProps: MapDispatchToPropsParam + ): InferableComponentEnhancerWithProps< + TStateProps & ResolveThunks, + TOwnProps + > + + /** mergeProps only */ + ( + mapStateToProps: null | undefined, + mapDispatchToProps: null | undefined, + mergeProps: MergeProps + ): InferableComponentEnhancerWithProps + + /** mapState and mergeProps */ + < + TStateProps = {}, + no_dispatch = {}, + TOwnProps = {}, + TMergedProps = {}, + State = DefaultState + >( + mapStateToProps: MapStateToPropsParam, + mapDispatchToProps: null | undefined, + mergeProps: MergeProps + ): InferableComponentEnhancerWithProps + + /** mapDispatch (as a object) and mergeProps */ + ( + mapStateToProps: null | undefined, + mapDispatchToProps: MapDispatchToPropsParam, + mergeProps: MergeProps + ): InferableComponentEnhancerWithProps + + /** mapState and options */ + ( + mapStateToProps: MapStateToPropsParam, + mapDispatchToProps: null | undefined, + mergeProps: null | undefined, + options: ConnectOptions + ): InferableComponentEnhancerWithProps + + /** mapDispatch (as a function) and options */ + ( + mapStateToProps: null | undefined, + mapDispatchToProps: MapDispatchToPropsNonObject, + mergeProps: null | undefined, + options: ConnectOptions<{}, TStateProps, TOwnProps> + ): InferableComponentEnhancerWithProps + + /** mapDispatch (as an object) and options*/ + ( + mapStateToProps: null | undefined, + mapDispatchToProps: MapDispatchToPropsParam, + mergeProps: null | undefined, + options: ConnectOptions<{}, TStateProps, TOwnProps> + ): InferableComponentEnhancerWithProps< + ResolveThunks, + TOwnProps + > + + /** mapState, mapDispatch (as a function), and options */ + ( + mapStateToProps: MapStateToPropsParam, + mapDispatchToProps: MapDispatchToPropsNonObject, + mergeProps: null | undefined, + options: ConnectOptions + ): InferableComponentEnhancerWithProps< + TStateProps & TDispatchProps, + TOwnProps + > + + /** mapState, mapDispatch (as an object), and options */ + ( + mapStateToProps: MapStateToPropsParam, + mapDispatchToProps: MapDispatchToPropsParam, + mergeProps: null | undefined, + options: ConnectOptions + ): InferableComponentEnhancerWithProps< + TStateProps & ResolveThunks, + TOwnProps + > + + /** mapState, mapDispatch, mergeProps, and options */ + < + TStateProps = {}, + TDispatchProps = {}, + TOwnProps = {}, + TMergedProps = {}, + State = DefaultState + >( + mapStateToProps: MapStateToPropsParam, + mapDispatchToProps: MapDispatchToPropsParam, + mergeProps: MergeProps< + TStateProps, + TDispatchProps, + TOwnProps, + TMergedProps + >, + options?: ConnectOptions + ): InferableComponentEnhancerWithProps + // tslint:enable:no-unnecessary-generics +} + +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< + TStateProps = {}, + TDispatchProps = {}, + TOwnProps = {}, + TMergedProps = {}, + State = unknown +>( + mapStateToProps?: MapStateToPropsParam, + mapDispatchToProps?: MapDispatchToPropsParam, + mergeProps?: 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, + areStatePropsEqual = shallowEqual, + areMergedPropsEqual = shallowEqual, + + // use React's forwardRef to expose a ref of the wrapped component + forwardRef = false, + + // the context consumer to use + context = ReactReduxContext, + }: ConnectOptions = {} +): unknown { + if (process.env.NODE_ENV !== 'production') { + if (pure !== undefined && !hasWarnedAboutDeprecatedPureOption) { + hasWarnedAboutDeprecatedPureOption = true + warning( + 'The `pure` option has been removed. `connect` is now always a "pure/memoized" component' + ) + } + } + + const Context = context + + const initMapStateToProps = mapStateToPropsFactory(mapStateToProps) + const initMapDispatchToProps = mapDispatchToPropsFactory(mapDispatchToProps) + const initMergeProps = mergePropsFactory(mergeProps) + + const shouldHandleStateChanges = Boolean(mapStateToProps) + + const wrapWithConnect = ( + WrappedComponent: ComponentType + ) => { + type WrappedComponentProps = TProps & + ConnectPropsMaybeWithoutContext + + if ( + process.env.NODE_ENV !== 'production' && + !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: SelectorFactoryOptions< + any, + any, + any, + any, + State + > = { + shouldHandleStateChanges, + displayName, + wrappedComponentName, + WrappedComponent, + // @ts-ignore + initMapStateToProps, + // @ts-ignore + initMapDispatchToProps, + initMergeProps, + areStatesEqual, + areStatePropsEqual, + areOwnPropsEqual, + areMergedPropsEqual, + } + + function ConnectFunction( + props: InternalConnectProps & TOwnProps + ) { + const [propsContext, reactReduxForwardedRef, wrapperProps] = + 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, ...wrapperProps } = props + return [props.context, reactReduxForwardedRef, wrapperProps] + }, [props]) + + const ContextToUse: ReactReduxContextInstance = 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 + isContextConsumer() + ? propsContext + : Context + }, [propsContext, Context]) + + // Retrieve the store and ancestor subscription via context, if available + const contextValue = 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 , ` + + `or pass a custom React context provider to and the corresponding ` + + `React context consumer to ${displayName} in connect options.` + ) + } + + // Based on the previous check, one of these must be true + const store: Store = didStoreComeFromProps + ? props.store! + : contextValue!.store + + const getServerState = didStoreComeFromContext + ? contextValue.getServerState + : store.getState + + const childPropsSelector = useMemo(() => { + // The child props selector needs the store reference as an input. + // Re-create this selector whenever the store changes. + return defaultSelectorFactory(store.dispatch, selectorFactoryOptions) + }, [store]) + + const [subscription, notifyNestedSubs] = 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 = 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 = 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 { + ...contextValue, + subscription, + } as ReactReduxContextValue + }, [didStoreComeFromProps, contextValue, subscription]) + + // Set up refs to coordinate values between the subscription effect and the render logic + const lastChildProps = useRef() + const lastWrapperProps = useRef(wrapperProps) + const childPropsFromStoreUpdate = useRef() + const renderIsScheduled = useRef(false) + const isProcessingDispatch = useRef(false) + const isMounted = useRef(false) + + const latestSubscriptionCallbackError = useRef() + + useIsomorphicLayoutEffect(() => { + isMounted.current = true + return () => { + isMounted.current = false + } + }, []) + + const actualChildPropsSelector = 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 = useMemo(() => { + const subscribe = (reactListener: () => void) => { + 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: Record + + 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 as Error + ).message += `\nThe error may be correlated with this previous error:\n${latestSubscriptionCallbackError.current.stack}\n\n` + } + + throw err + } + + 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 = useMemo(() => { + return ( + // @ts-ignore + + ) + }, [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 = 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 ( + + {renderedWrappedComponent} + + ) + } + + return renderedWrappedComponent + }, [ContextToUse, renderedWrappedComponent, overriddenContextValue]) + + return renderedChild + } + + const _Connect = React.memo(ConnectFunction) + + type ConnectedWrapperComponent = typeof _Connect & { + WrappedComponent: typeof WrappedComponent + } + + // Add a hacky cast to get the right output type + const Connect = _Connect as unknown as ConnectedComponent< + typeof WrappedComponent, + WrappedComponentProps + > + Connect.WrappedComponent = WrappedComponent + Connect.displayName = ConnectFunction.displayName = displayName + + if (forwardRef) { + const _forwarded = React.forwardRef(function forwardConnectRef( + props, + ref + ) { + // @ts-ignore + return + }) + + const forwarded = _forwarded as ConnectedWrapperComponent + forwarded.displayName = displayName + forwarded.WrappedComponent = WrappedComponent + return hoistStatics(forwarded, WrappedComponent) + } + + return hoistStatics(Connect, WrappedComponent) + } + + return wrapWithConnect +} + +export default connect as Connect diff --git a/.yalc/react-redux/src/connect/invalidArgFactory.ts b/.yalc/react-redux/src/connect/invalidArgFactory.ts old mode 100644 new mode 100755 index 4129320..f376b96 --- a/.yalc/react-redux/src/connect/invalidArgFactory.ts +++ b/.yalc/react-redux/src/connect/invalidArgFactory.ts @@ -1,14 +1,14 @@ -import type { Action, Dispatch } from 'redux' - -export function createInvalidArgFactory(arg: unknown, name: string) { - return ( - dispatch: Dispatch>, - options: { readonly wrappedComponentName: string } - ) => { - throw new Error( - `Invalid value of type ${typeof arg} for ${name} argument when connecting component ${ - options.wrappedComponentName - }.` - ) - } -} +import type { Action, Dispatch } from 'redux' + +export function createInvalidArgFactory(arg: unknown, name: string) { + return ( + dispatch: Dispatch>, + options: { readonly wrappedComponentName: string } + ) => { + throw new Error( + `Invalid value of type ${typeof arg} for ${name} argument when connecting component ${ + options.wrappedComponentName + }.` + ) + } +} diff --git a/.yalc/react-redux/src/connect/mapDispatchToProps.ts b/.yalc/react-redux/src/connect/mapDispatchToProps.ts old mode 100644 new mode 100755 index 48c5b05..f84b8ac --- a/.yalc/react-redux/src/connect/mapDispatchToProps.ts +++ b/.yalc/react-redux/src/connect/mapDispatchToProps.ts @@ -1,25 +1,25 @@ -import type { Action, Dispatch } from 'redux' -import bindActionCreators from '../utils/bindActionCreators' -import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps' -import { createInvalidArgFactory } from './invalidArgFactory' -import type { MapDispatchToPropsParam } from './selectorFactory' - -export function mapDispatchToPropsFactory( - mapDispatchToProps: - | MapDispatchToPropsParam - | undefined -) { - return mapDispatchToProps && typeof mapDispatchToProps === 'object' - ? wrapMapToPropsConstant((dispatch: Dispatch>) => - // @ts-ignore - bindActionCreators(mapDispatchToProps, dispatch) - ) - : !mapDispatchToProps - ? wrapMapToPropsConstant((dispatch: Dispatch>) => ({ - dispatch, - })) - : typeof mapDispatchToProps === 'function' - ? // @ts-ignore - wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps') - : createInvalidArgFactory(mapDispatchToProps, 'mapDispatchToProps') -} +import type { Action, Dispatch } from 'redux' +import bindActionCreators from '../utils/bindActionCreators' +import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps' +import { createInvalidArgFactory } from './invalidArgFactory' +import type { MapDispatchToPropsParam } from './selectorFactory' + +export function mapDispatchToPropsFactory( + mapDispatchToProps: + | MapDispatchToPropsParam + | undefined +) { + return mapDispatchToProps && typeof mapDispatchToProps === 'object' + ? wrapMapToPropsConstant((dispatch: Dispatch>) => + // @ts-ignore + bindActionCreators(mapDispatchToProps, dispatch) + ) + : !mapDispatchToProps + ? wrapMapToPropsConstant((dispatch: Dispatch>) => ({ + dispatch, + })) + : typeof mapDispatchToProps === 'function' + ? // @ts-ignore + wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps') + : createInvalidArgFactory(mapDispatchToProps, 'mapDispatchToProps') +} diff --git a/.yalc/react-redux/src/connect/mapStateToProps.ts b/.yalc/react-redux/src/connect/mapStateToProps.ts old mode 100644 new mode 100755 index fbf8a3e..416dfc0 --- a/.yalc/react-redux/src/connect/mapStateToProps.ts +++ b/.yalc/react-redux/src/connect/mapStateToProps.ts @@ -1,14 +1,14 @@ -import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps' -import { createInvalidArgFactory } from './invalidArgFactory' -import type { MapStateToPropsParam } from './selectorFactory' - -export function mapStateToPropsFactory( - mapStateToProps: MapStateToPropsParam -) { - return !mapStateToProps - ? wrapMapToPropsConstant(() => ({})) - : typeof mapStateToProps === 'function' - ? // @ts-ignore - wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps') - : createInvalidArgFactory(mapStateToProps, 'mapStateToProps') -} +import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps' +import { createInvalidArgFactory } from './invalidArgFactory' +import type { MapStateToPropsParam } from './selectorFactory' + +export function mapStateToPropsFactory( + mapStateToProps: MapStateToPropsParam +) { + return !mapStateToProps + ? wrapMapToPropsConstant(() => ({})) + : typeof mapStateToProps === 'function' + ? // @ts-ignore + wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps') + : createInvalidArgFactory(mapStateToProps, 'mapStateToProps') +} diff --git a/.yalc/react-redux/src/connect/mergeProps.ts b/.yalc/react-redux/src/connect/mergeProps.ts old mode 100644 new mode 100755 index c62027c..b91bcb8 --- a/.yalc/react-redux/src/connect/mergeProps.ts +++ b/.yalc/react-redux/src/connect/mergeProps.ts @@ -1,78 +1,78 @@ -import type { Action, Dispatch } from 'redux' -import verifyPlainObject from '../utils/verifyPlainObject' -import { createInvalidArgFactory } from './invalidArgFactory' -import type { MergeProps } from './selectorFactory' -import type { EqualityFn } from '../types' - -export function defaultMergeProps< - TStateProps, - TDispatchProps, - TOwnProps, - TMergedProps ->( - stateProps: TStateProps, - dispatchProps: TDispatchProps, - ownProps: TOwnProps -): TMergedProps { - // @ts-ignore - return { ...ownProps, ...stateProps, ...dispatchProps } -} - -export function wrapMergePropsFunc< - TStateProps, - TDispatchProps, - TOwnProps, - TMergedProps ->( - mergeProps: MergeProps -): ( - dispatch: Dispatch>, - options: { - readonly displayName: string - readonly areMergedPropsEqual: EqualityFn - } -) => MergeProps { - return function initMergePropsProxy( - dispatch, - { displayName, areMergedPropsEqual } - ) { - let hasRunOnce = false - let mergedProps: TMergedProps - - return function mergePropsProxy( - stateProps: TStateProps, - dispatchProps: TDispatchProps, - ownProps: TOwnProps - ) { - const nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps) - - if (hasRunOnce) { - if (!areMergedPropsEqual(nextMergedProps, mergedProps)) - mergedProps = nextMergedProps - } else { - hasRunOnce = true - mergedProps = nextMergedProps - - if (process.env.NODE_ENV !== 'production') - verifyPlainObject(mergedProps, displayName, 'mergeProps') - } - - return mergedProps - } - } -} - -export function mergePropsFactory< - TStateProps, - TDispatchProps, - TOwnProps, - TMergedProps ->( - mergeProps?: MergeProps -) { - return !mergeProps - ? () => defaultMergeProps - : typeof mergeProps === 'function' - ? wrapMergePropsFunc(mergeProps) - : createInvalidArgFactory(mergeProps, 'mergeProps') -} +import type { Action, Dispatch } from 'redux' +import verifyPlainObject from '../utils/verifyPlainObject' +import { createInvalidArgFactory } from './invalidArgFactory' +import type { MergeProps } from './selectorFactory' +import type { EqualityFn } from '../types' + +export function defaultMergeProps< + TStateProps, + TDispatchProps, + TOwnProps, + TMergedProps +>( + stateProps: TStateProps, + dispatchProps: TDispatchProps, + ownProps: TOwnProps +): TMergedProps { + // @ts-ignore + return { ...ownProps, ...stateProps, ...dispatchProps } +} + +export function wrapMergePropsFunc< + TStateProps, + TDispatchProps, + TOwnProps, + TMergedProps +>( + mergeProps: MergeProps +): ( + dispatch: Dispatch>, + options: { + readonly displayName: string + readonly areMergedPropsEqual: EqualityFn + } +) => MergeProps { + return function initMergePropsProxy( + dispatch, + { displayName, areMergedPropsEqual } + ) { + let hasRunOnce = false + let mergedProps: TMergedProps + + return function mergePropsProxy( + stateProps: TStateProps, + dispatchProps: TDispatchProps, + ownProps: TOwnProps + ) { + const nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps) + + if (hasRunOnce) { + if (!areMergedPropsEqual(nextMergedProps, mergedProps)) + mergedProps = nextMergedProps + } else { + hasRunOnce = true + mergedProps = nextMergedProps + + if (process.env.NODE_ENV !== 'production') + verifyPlainObject(mergedProps, displayName, 'mergeProps') + } + + return mergedProps + } + } +} + +export function mergePropsFactory< + TStateProps, + TDispatchProps, + TOwnProps, + TMergedProps +>( + mergeProps?: MergeProps +) { + return !mergeProps + ? () => defaultMergeProps + : typeof mergeProps === 'function' + ? wrapMergePropsFunc(mergeProps) + : createInvalidArgFactory(mergeProps, 'mergeProps') +} diff --git a/.yalc/react-redux/src/connect/selectorFactory.ts b/.yalc/react-redux/src/connect/selectorFactory.ts old mode 100644 new mode 100755 index 1162063..fadb389 --- a/.yalc/react-redux/src/connect/selectorFactory.ts +++ b/.yalc/react-redux/src/connect/selectorFactory.ts @@ -1,242 +1,242 @@ -import type { Dispatch, Action } from 'redux' -import type { ComponentType } from 'react' -import verifySubselectors from './verifySubselectors' -import type { EqualityFn, ExtendedEqualityFn } from '../types' - -export type SelectorFactory = ( - dispatch: Dispatch>, - factoryOptions: TFactoryOptions -) => Selector - -export type Selector = TOwnProps extends - | null - | undefined - ? (state: S) => TProps - : (state: S, ownProps: TOwnProps) => TProps - -export type MapStateToProps = ( - state: State, - ownProps: TOwnProps -) => TStateProps - -export type MapStateToPropsFactory = ( - initialState: State, - ownProps: TOwnProps -) => MapStateToProps - -export type MapStateToPropsParam = - | MapStateToPropsFactory - | MapStateToProps - | null - | undefined - -export type MapDispatchToPropsFunction = ( - dispatch: Dispatch>, - ownProps: TOwnProps -) => TDispatchProps - -export type MapDispatchToProps = - | MapDispatchToPropsFunction - | TDispatchProps - -export type MapDispatchToPropsFactory = ( - dispatch: Dispatch>, - ownProps: TOwnProps -) => MapDispatchToPropsFunction - -export type MapDispatchToPropsParam = - | MapDispatchToPropsFactory - | MapDispatchToProps - -export type MapDispatchToPropsNonObject = - | MapDispatchToPropsFactory - | MapDispatchToPropsFunction - -export type MergeProps = ( - stateProps: TStateProps, - dispatchProps: TDispatchProps, - ownProps: TOwnProps -) => TMergedProps - -interface PureSelectorFactoryComparisonOptions { - readonly areStatesEqual: ExtendedEqualityFn - readonly areStatePropsEqual: EqualityFn - readonly areOwnPropsEqual: EqualityFn -} - -export function pureFinalPropsSelectorFactory< - TStateProps, - TOwnProps, - TDispatchProps, - TMergedProps, - State ->( - mapStateToProps: WrappedMapStateToProps, - mapDispatchToProps: WrappedMapDispatchToProps, - mergeProps: MergeProps, - dispatch: Dispatch>, - { - areStatesEqual, - areOwnPropsEqual, - areStatePropsEqual, - }: PureSelectorFactoryComparisonOptions -) { - let hasRunAtLeastOnce = false - let state: State - let ownProps: TOwnProps - let stateProps: TStateProps - let dispatchProps: TDispatchProps - let mergedProps: TMergedProps - - function handleFirstCall(firstState: State, firstOwnProps: TOwnProps) { - state = firstState - ownProps = firstOwnProps - stateProps = mapStateToProps(state, ownProps) - dispatchProps = mapDispatchToProps(dispatch, ownProps) - mergedProps = mergeProps(stateProps, dispatchProps, ownProps) - hasRunAtLeastOnce = true - return mergedProps - } - - function handleNewPropsAndNewState() { - stateProps = mapStateToProps(state, ownProps) - - if (mapDispatchToProps.dependsOnOwnProps) - dispatchProps = mapDispatchToProps(dispatch, ownProps) - - mergedProps = mergeProps(stateProps, dispatchProps, ownProps) - return mergedProps - } - - function handleNewProps() { - if (mapStateToProps.dependsOnOwnProps) - stateProps = mapStateToProps(state, ownProps) - - if (mapDispatchToProps.dependsOnOwnProps) - dispatchProps = mapDispatchToProps(dispatch, ownProps) - - mergedProps = mergeProps(stateProps, dispatchProps, ownProps) - return mergedProps - } - - function handleNewState() { - const nextStateProps = mapStateToProps(state, ownProps) - const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps) - stateProps = nextStateProps - - if (statePropsChanged) - mergedProps = mergeProps(stateProps, dispatchProps, ownProps) - - return mergedProps - } - - function handleSubsequentCalls(nextState: State, nextOwnProps: TOwnProps) { - const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps) - const stateChanged = !areStatesEqual( - nextState, - state, - nextOwnProps, - ownProps - ) - state = nextState - ownProps = nextOwnProps - - if (propsChanged && stateChanged) return handleNewPropsAndNewState() - if (propsChanged) return handleNewProps() - if (stateChanged) return handleNewState() - return mergedProps - } - - return function pureFinalPropsSelector( - nextState: State, - nextOwnProps: TOwnProps - ) { - return hasRunAtLeastOnce - ? handleSubsequentCalls(nextState, nextOwnProps) - : handleFirstCall(nextState, nextOwnProps) - } -} - -interface WrappedMapStateToProps { - (state: State, ownProps: TOwnProps): TStateProps - readonly dependsOnOwnProps: boolean -} - -interface WrappedMapDispatchToProps { - (dispatch: Dispatch>, ownProps: TOwnProps): TDispatchProps - readonly dependsOnOwnProps: boolean -} - -export interface InitOptions - extends PureSelectorFactoryComparisonOptions { - readonly shouldHandleStateChanges: boolean - readonly displayName: string - readonly wrappedComponentName: string - readonly WrappedComponent: ComponentType - readonly areMergedPropsEqual: EqualityFn -} - -export interface SelectorFactoryOptions< - TStateProps, - TOwnProps, - TDispatchProps, - TMergedProps, - State -> extends InitOptions { - readonly initMapStateToProps: ( - dispatch: Dispatch>, - options: InitOptions - ) => WrappedMapStateToProps - readonly initMapDispatchToProps: ( - dispatch: Dispatch>, - options: InitOptions - ) => WrappedMapDispatchToProps - readonly initMergeProps: ( - dispatch: Dispatch>, - options: InitOptions - ) => MergeProps -} - -// TODO: Add more comments - -// The selector returned by selectorFactory will memoize its results, -// allowing connect's shouldComponentUpdate to return false if final -// props have not changed. - -export default function finalPropsSelectorFactory< - TStateProps, - TOwnProps, - TDispatchProps, - TMergedProps, - State ->( - dispatch: Dispatch>, - { - initMapStateToProps, - initMapDispatchToProps, - initMergeProps, - ...options - }: SelectorFactoryOptions< - TStateProps, - TOwnProps, - TDispatchProps, - TMergedProps, - State - > -) { - const mapStateToProps = initMapStateToProps(dispatch, options) - const mapDispatchToProps = initMapDispatchToProps(dispatch, options) - const mergeProps = initMergeProps(dispatch, options) - - if (process.env.NODE_ENV !== 'production') { - verifySubselectors(mapStateToProps, mapDispatchToProps, mergeProps) - } - - return pureFinalPropsSelectorFactory< - TStateProps, - TOwnProps, - TDispatchProps, - TMergedProps, - State - >(mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options) -} +import type { Dispatch, Action } from 'redux' +import type { ComponentType } from 'react' +import verifySubselectors from './verifySubselectors' +import type { EqualityFn, ExtendedEqualityFn } from '../types' + +export type SelectorFactory = ( + dispatch: Dispatch>, + factoryOptions: TFactoryOptions +) => Selector + +export type Selector = TOwnProps extends + | null + | undefined + ? (state: S) => TProps + : (state: S, ownProps: TOwnProps) => TProps + +export type MapStateToProps = ( + state: State, + ownProps: TOwnProps +) => TStateProps + +export type MapStateToPropsFactory = ( + initialState: State, + ownProps: TOwnProps +) => MapStateToProps + +export type MapStateToPropsParam = + | MapStateToPropsFactory + | MapStateToProps + | null + | undefined + +export type MapDispatchToPropsFunction = ( + dispatch: Dispatch>, + ownProps: TOwnProps +) => TDispatchProps + +export type MapDispatchToProps = + | MapDispatchToPropsFunction + | TDispatchProps + +export type MapDispatchToPropsFactory = ( + dispatch: Dispatch>, + ownProps: TOwnProps +) => MapDispatchToPropsFunction + +export type MapDispatchToPropsParam = + | MapDispatchToPropsFactory + | MapDispatchToProps + +export type MapDispatchToPropsNonObject = + | MapDispatchToPropsFactory + | MapDispatchToPropsFunction + +export type MergeProps = ( + stateProps: TStateProps, + dispatchProps: TDispatchProps, + ownProps: TOwnProps +) => TMergedProps + +interface PureSelectorFactoryComparisonOptions { + readonly areStatesEqual: ExtendedEqualityFn + readonly areStatePropsEqual: EqualityFn + readonly areOwnPropsEqual: EqualityFn +} + +export function pureFinalPropsSelectorFactory< + TStateProps, + TOwnProps, + TDispatchProps, + TMergedProps, + State +>( + mapStateToProps: WrappedMapStateToProps, + mapDispatchToProps: WrappedMapDispatchToProps, + mergeProps: MergeProps, + dispatch: Dispatch>, + { + areStatesEqual, + areOwnPropsEqual, + areStatePropsEqual, + }: PureSelectorFactoryComparisonOptions +) { + let hasRunAtLeastOnce = false + let state: State + let ownProps: TOwnProps + let stateProps: TStateProps + let dispatchProps: TDispatchProps + let mergedProps: TMergedProps + + function handleFirstCall(firstState: State, firstOwnProps: TOwnProps) { + state = firstState + ownProps = firstOwnProps + stateProps = mapStateToProps(state, ownProps) + dispatchProps = mapDispatchToProps(dispatch, ownProps) + mergedProps = mergeProps(stateProps, dispatchProps, ownProps) + hasRunAtLeastOnce = true + return mergedProps + } + + function handleNewPropsAndNewState() { + stateProps = mapStateToProps(state, ownProps) + + if (mapDispatchToProps.dependsOnOwnProps) + dispatchProps = mapDispatchToProps(dispatch, ownProps) + + mergedProps = mergeProps(stateProps, dispatchProps, ownProps) + return mergedProps + } + + function handleNewProps() { + if (mapStateToProps.dependsOnOwnProps) + stateProps = mapStateToProps(state, ownProps) + + if (mapDispatchToProps.dependsOnOwnProps) + dispatchProps = mapDispatchToProps(dispatch, ownProps) + + mergedProps = mergeProps(stateProps, dispatchProps, ownProps) + return mergedProps + } + + function handleNewState() { + const nextStateProps = mapStateToProps(state, ownProps) + const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps) + stateProps = nextStateProps + + if (statePropsChanged) + mergedProps = mergeProps(stateProps, dispatchProps, ownProps) + + return mergedProps + } + + function handleSubsequentCalls(nextState: State, nextOwnProps: TOwnProps) { + const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps) + const stateChanged = !areStatesEqual( + nextState, + state, + nextOwnProps, + ownProps + ) + state = nextState + ownProps = nextOwnProps + + if (propsChanged && stateChanged) return handleNewPropsAndNewState() + if (propsChanged) return handleNewProps() + if (stateChanged) return handleNewState() + return mergedProps + } + + return function pureFinalPropsSelector( + nextState: State, + nextOwnProps: TOwnProps + ) { + return hasRunAtLeastOnce + ? handleSubsequentCalls(nextState, nextOwnProps) + : handleFirstCall(nextState, nextOwnProps) + } +} + +interface WrappedMapStateToProps { + (state: State, ownProps: TOwnProps): TStateProps + readonly dependsOnOwnProps: boolean +} + +interface WrappedMapDispatchToProps { + (dispatch: Dispatch>, ownProps: TOwnProps): TDispatchProps + readonly dependsOnOwnProps: boolean +} + +export interface InitOptions + extends PureSelectorFactoryComparisonOptions { + readonly shouldHandleStateChanges: boolean + readonly displayName: string + readonly wrappedComponentName: string + readonly WrappedComponent: ComponentType + readonly areMergedPropsEqual: EqualityFn +} + +export interface SelectorFactoryOptions< + TStateProps, + TOwnProps, + TDispatchProps, + TMergedProps, + State +> extends InitOptions { + readonly initMapStateToProps: ( + dispatch: Dispatch>, + options: InitOptions + ) => WrappedMapStateToProps + readonly initMapDispatchToProps: ( + dispatch: Dispatch>, + options: InitOptions + ) => WrappedMapDispatchToProps + readonly initMergeProps: ( + dispatch: Dispatch>, + options: InitOptions + ) => MergeProps +} + +// TODO: Add more comments + +// The selector returned by selectorFactory will memoize its results, +// allowing connect's shouldComponentUpdate to return false if final +// props have not changed. + +export default function finalPropsSelectorFactory< + TStateProps, + TOwnProps, + TDispatchProps, + TMergedProps, + State +>( + dispatch: Dispatch>, + { + initMapStateToProps, + initMapDispatchToProps, + initMergeProps, + ...options + }: SelectorFactoryOptions< + TStateProps, + TOwnProps, + TDispatchProps, + TMergedProps, + State + > +) { + const mapStateToProps = initMapStateToProps(dispatch, options) + const mapDispatchToProps = initMapDispatchToProps(dispatch, options) + const mergeProps = initMergeProps(dispatch, options) + + if (process.env.NODE_ENV !== 'production') { + verifySubselectors(mapStateToProps, mapDispatchToProps, mergeProps) + } + + return pureFinalPropsSelectorFactory< + TStateProps, + TOwnProps, + TDispatchProps, + TMergedProps, + State + >(mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options) +} diff --git a/.yalc/react-redux/src/connect/verifySubselectors.ts b/.yalc/react-redux/src/connect/verifySubselectors.ts old mode 100644 new mode 100755 index 487cdb1..10a5da0 --- a/.yalc/react-redux/src/connect/verifySubselectors.ts +++ b/.yalc/react-redux/src/connect/verifySubselectors.ts @@ -1,26 +1,26 @@ -import warning from '../utils/warning' - -function verify(selector: unknown, methodName: string): void { - if (!selector) { - throw new Error(`Unexpected value for ${methodName} in connect.`) - } else if ( - methodName === 'mapStateToProps' || - methodName === 'mapDispatchToProps' - ) { - if (!Object.prototype.hasOwnProperty.call(selector, 'dependsOnOwnProps')) { - warning( - `The selector for ${methodName} of connect did not specify a value for dependsOnOwnProps.` - ) - } - } -} - -export default function verifySubselectors( - mapStateToProps: unknown, - mapDispatchToProps: unknown, - mergeProps: unknown -): void { - verify(mapStateToProps, 'mapStateToProps') - verify(mapDispatchToProps, 'mapDispatchToProps') - verify(mergeProps, 'mergeProps') -} +import warning from '../utils/warning' + +function verify(selector: unknown, methodName: string): void { + if (!selector) { + throw new Error(`Unexpected value for ${methodName} in connect.`) + } else if ( + methodName === 'mapStateToProps' || + methodName === 'mapDispatchToProps' + ) { + if (!Object.prototype.hasOwnProperty.call(selector, 'dependsOnOwnProps')) { + warning( + `The selector for ${methodName} of connect did not specify a value for dependsOnOwnProps.` + ) + } + } +} + +export default function verifySubselectors( + mapStateToProps: unknown, + mapDispatchToProps: unknown, + mergeProps: unknown +): void { + verify(mapStateToProps, 'mapStateToProps') + verify(mapDispatchToProps, 'mapDispatchToProps') + verify(mergeProps, 'mergeProps') +} diff --git a/.yalc/react-redux/src/connect/wrapMapToProps.ts b/.yalc/react-redux/src/connect/wrapMapToProps.ts old mode 100644 new mode 100755 index 9dad1e7..4b86701 --- a/.yalc/react-redux/src/connect/wrapMapToProps.ts +++ b/.yalc/react-redux/src/connect/wrapMapToProps.ts @@ -1,110 +1,110 @@ -import type { ActionCreatorsMapObject, Dispatch, ActionCreator } from 'redux' - -import type { FixTypeLater } from '../types' -import verifyPlainObject from '../utils/verifyPlainObject' - -type AnyState = { [key: string]: any } -type StateOrDispatch = S | Dispatch - -type AnyProps = { [key: string]: any } - -export type MapToProps

= { - // eslint-disable-next-line no-unused-vars - (stateOrDispatch: StateOrDispatch, ownProps?: P): FixTypeLater - dependsOnOwnProps?: boolean -} - -export function wrapMapToPropsConstant( - // * Note: - // It seems that the dispatch argument - // could be a dispatch function in some cases (ex: whenMapDispatchToPropsIsMissing) - // and a state object in some others (ex: whenMapStateToPropsIsMissing) - // eslint-disable-next-line no-unused-vars - getConstant: (dispatch: Dispatch) => - | { - dispatch?: Dispatch - dependsOnOwnProps?: boolean - } - | ActionCreatorsMapObject - | ActionCreator -) { - return function initConstantSelector(dispatch: Dispatch) { - const constant = getConstant(dispatch) - - function constantSelector() { - return constant - } - constantSelector.dependsOnOwnProps = false - return constantSelector - } -} - -// dependsOnOwnProps is used by createMapToPropsProxy to determine whether to pass props as args -// to the mapToProps function being wrapped. It is also used by makePurePropsSelector to determine -// whether mapToProps needs to be invoked when props have changed. -// -// A length of one signals that mapToProps does not depend on props from the parent component. -// A length of zero is assumed to mean mapToProps is getting args via arguments or ...args and -// therefore not reporting its length accurately.. -// TODO Can this get pulled out so that we can subscribe directly to the store if we don't need ownProps? -export function getDependsOnOwnProps(mapToProps: MapToProps) { - return mapToProps.dependsOnOwnProps - ? Boolean(mapToProps.dependsOnOwnProps) - : mapToProps.length !== 1 -} - -// Used by whenMapStateToPropsIsFunction and whenMapDispatchToPropsIsFunction, -// this function wraps mapToProps in a proxy function which does several things: -// -// * Detects whether the mapToProps function being called depends on props, which -// is used by selectorFactory to decide if it should reinvoke on props changes. -// -// * On first call, handles mapToProps if returns another function, and treats that -// new function as the true mapToProps for subsequent calls. -// -// * On first call, verifies the first result is a plain object, in order to warn -// the developer that their mapToProps function is not returning a valid result. -// -export function wrapMapToPropsFunc

( - mapToProps: MapToProps, - methodName: string -) { - return function initProxySelector( - dispatch: Dispatch, - { displayName }: { displayName: string } - ) { - const proxy = function mapToPropsProxy( - stateOrDispatch: StateOrDispatch, - ownProps?: P - ): MapToProps { - return proxy.dependsOnOwnProps - ? proxy.mapToProps(stateOrDispatch, ownProps) - : proxy.mapToProps(stateOrDispatch, undefined) - } - - // allow detectFactoryAndVerify to get ownProps - proxy.dependsOnOwnProps = true - - proxy.mapToProps = function detectFactoryAndVerify( - stateOrDispatch: StateOrDispatch, - ownProps?: P - ): MapToProps { - proxy.mapToProps = mapToProps - proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps) - let props = proxy(stateOrDispatch, ownProps) - - if (typeof props === 'function') { - proxy.mapToProps = props - proxy.dependsOnOwnProps = getDependsOnOwnProps(props) - props = proxy(stateOrDispatch, ownProps) - } - - if (process.env.NODE_ENV !== 'production') - verifyPlainObject(props, displayName, methodName) - - return props - } - - return proxy - } -} +import type { ActionCreatorsMapObject, Dispatch, ActionCreator } from 'redux' + +import type { FixTypeLater } from '../types' +import verifyPlainObject from '../utils/verifyPlainObject' + +type AnyState = { [key: string]: any } +type StateOrDispatch = S | Dispatch + +type AnyProps = { [key: string]: any } + +export type MapToProps

= { + // eslint-disable-next-line no-unused-vars + (stateOrDispatch: StateOrDispatch, ownProps?: P): FixTypeLater + dependsOnOwnProps?: boolean +} + +export function wrapMapToPropsConstant( + // * Note: + // It seems that the dispatch argument + // could be a dispatch function in some cases (ex: whenMapDispatchToPropsIsMissing) + // and a state object in some others (ex: whenMapStateToPropsIsMissing) + // eslint-disable-next-line no-unused-vars + getConstant: (dispatch: Dispatch) => + | { + dispatch?: Dispatch + dependsOnOwnProps?: boolean + } + | ActionCreatorsMapObject + | ActionCreator +) { + return function initConstantSelector(dispatch: Dispatch) { + const constant = getConstant(dispatch) + + function constantSelector() { + return constant + } + constantSelector.dependsOnOwnProps = false + return constantSelector + } +} + +// dependsOnOwnProps is used by createMapToPropsProxy to determine whether to pass props as args +// to the mapToProps function being wrapped. It is also used by makePurePropsSelector to determine +// whether mapToProps needs to be invoked when props have changed. +// +// A length of one signals that mapToProps does not depend on props from the parent component. +// A length of zero is assumed to mean mapToProps is getting args via arguments or ...args and +// therefore not reporting its length accurately.. +// TODO Can this get pulled out so that we can subscribe directly to the store if we don't need ownProps? +export function getDependsOnOwnProps(mapToProps: MapToProps) { + return mapToProps.dependsOnOwnProps + ? Boolean(mapToProps.dependsOnOwnProps) + : mapToProps.length !== 1 +} + +// Used by whenMapStateToPropsIsFunction and whenMapDispatchToPropsIsFunction, +// this function wraps mapToProps in a proxy function which does several things: +// +// * Detects whether the mapToProps function being called depends on props, which +// is used by selectorFactory to decide if it should reinvoke on props changes. +// +// * On first call, handles mapToProps if returns another function, and treats that +// new function as the true mapToProps for subsequent calls. +// +// * On first call, verifies the first result is a plain object, in order to warn +// the developer that their mapToProps function is not returning a valid result. +// +export function wrapMapToPropsFunc

( + mapToProps: MapToProps, + methodName: string +) { + return function initProxySelector( + dispatch: Dispatch, + { displayName }: { displayName: string } + ) { + const proxy = function mapToPropsProxy( + stateOrDispatch: StateOrDispatch, + ownProps?: P + ): MapToProps { + return proxy.dependsOnOwnProps + ? proxy.mapToProps(stateOrDispatch, ownProps) + : proxy.mapToProps(stateOrDispatch, undefined) + } + + // allow detectFactoryAndVerify to get ownProps + proxy.dependsOnOwnProps = true + + proxy.mapToProps = function detectFactoryAndVerify( + stateOrDispatch: StateOrDispatch, + ownProps?: P + ): MapToProps { + proxy.mapToProps = mapToProps + proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps) + let props = proxy(stateOrDispatch, ownProps) + + if (typeof props === 'function') { + proxy.mapToProps = props + proxy.dependsOnOwnProps = getDependsOnOwnProps(props) + props = proxy(stateOrDispatch, ownProps) + } + + if (process.env.NODE_ENV !== 'production') + verifyPlainObject(props, displayName, methodName) + + return props + } + + return proxy + } +} diff --git a/.yalc/react-redux/src/exports.ts b/.yalc/react-redux/src/exports.ts old mode 100644 new mode 100755 index 5edb570..0a84eea --- a/.yalc/react-redux/src/exports.ts +++ b/.yalc/react-redux/src/exports.ts @@ -1,63 +1,63 @@ -import Provider from './components/Provider' -import type { ProviderProps } from './components/Provider' -import connect from './components/connect' -import type { - Connect, - ConnectProps, - ConnectedProps, -} from './components/connect' -import type { - SelectorFactory, - Selector, - MapStateToProps, - MapStateToPropsFactory, - MapStateToPropsParam, - MapDispatchToPropsFunction, - MapDispatchToProps, - MapDispatchToPropsFactory, - MapDispatchToPropsParam, - MapDispatchToPropsNonObject, - MergeProps, -} from './connect/selectorFactory' -import { ReactReduxContext } from './components/Context' -import type { ReactReduxContextValue } from './components/Context' - -import { useDispatch, createDispatchHook } from './hooks/useDispatch' -import { useSelector, createSelectorHook } from './hooks/useSelector' -import { useStore, createStoreHook } from './hooks/useStore' - -import shallowEqual from './utils/shallowEqual' -import type { Subscription } from './utils/Subscription' - -export * from './types' -export type { - ProviderProps, - SelectorFactory, - Selector, - MapStateToProps, - MapStateToPropsFactory, - MapStateToPropsParam, - Connect, - ConnectProps, - ConnectedProps, - MapDispatchToPropsFunction, - MapDispatchToProps, - MapDispatchToPropsFactory, - MapDispatchToPropsParam, - MapDispatchToPropsNonObject, - MergeProps, - ReactReduxContextValue, - Subscription, -} -export { - Provider, - ReactReduxContext, - connect, - useDispatch, - createDispatchHook, - useSelector, - createSelectorHook, - useStore, - createStoreHook, - shallowEqual, -} +import Provider from './components/Provider' +import type { ProviderProps } from './components/Provider' +import connect from './components/connect' +import type { + Connect, + ConnectProps, + ConnectedProps, +} from './components/connect' +import type { + SelectorFactory, + Selector, + MapStateToProps, + MapStateToPropsFactory, + MapStateToPropsParam, + MapDispatchToPropsFunction, + MapDispatchToProps, + MapDispatchToPropsFactory, + MapDispatchToPropsParam, + MapDispatchToPropsNonObject, + MergeProps, +} from './connect/selectorFactory' +import { ReactReduxContext } from './components/Context' +import type { ReactReduxContextValue } from './components/Context' + +import { useDispatch, createDispatchHook } from './hooks/useDispatch' +import { useSelector, createSelectorHook } from './hooks/useSelector' +import { useStore, createStoreHook } from './hooks/useStore' + +import shallowEqual from './utils/shallowEqual' +import type { Subscription } from './utils/Subscription' + +export * from './types' +export type { + ProviderProps, + SelectorFactory, + Selector, + MapStateToProps, + MapStateToPropsFactory, + MapStateToPropsParam, + Connect, + ConnectProps, + ConnectedProps, + MapDispatchToPropsFunction, + MapDispatchToProps, + MapDispatchToPropsFactory, + MapDispatchToPropsParam, + MapDispatchToPropsNonObject, + MergeProps, + ReactReduxContextValue, + Subscription, +} +export { + Provider, + ReactReduxContext, + connect, + useDispatch, + createDispatchHook, + useSelector, + createSelectorHook, + useStore, + createStoreHook, + shallowEqual, +} diff --git a/.yalc/react-redux/src/hooks/useDispatch.ts b/.yalc/react-redux/src/hooks/useDispatch.ts old mode 100644 new mode 100755 index f747e1b..1a6e9c4 --- a/.yalc/react-redux/src/hooks/useDispatch.ts +++ b/.yalc/react-redux/src/hooks/useDispatch.ts @@ -1,53 +1,53 @@ -import type { Action, AnyAction, Dispatch } from 'redux' -import type { Context } from 'react' - -import type { ReactReduxContextValue } from '../components/Context' -import { ReactReduxContext } from '../components/Context' -import { useStore as useDefaultStore, createStoreHook } from './useStore' - -/** - * Hook factory, which creates a `useDispatch` hook bound to a given context. - * - * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. - * @returns {Function} A `useDispatch` hook bound to the specified context. - */ -export function createDispatchHook< - S = unknown, - A extends Action = AnyAction - // @ts-ignore ->(context?: Context> = ReactReduxContext) { - const useStore = - // @ts-ignore - context === ReactReduxContext ? useDefaultStore : createStoreHook(context) - - return function useDispatch< - AppDispatch extends Dispatch = Dispatch - >(): AppDispatch { - const store = useStore() - // @ts-ignore - return store.dispatch - } -} - -/** - * A hook to access the redux `dispatch` function. - * - * @returns {any|function} redux store's `dispatch` function - * - * @example - * - * import React, { useCallback } from 'react' - * import { useDispatch } from 'react-redux' - * - * export const CounterComponent = ({ value }) => { - * const dispatch = useDispatch() - * const increaseCounter = useCallback(() => dispatch({ type: 'increase-counter' }), []) - * return ( - *

- * ) - * } - */ -export const useDispatch = /*#__PURE__*/ createDispatchHook() +import type { Action, AnyAction, Dispatch } from 'redux' +import type { Context } from 'react' + +import type { ReactReduxContextValue } from '../components/Context' +import { ReactReduxContext } from '../components/Context' +import { useStore as useDefaultStore, createStoreHook } from './useStore' + +/** + * Hook factory, which creates a `useDispatch` hook bound to a given context. + * + * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. + * @returns {Function} A `useDispatch` hook bound to the specified context. + */ +export function createDispatchHook< + S = unknown, + A extends Action = AnyAction + // @ts-ignore +>(context?: Context> = ReactReduxContext) { + const useStore = + // @ts-ignore + context === ReactReduxContext ? useDefaultStore : createStoreHook(context) + + return function useDispatch< + AppDispatch extends Dispatch = Dispatch + >(): AppDispatch { + const store = useStore() + // @ts-ignore + return store.dispatch + } +} + +/** + * A hook to access the redux `dispatch` function. + * + * @returns {any|function} redux store's `dispatch` function + * + * @example + * + * import React, { useCallback } from 'react' + * import { useDispatch } from 'react-redux' + * + * export const CounterComponent = ({ value }) => { + * const dispatch = useDispatch() + * const increaseCounter = useCallback(() => dispatch({ type: 'increase-counter' }), []) + * return ( + *
+ * {value} + * + *
+ * ) + * } + */ +export const useDispatch = /*#__PURE__*/ createDispatchHook() diff --git a/.yalc/react-redux/src/hooks/useReduxContext.ts b/.yalc/react-redux/src/hooks/useReduxContext.ts old mode 100644 new mode 100755 index 1e0f388..32b3626 --- a/.yalc/react-redux/src/hooks/useReduxContext.ts +++ b/.yalc/react-redux/src/hooks/useReduxContext.ts @@ -1,42 +1,42 @@ -import { useContext } from 'react' -import { ReactReduxContext } from '../components/Context' -import type { ReactReduxContextValue } from '../components/Context' - -/** - * Hook factory, which creates a `useReduxContext` hook bound to a given context. This is a low-level - * hook that you should usually not need to call directly. - * - * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. - * @returns {Function} A `useReduxContext` hook bound to the specified context. - */ -export function createReduxContextHook(context = ReactReduxContext) { - return function useReduxContext(): ReactReduxContextValue | null { - const contextValue = useContext(context) - - if (process.env.NODE_ENV !== 'production' && !contextValue) { - throw new Error( - 'could not find react-redux context value; please ensure the component is wrapped in a ' - ) - } - - return contextValue - } -} - -/** - * A hook to access the value of the `ReactReduxContext`. This is a low-level - * hook that you should usually not need to call directly. - * - * @returns {any} the value of the `ReactReduxContext` - * - * @example - * - * import React from 'react' - * import { useReduxContext } from 'react-redux' - * - * export const CounterComponent = () => { - * const { store } = useReduxContext() - * return
{store.getState()}
- * } - */ -export const useReduxContext = /*#__PURE__*/ createReduxContextHook() +import { useContext } from 'react' +import { ReactReduxContext } from '../components/Context' +import type { ReactReduxContextValue } from '../components/Context' + +/** + * Hook factory, which creates a `useReduxContext` hook bound to a given context. This is a low-level + * hook that you should usually not need to call directly. + * + * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. + * @returns {Function} A `useReduxContext` hook bound to the specified context. + */ +export function createReduxContextHook(context = ReactReduxContext) { + return function useReduxContext(): ReactReduxContextValue | null { + const contextValue = useContext(context) + + if (process.env.NODE_ENV !== 'production' && !contextValue) { + throw new Error( + 'could not find react-redux context value; please ensure the component is wrapped in a ' + ) + } + + return contextValue + } +} + +/** + * A hook to access the value of the `ReactReduxContext`. This is a low-level + * hook that you should usually not need to call directly. + * + * @returns {any} the value of the `ReactReduxContext` + * + * @example + * + * import React from 'react' + * import { useReduxContext } from 'react-redux' + * + * export const CounterComponent = () => { + * const { store } = useReduxContext() + * return
{store.getState()}
+ * } + */ +export const useReduxContext = /*#__PURE__*/ createReduxContextHook() diff --git a/.yalc/react-redux/src/hooks/useSelector.ts b/.yalc/react-redux/src/hooks/useSelector.ts old mode 100644 new mode 100755 index 4241edd..5a8a50f --- a/.yalc/react-redux/src/hooks/useSelector.ts +++ b/.yalc/react-redux/src/hooks/useSelector.ts @@ -1,221 +1,221 @@ -import { useCallback, useDebugValue, useMemo, useRef } from 'react' - -import { - createReduxContextHook, - useReduxContext as useDefaultReduxContext, -} from './useReduxContext' -import { ReactReduxContext } from '../components/Context' -import type { EqualityFn, NoInfer } from '../types' -import type { uSESWS } from '../utils/useSyncExternalStore' -import { notInitialized } from '../utils/useSyncExternalStore' -import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect' -import { createCache } from '../utils/autotracking/autotracking' -import { CacheWrapper } from '../utils/Subscription' - -export type CheckFrequency = 'never' | 'once' | 'always' - -export interface UseSelectorOptions { - equalityFn?: EqualityFn - stabilityCheck?: CheckFrequency - noopCheck?: CheckFrequency -} - -export interface UseSelector { - ( - selector: (state: TState) => Selected, - equalityFn?: EqualityFn - ): Selected - ( - selector: (state: TState) => Selected, - options?: UseSelectorOptions - ): Selected -} - -let useSyncExternalStoreWithSelector = notInitialized as uSESWS -export const initializeUseSelector = (fn: uSESWS) => { - useSyncExternalStoreWithSelector = fn -} - -const refEquality: EqualityFn = (a, b) => a === b - -/** - * Hook factory, which creates a `useSelector` hook bound to a given context. - * - * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. - * @returns {Function} A `useSelector` hook bound to the specified context. - */ -export function createSelectorHook(context = ReactReduxContext): UseSelector { - const useReduxContext = - context === ReactReduxContext - ? useDefaultReduxContext - : createReduxContextHook(context) - - return function useSelector( - selector: (state: TState) => Selected, - equalityFnOrOptions: - | EqualityFn> - | UseSelectorOptions> = {} - ): Selected { - const { - equalityFn = refEquality, - stabilityCheck = undefined, - noopCheck = undefined, - } = typeof equalityFnOrOptions === 'function' - ? { equalityFn: equalityFnOrOptions } - : equalityFnOrOptions - 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` - ) - } - } - - const { - store, - subscription, - getServerState, - stabilityCheck: globalStabilityCheck, - noopCheck: globalNoopCheck, - trackingNode, - } = useReduxContext()! - - const firstRun = useRef(true) - - const wrappedSelector = useCallback( - { - [selector.name](state: TState) { - const selected = selector(state) - if (process.env.NODE_ENV !== 'production') { - const finalStabilityCheck = - typeof stabilityCheck === 'undefined' - ? globalStabilityCheck - : stabilityCheck - if ( - finalStabilityCheck === 'always' || - (finalStabilityCheck === 'once' && firstRun.current) - ) { - const toCompare = selector(state) - if (!equalityFn(selected, toCompare)) { - 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', - { - state, - selected, - selected2: toCompare, - } - ) - } - } - const finalNoopCheck = - typeof noopCheck === 'undefined' ? globalNoopCheck : noopCheck - if ( - finalNoopCheck === 'always' || - (finalNoopCheck === 'once' && firstRun.current) - ) { - // @ts-ignore - if (selected === state) { - 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.' - ) - } - } - if (firstRun.current) firstRun.current = false - } - return selected - }, - }[selector.name], - [selector, globalStabilityCheck, stabilityCheck] - ) - - const latestWrappedSelectorRef = useRef(wrappedSelector) - - // console.log( - // 'Writing latest selector. Same reference? ', - // wrappedSelector === latestWrappedSelectorRef.current - // ) - latestWrappedSelectorRef.current = wrappedSelector - - const cache = useMemo(() => { - //console.log('Recreating cache') - const cache = createCache(() => { - // console.log('Wrapper cache called: ', store.getState()) - //return latestWrappedSelectorRef.current(trackingNode.proxy as TState) - return wrappedSelector(trackingNode.proxy as TState) - }) - return cache - }, [trackingNode, wrappedSelector]) - - const cacheWrapper = useRef({ cache } as CacheWrapper) - - useIsomorphicLayoutEffect(() => { - cacheWrapper.current.cache = cache - }) - - const subscribeToStore = useMemo(() => { - const subscribeToStore = (onStoreChange: () => void) => { - const wrappedOnStoreChange = () => { - // console.log('wrappedOnStoreChange') - return onStoreChange() - } - // console.log('Subscribing to store with tracking') - return subscription.addNestedSub(wrappedOnStoreChange, { - trigger: 'tracked', - cache: cacheWrapper.current, - }) - } - return subscribeToStore - }, [subscription]) - - const selectedState = useSyncExternalStoreWithSelector( - //subscription.addNestedSub, - subscribeToStore, - store.getState, - //() => trackingNode.proxy as TState, - getServerState || store.getState, - cache.getValue, - equalityFn - ) - - 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
{counter}
- * } - */ -export const useSelector = /*#__PURE__*/ createSelectorHook() +import { useCallback, useDebugValue, useMemo, useRef } from 'react' + +import { + createReduxContextHook, + useReduxContext as useDefaultReduxContext, +} from './useReduxContext' +import { ReactReduxContext } from '../components/Context' +import type { EqualityFn, NoInfer } from '../types' +import type { uSESWS } from '../utils/useSyncExternalStore' +import { notInitialized } from '../utils/useSyncExternalStore' +import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect' +import { createCache } from '../utils/autotracking/autotracking' +import { CacheWrapper } from '../utils/Subscription' + +export type CheckFrequency = 'never' | 'once' | 'always' + +export interface UseSelectorOptions { + equalityFn?: EqualityFn + stabilityCheck?: CheckFrequency + noopCheck?: CheckFrequency +} + +export interface UseSelector { + ( + selector: (state: TState) => Selected, + equalityFn?: EqualityFn + ): Selected + ( + selector: (state: TState) => Selected, + options?: UseSelectorOptions + ): Selected +} + +let useSyncExternalStoreWithSelector = notInitialized as uSESWS +export const initializeUseSelector = (fn: uSESWS) => { + useSyncExternalStoreWithSelector = fn +} + +const refEquality: EqualityFn = (a, b) => a === b + +/** + * Hook factory, which creates a `useSelector` hook bound to a given context. + * + * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. + * @returns {Function} A `useSelector` hook bound to the specified context. + */ +export function createSelectorHook(context = ReactReduxContext): UseSelector { + const useReduxContext = + context === ReactReduxContext + ? useDefaultReduxContext + : createReduxContextHook(context) + + return function useSelector( + selector: (state: TState) => Selected, + equalityFnOrOptions: + | EqualityFn> + | UseSelectorOptions> = {} + ): Selected { + const { + equalityFn = refEquality, + stabilityCheck = undefined, + noopCheck = undefined, + } = typeof equalityFnOrOptions === 'function' + ? { equalityFn: equalityFnOrOptions } + : equalityFnOrOptions + 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` + ) + } + } + + const { + store, + subscription, + getServerState, + stabilityCheck: globalStabilityCheck, + noopCheck: globalNoopCheck, + trackingNode, + } = useReduxContext()! + + const firstRun = useRef(true) + + const wrappedSelector = useCallback( + { + [selector.name](state: TState) { + const selected = selector(state) + if (process.env.NODE_ENV !== 'production') { + const finalStabilityCheck = + typeof stabilityCheck === 'undefined' + ? globalStabilityCheck + : stabilityCheck + if ( + finalStabilityCheck === 'always' || + (finalStabilityCheck === 'once' && firstRun.current) + ) { + const toCompare = selector(state) + if (!equalityFn(selected, toCompare)) { + 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', + { + state, + selected, + selected2: toCompare, + } + ) + } + } + const finalNoopCheck = + typeof noopCheck === 'undefined' ? globalNoopCheck : noopCheck + if ( + finalNoopCheck === 'always' || + (finalNoopCheck === 'once' && firstRun.current) + ) { + // @ts-ignore + if (selected === state) { + 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.' + ) + } + } + if (firstRun.current) firstRun.current = false + } + return selected + }, + }[selector.name], + [selector, globalStabilityCheck, stabilityCheck] + ) + + const latestWrappedSelectorRef = useRef(wrappedSelector) + + // console.log( + // 'Writing latest selector. Same reference? ', + // wrappedSelector === latestWrappedSelectorRef.current + // ) + latestWrappedSelectorRef.current = wrappedSelector + + const cache = useMemo(() => { + //console.log('Recreating cache') + const cache = createCache(() => { + // console.log('Wrapper cache called: ', store.getState()) + //return latestWrappedSelectorRef.current(trackingNode.proxy as TState) + return wrappedSelector(trackingNode.proxy as TState) + }) + return cache + }, [trackingNode, wrappedSelector]) + + const cacheWrapper = useRef({ cache } as CacheWrapper) + + useIsomorphicLayoutEffect(() => { + cacheWrapper.current.cache = cache + }) + + const subscribeToStore = useMemo(() => { + const subscribeToStore = (onStoreChange: () => void) => { + const wrappedOnStoreChange = () => { + // console.log('wrappedOnStoreChange') + return onStoreChange() + } + // console.log('Subscribing to store with tracking') + return subscription.addNestedSub(wrappedOnStoreChange, { + trigger: 'tracked', + cache: cacheWrapper.current, + }) + } + return subscribeToStore + }, [subscription]) + + const selectedState = useSyncExternalStoreWithSelector( + //subscription.addNestedSub, + subscribeToStore, + store.getState, + //() => trackingNode.proxy as TState, + getServerState || store.getState, + cache.getValue, + equalityFn + ) + + 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
{counter}
+ * } + */ +export const useSelector = /*#__PURE__*/ createSelectorHook() diff --git a/.yalc/react-redux/src/hooks/useStore.ts b/.yalc/react-redux/src/hooks/useStore.ts old mode 100644 new mode 100755 index 1d92e39..13b38cc --- a/.yalc/react-redux/src/hooks/useStore.ts +++ b/.yalc/react-redux/src/hooks/useStore.ts @@ -1,53 +1,53 @@ -import type { Context } from 'react' -import type { Action as BasicAction, AnyAction, Store } from 'redux' -import type { ReactReduxContextValue } from '../components/Context' -import { ReactReduxContext } from '../components/Context' -import { - useReduxContext as useDefaultReduxContext, - createReduxContextHook, -} from './useReduxContext' - -/** - * Hook factory, which creates a `useStore` hook bound to a given context. - * - * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. - * @returns {Function} A `useStore` hook bound to the specified context. - */ -export function createStoreHook< - S = unknown, - A extends BasicAction = AnyAction - // @ts-ignore ->(context?: Context> = ReactReduxContext) { - const useReduxContext = - // @ts-ignore - context === ReactReduxContext - ? useDefaultReduxContext - : // @ts-ignore - createReduxContextHook(context) - return function useStore< - State = S, - Action extends BasicAction = A - // @ts-ignore - >() { - const { store } = useReduxContext()! - // @ts-ignore - return store as Store - } -} - -/** - * A hook to access the redux store. - * - * @returns {any} the redux store - * - * @example - * - * import React from 'react' - * import { useStore } from 'react-redux' - * - * export const ExampleComponent = () => { - * const store = useStore() - * return
{store.getState()}
- * } - */ -export const useStore = /*#__PURE__*/ createStoreHook() +import type { Context } from 'react' +import type { Action as BasicAction, AnyAction, Store } from 'redux' +import type { ReactReduxContextValue } from '../components/Context' +import { ReactReduxContext } from '../components/Context' +import { + useReduxContext as useDefaultReduxContext, + createReduxContextHook, +} from './useReduxContext' + +/** + * Hook factory, which creates a `useStore` hook bound to a given context. + * + * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. + * @returns {Function} A `useStore` hook bound to the specified context. + */ +export function createStoreHook< + S = unknown, + A extends BasicAction = AnyAction + // @ts-ignore +>(context?: Context> = ReactReduxContext) { + const useReduxContext = + // @ts-ignore + context === ReactReduxContext + ? useDefaultReduxContext + : // @ts-ignore + createReduxContextHook(context) + return function useStore< + State = S, + Action extends BasicAction = A + // @ts-ignore + >() { + const { store } = useReduxContext()! + // @ts-ignore + return store as Store + } +} + +/** + * A hook to access the redux store. + * + * @returns {any} the redux store + * + * @example + * + * import React from 'react' + * import { useStore } from 'react-redux' + * + * export const ExampleComponent = () => { + * const store = useStore() + * return
{store.getState()}
+ * } + */ +export const useStore = /*#__PURE__*/ createStoreHook() diff --git a/.yalc/react-redux/src/index.ts b/.yalc/react-redux/src/index.ts old mode 100644 new mode 100755 index 1cabfee..3f04e45 --- a/.yalc/react-redux/src/index.ts +++ b/.yalc/react-redux/src/index.ts @@ -1,23 +1,23 @@ -// The primary entry point assumes we're working with standard ReactDOM/RN, but -// older versions that do not include `useSyncExternalStore` (React 16.9 - 17.x). -// Because of that, the useSyncExternalStore compat shim is needed. - -import { useSyncExternalStore } from 'use-sync-external-store/shim' -import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector' - -import { unstable_batchedUpdates as batch } from './utils/reactBatchedUpdates' -import { setBatch } from './utils/batch' - -import { initializeUseSelector } from './hooks/useSelector' -import { initializeConnect } from './components/connect' - -initializeUseSelector(useSyncExternalStoreWithSelector) -initializeConnect(useSyncExternalStore) - -// Enable batched updates in our subscriptions for use -// with standard React renderers (ReactDOM, React Native) -setBatch(batch) - -export { batch } - -export * from './exports' +// The primary entry point assumes we're working with standard ReactDOM/RN, but +// older versions that do not include `useSyncExternalStore` (React 16.9 - 17.x). +// Because of that, the useSyncExternalStore compat shim is needed. + +import { useSyncExternalStore } from 'use-sync-external-store/shim' +import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector' + +import { unstable_batchedUpdates as batch } from './utils/reactBatchedUpdates' +import { setBatch } from './utils/batch' + +import { initializeUseSelector } from './hooks/useSelector' +import { initializeConnect } from './components/connect' + +initializeUseSelector(useSyncExternalStoreWithSelector) +initializeConnect(useSyncExternalStore) + +// Enable batched updates in our subscriptions for use +// with standard React renderers (ReactDOM, React Native) +setBatch(batch) + +export { batch } + +export * from './exports' diff --git a/.yalc/react-redux/src/next.ts b/.yalc/react-redux/src/next.ts old mode 100644 new mode 100755 index c030ab1..74f37fa --- a/.yalc/react-redux/src/next.ts +++ b/.yalc/react-redux/src/next.ts @@ -1,24 +1,24 @@ -// The secondary entry point assumes we are working with React 18, and thus have -// useSyncExternalStore available. We can import that directly from React itself. -// The useSyncExternalStoreWithSelector has to be imported, but we can use the -// non-shim version. This shaves off the byte size of the shim. - -import { useSyncExternalStore } from 'react' -import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector' - -import { unstable_batchedUpdates as batch } from './utils/reactBatchedUpdates' -import { setBatch } from './utils/batch' - -import { initializeUseSelector } from './hooks/useSelector' -import { initializeConnect } from './components/connect' - -initializeUseSelector(useSyncExternalStoreWithSelector) -initializeConnect(useSyncExternalStore) - -// Enable batched updates in our subscriptions for use -// with standard React renderers (ReactDOM, React Native) -setBatch(batch) - -export { batch } - -export * from './exports' +// The secondary entry point assumes we are working with React 18, and thus have +// useSyncExternalStore available. We can import that directly from React itself. +// The useSyncExternalStoreWithSelector has to be imported, but we can use the +// non-shim version. This shaves off the byte size of the shim. + +import { useSyncExternalStore } from 'react' +import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector' + +import { unstable_batchedUpdates as batch } from './utils/reactBatchedUpdates' +import { setBatch } from './utils/batch' + +import { initializeUseSelector } from './hooks/useSelector' +import { initializeConnect } from './components/connect' + +initializeUseSelector(useSyncExternalStoreWithSelector) +initializeConnect(useSyncExternalStore) + +// Enable batched updates in our subscriptions for use +// with standard React renderers (ReactDOM, React Native) +setBatch(batch) + +export { batch } + +export * from './exports' diff --git a/.yalc/react-redux/src/types.ts b/.yalc/react-redux/src/types.ts old mode 100644 new mode 100755 index 5a1f0c4..c774845 --- a/.yalc/react-redux/src/types.ts +++ b/.yalc/react-redux/src/types.ts @@ -1,178 +1,178 @@ -import type { - ClassAttributes, - ComponentClass, - ComponentType, - FunctionComponent, -} from 'react' - -import type { Action, AnyAction, Dispatch } from 'redux' - -import type { NonReactStatics } from 'hoist-non-react-statics' - -import type { ConnectProps } from './components/connect' - -import type { UseSelectorOptions } from './hooks/useSelector' - -export type FixTypeLater = any - -export type EqualityFn = (a: T, b: T) => boolean - -export type ExtendedEqualityFn = (a: T, b: T, c: P, d: P) => boolean - -export type AnyIfEmpty = keyof T extends never ? any : T - -export type DistributiveOmit = T extends unknown - ? Omit - : never - -export interface DispatchProp
{ - dispatch: Dispatch -} - -/** - * A property P will be present if: - * - it is present in DecorationTargetProps - * - * Its value will be dependent on the following conditions - * - if property P is present in InjectedProps and its definition extends the definition - * in DecorationTargetProps, then its definition will be that of DecorationTargetProps[P] - * - if property P is not present in InjectedProps then its definition will be that of - * DecorationTargetProps[P] - * - if property P is present in InjectedProps but does not extend the - * DecorationTargetProps[P] definition, its definition will be that of InjectedProps[P] - */ -export type Matching = { - [P in keyof DecorationTargetProps]: P extends keyof InjectedProps - ? InjectedProps[P] extends DecorationTargetProps[P] - ? DecorationTargetProps[P] - : InjectedProps[P] - : DecorationTargetProps[P] -} - -/** - * a property P will be present if : - * - it is present in both DecorationTargetProps and InjectedProps - * - InjectedProps[P] can satisfy DecorationTargetProps[P] - * ie: decorated component can accept more types than decorator is injecting - * - * For decoration, inject props or ownProps are all optionally - * required by the decorated (right hand side) component. - * But any property required by the decorated component must be satisfied by the injected property. - */ -export type Shared = { - [P in Extract< - keyof InjectedProps, - keyof DecorationTargetProps - >]?: InjectedProps[P] extends DecorationTargetProps[P] - ? DecorationTargetProps[P] - : never -} - -// Infers prop type from component C -export type GetProps = C extends ComponentType - ? C extends ComponentClass

- ? ClassAttributes> & P - : P - : never - -// Applies LibraryManagedAttributes (proper handling of defaultProps -// and propTypes). -export type GetLibraryManagedProps = JSX.LibraryManagedAttributes< - C, - GetProps -> - -// Applies LibraryManagedAttributes (proper handling of defaultProps -// and propTypes), as well as defines WrappedComponent. -export type ConnectedComponent< - C extends ComponentType, - P -> = FunctionComponent

& - NonReactStatics & { - WrappedComponent: C - } - -export type ConnectPropsMaybeWithoutContext = - TActualOwnProps extends { context: any } - ? Omit - : ConnectProps - -type Identity = T -export type Mapped = Identity<{ [k in keyof T]: T[k] }> - -// Injects props and removes them from the prop requirements. -// Will not pass through the injected props if they are passed in during -// render. Also adds new prop requirements from TNeedsProps. -// Uses distributive omit to preserve discriminated unions part of original prop type. -// Note> Most of the time TNeedsProps is empty, because the overloads in `Connect` -// just pass in `{}`. The real props we need come from the component. -export type InferableComponentEnhancerWithProps = < - C extends ComponentType>> ->( - component: C -) => ConnectedComponent< - C, - Mapped< - DistributiveOmit< - GetLibraryManagedProps, - keyof Shared> - > & - TNeedsProps & - ConnectPropsMaybeWithoutContext> - > -> - -// Injects props and removes them from the prop requirements. -// Will not pass through the injected props if they are passed in during -// render. -export type InferableComponentEnhancer = - InferableComponentEnhancerWithProps - -export type InferThunkActionCreatorType< - TActionCreator extends (...args: any[]) => any -> = TActionCreator extends ( - ...args: infer TParams -) => (...args: any[]) => infer TReturn - ? (...args: TParams) => TReturn - : TActionCreator - -export type HandleThunkActionCreator = TActionCreator extends ( - ...args: any[] -) => any - ? InferThunkActionCreatorType - : TActionCreator - -// redux-thunk middleware returns thunk's return value from dispatch call -// https://github.com/reduxjs/redux-thunk#composition -export type ResolveThunks = TDispatchProps extends { - [key: string]: any -} - ? { - [C in keyof TDispatchProps]: HandleThunkActionCreator - } - : TDispatchProps - -/** - * This interface allows you to easily create a hook that is properly typed for your - * store's root state. - * - * @example - * - * interface RootState { - * property: string; - * } - * - * const useTypedSelector: TypedUseSelectorHook = useSelector; - */ -export interface TypedUseSelectorHook { - ( - selector: (state: TState) => TSelected, - equalityFn?: EqualityFn> - ): TSelected - ( - selector: (state: TState) => Selected, - options?: UseSelectorOptions - ): Selected -} - -export type NoInfer = [T][T extends any ? 0 : never] +import type { + ClassAttributes, + ComponentClass, + ComponentType, + FunctionComponent, +} from 'react' + +import type { Action, AnyAction, Dispatch } from 'redux' + +import type { NonReactStatics } from 'hoist-non-react-statics' + +import type { ConnectProps } from './components/connect' + +import type { UseSelectorOptions } from './hooks/useSelector' + +export type FixTypeLater = any + +export type EqualityFn = (a: T, b: T) => boolean + +export type ExtendedEqualityFn = (a: T, b: T, c: P, d: P) => boolean + +export type AnyIfEmpty = keyof T extends never ? any : T + +export type DistributiveOmit = T extends unknown + ? Omit + : never + +export interface DispatchProp { + dispatch: Dispatch +} + +/** + * A property P will be present if: + * - it is present in DecorationTargetProps + * + * Its value will be dependent on the following conditions + * - if property P is present in InjectedProps and its definition extends the definition + * in DecorationTargetProps, then its definition will be that of DecorationTargetProps[P] + * - if property P is not present in InjectedProps then its definition will be that of + * DecorationTargetProps[P] + * - if property P is present in InjectedProps but does not extend the + * DecorationTargetProps[P] definition, its definition will be that of InjectedProps[P] + */ +export type Matching = { + [P in keyof DecorationTargetProps]: P extends keyof InjectedProps + ? InjectedProps[P] extends DecorationTargetProps[P] + ? DecorationTargetProps[P] + : InjectedProps[P] + : DecorationTargetProps[P] +} + +/** + * a property P will be present if : + * - it is present in both DecorationTargetProps and InjectedProps + * - InjectedProps[P] can satisfy DecorationTargetProps[P] + * ie: decorated component can accept more types than decorator is injecting + * + * For decoration, inject props or ownProps are all optionally + * required by the decorated (right hand side) component. + * But any property required by the decorated component must be satisfied by the injected property. + */ +export type Shared = { + [P in Extract< + keyof InjectedProps, + keyof DecorationTargetProps + >]?: InjectedProps[P] extends DecorationTargetProps[P] + ? DecorationTargetProps[P] + : never +} + +// Infers prop type from component C +export type GetProps = C extends ComponentType + ? C extends ComponentClass

+ ? ClassAttributes> & P + : P + : never + +// Applies LibraryManagedAttributes (proper handling of defaultProps +// and propTypes). +export type GetLibraryManagedProps = JSX.LibraryManagedAttributes< + C, + GetProps +> + +// Applies LibraryManagedAttributes (proper handling of defaultProps +// and propTypes), as well as defines WrappedComponent. +export type ConnectedComponent< + C extends ComponentType, + P +> = FunctionComponent

& + NonReactStatics & { + WrappedComponent: C + } + +export type ConnectPropsMaybeWithoutContext = + TActualOwnProps extends { context: any } + ? Omit + : ConnectProps + +type Identity = T +export type Mapped = Identity<{ [k in keyof T]: T[k] }> + +// Injects props and removes them from the prop requirements. +// Will not pass through the injected props if they are passed in during +// render. Also adds new prop requirements from TNeedsProps. +// Uses distributive omit to preserve discriminated unions part of original prop type. +// Note> Most of the time TNeedsProps is empty, because the overloads in `Connect` +// just pass in `{}`. The real props we need come from the component. +export type InferableComponentEnhancerWithProps = < + C extends ComponentType>> +>( + component: C +) => ConnectedComponent< + C, + Mapped< + DistributiveOmit< + GetLibraryManagedProps, + keyof Shared> + > & + TNeedsProps & + ConnectPropsMaybeWithoutContext> + > +> + +// Injects props and removes them from the prop requirements. +// Will not pass through the injected props if they are passed in during +// render. +export type InferableComponentEnhancer = + InferableComponentEnhancerWithProps + +export type InferThunkActionCreatorType< + TActionCreator extends (...args: any[]) => any +> = TActionCreator extends ( + ...args: infer TParams +) => (...args: any[]) => infer TReturn + ? (...args: TParams) => TReturn + : TActionCreator + +export type HandleThunkActionCreator = TActionCreator extends ( + ...args: any[] +) => any + ? InferThunkActionCreatorType + : TActionCreator + +// redux-thunk middleware returns thunk's return value from dispatch call +// https://github.com/reduxjs/redux-thunk#composition +export type ResolveThunks = TDispatchProps extends { + [key: string]: any +} + ? { + [C in keyof TDispatchProps]: HandleThunkActionCreator + } + : TDispatchProps + +/** + * This interface allows you to easily create a hook that is properly typed for your + * store's root state. + * + * @example + * + * interface RootState { + * property: string; + * } + * + * const useTypedSelector: TypedUseSelectorHook = useSelector; + */ +export interface TypedUseSelectorHook { + ( + selector: (state: TState) => TSelected, + equalityFn?: EqualityFn> + ): TSelected + ( + selector: (state: TState) => Selected, + options?: UseSelectorOptions + ): Selected +} + +export type NoInfer = [T][T extends any ? 0 : never] diff --git a/.yalc/react-redux/src/utils/Subscription.ts b/.yalc/react-redux/src/utils/Subscription.ts old mode 100644 new mode 100755 index c03f908..f666732 --- a/.yalc/react-redux/src/utils/Subscription.ts +++ b/.yalc/react-redux/src/utils/Subscription.ts @@ -1,219 +1,242 @@ -import type { Store } from 'redux' -import { getBatch } from './batch' -import type { Node } from './autotracking/tracking' - -import { - createCache, - TrackingCache, - $REVISION, -} from './autotracking/autotracking' -import { updateNode } from './autotracking/proxy' - -// encapsulates the subscription logic for connecting a component to the redux store, as -// well as nesting subscriptions of descendant components, so that we can ensure the -// ancestor components re-render before descendants - -type VoidFunc = () => void - -export interface CacheWrapper { - cache: TrackingCache -} - -type Listener = { - callback: VoidFunc - next: Listener | null - prev: Listener | null - trigger: 'always' | 'tracked' - selectorCache?: CacheWrapper -} - -function createListenerCollection() { - const batch = getBatch() - let first: Listener | null = null - let last: Listener | null = null - - return { - clear() { - first = null - last = null - }, - - notify() { - //console.log('Notifying subscribers') - batch(() => { - let listener = first - while (listener) { - //console.log('Listener: ', listener) - if (listener.trigger == 'tracked') { - if (listener.selectorCache!.cache.needsRecalculation()) { - //console.log('Calling subscriber due to recalc need') - // console.log( - // 'Calling subscriber due to recalc. Revision before: ', - // $REVISION - // ) - listener.callback() - //console.log('Revision after: ', $REVISION) - } else { - // console.log( - // 'Skipping subscriber, no recalc: ', - // listener.selectorCache - // ) - } - } else { - listener.callback() - } - listener = listener.next - } - }) - }, - - get() { - let listeners: Listener[] = [] - let listener = first - while (listener) { - listeners.push(listener) - listener = listener.next - } - return listeners - }, - - subscribe( - callback: () => void, - options: AddNestedSubOptions = { trigger: 'always' } - ) { - let isSubscribed = true - - //console.log('Adding listener: ', options.trigger) - - let listener: Listener = (last = { - callback, - next: null, - prev: last, - trigger: options.trigger, - selectorCache: - options.trigger === 'tracked' ? options.cache! : undefined, - // subscriberCache: - // options.trigger === 'tracked' - // ? createCache(() => { - // console.log('Calling subscriberCache') - // listener.selectorCache!.get() - // callback() - // }) - // : undefined, - }) - - if (listener.prev) { - listener.prev.next = listener - } else { - first = listener - } - - return function unsubscribe() { - if (!isSubscribed || first === null) return - isSubscribed = false - - if (listener.next) { - listener.next.prev = listener.prev - } else { - last = listener.prev - } - if (listener.prev) { - listener.prev.next = listener.next - } else { - first = listener.next - } - } - }, - } -} - -type ListenerCollection = ReturnType - -interface AddNestedSubOptions { - trigger: 'always' | 'tracked' - cache?: CacheWrapper -} - -export interface Subscription { - addNestedSub: (listener: VoidFunc, options?: AddNestedSubOptions) => VoidFunc - notifyNestedSubs: VoidFunc - handleChangeWrapper: VoidFunc - isSubscribed: () => boolean - onStateChange?: VoidFunc | null - trySubscribe: (options?: AddNestedSubOptions) => void - tryUnsubscribe: VoidFunc - getListeners: () => ListenerCollection -} - -const nullListeners = { - notify() {}, - get: () => [], -} as unknown as ListenerCollection - -export function createSubscription( - store: Store, - parentSub?: Subscription, - trackingNode?: Node -) { - let unsubscribe: VoidFunc | undefined - let listeners: ListenerCollection = nullListeners - - function addNestedSub( - listener: () => void, - options: AddNestedSubOptions = { trigger: 'always' } - ) { - //console.log('addNestedSub: ', options) - trySubscribe(options) - return listeners.subscribe(listener, options) - } - - function notifyNestedSubs() { - if (store && trackingNode) { - //console.log('Updating node in notifyNestedSubs') - updateNode(trackingNode, store.getState()) - } - listeners.notify() - } - - function handleChangeWrapper() { - if (subscription.onStateChange) { - subscription.onStateChange() - } - } - - function isSubscribed() { - return Boolean(unsubscribe) - } - - function trySubscribe(options: AddNestedSubOptions = { trigger: 'always' }) { - if (!unsubscribe) { - //console.log('trySubscribe, parentSub: ', parentSub) - unsubscribe = parentSub - ? parentSub.addNestedSub(handleChangeWrapper, options) - : store.subscribe(handleChangeWrapper) - - listeners = createListenerCollection() - } - } - - function tryUnsubscribe() { - if (unsubscribe) { - unsubscribe() - unsubscribe = undefined - listeners.clear() - listeners = nullListeners - } - } - - const subscription: Subscription = { - addNestedSub, - notifyNestedSubs, - handleChangeWrapper, - isSubscribed, - trySubscribe, - tryUnsubscribe, - getListeners: () => listeners, - } - - return subscription -} +import type { Store } from 'redux' +import { getBatch } from './batch' +import type { Node } from './autotracking/tracking' + +import { + createCache, + TrackingCache, + $REVISION, +} from './autotracking/autotracking' +import { updateNode } from './autotracking/proxy' + +// encapsulates the subscription logic for connecting a component to the redux store, as +// well as nesting subscriptions of descendant components, so that we can ensure the +// ancestor components re-render before descendants + +type VoidFunc = () => void + +export interface CacheWrapper { + cache: TrackingCache +} + +type Listener = { + callback: VoidFunc + next: Listener | null + prev: Listener | null + trigger: 'always' | 'tracked' + selectorCache?: CacheWrapper +} + +function createListenerCollection() { + const batch = getBatch() + let first: Listener | null = null + let last: Listener | null = null + + return { + clear() { + first = null + last = null + }, + + notify() { + //console.log('Notifying subscribers') + let numCalled = 0 + let numSkipped = 0 + batch(() => { + let listener = first + while (listener) { + //console.log('Listener: ', listener) + if (listener.trigger == 'tracked') { + if (listener.selectorCache!.cache.needsRecalculation()) { + //console.log('Calling subscriber due to recalc need') + // console.log( + // 'Calling subscriber due to recalc. Revision before: ', + // $REVISION + // ) + numCalled++ + listener.callback() + //console.log('Revision after: ', $REVISION) + } else { + numSkipped++ + // console.log( + // 'Skipping subscriber, no recalc: ', + // listener.selectorCache + // ) + } + } else { + listener.callback() + } + listener = listener.next + } + }) + const result = { numCalled, numSkipped } + return result + }, + + get() { + let listeners: Listener[] = [] + let listener = first + while (listener) { + listeners.push(listener) + listener = listener.next + } + return listeners + }, + + subscribe( + callback: () => void, + options: AddNestedSubOptions = { trigger: 'always' } + ) { + let isSubscribed = true + + //console.log('Adding listener: ', options.trigger) + + let listener: Listener = (last = { + callback, + next: null, + prev: last, + trigger: options.trigger, + selectorCache: + options.trigger === 'tracked' ? options.cache! : undefined, + // subscriberCache: + // options.trigger === 'tracked' + // ? createCache(() => { + // console.log('Calling subscriberCache') + // listener.selectorCache!.get() + // callback() + // }) + // : undefined, + }) + + if (listener.prev) { + listener.prev.next = listener + } else { + first = listener + } + + return function unsubscribe() { + if (!isSubscribed || first === null) return + isSubscribed = false + + if (listener.next) { + listener.next.prev = listener.prev + } else { + last = listener.prev + } + if (listener.prev) { + listener.prev.next = listener.next + } else { + first = listener.next + } + } + }, + } +} + +type ListenerCollection = ReturnType + +interface AddNestedSubOptions { + trigger: 'always' | 'tracked' + cache?: CacheWrapper +} + +export interface Subscription { + addNestedSub: (listener: VoidFunc, options?: AddNestedSubOptions) => VoidFunc + notifyNestedSubs: VoidFunc + handleChangeWrapper: VoidFunc + isSubscribed: () => boolean + onStateChange?: VoidFunc | null + trySubscribe: (options?: AddNestedSubOptions) => void + tryUnsubscribe: VoidFunc + getListeners: () => ListenerCollection +} + +const nullListeners = { + notify() {}, + get: () => [], +} as unknown as ListenerCollection + +interface Duration { + start: number + end: number + duration: number +} + +export function createSubscription( + store: Store, + parentSub?: Subscription, + trackingNode?: Node +) { + let unsubscribe: VoidFunc | undefined + let listeners: ListenerCollection = nullListeners + + const updateNodeTimes: Duration[] = [] + const notifyTimes: Duration[] = [] + const resultCounts: { numCalled: number; numSkipped: number }[] = [] + + function addNestedSub( + listener: () => void, + options: AddNestedSubOptions = { trigger: 'always' } + ) { + //console.log('addNestedSub: ', options) + trySubscribe(options) + return listeners.subscribe(listener, options) + } + + function notifyNestedSubs() { + if (store && trackingNode) { + //console.log('Updating node in notifyNestedSubs') + const start = performance.now() + updateNode(trackingNode, store.getState()) + const end = performance.now() + updateNodeTimes.push({ start, end, duration: end - start }) + } + const start = performance.now() + const results = listeners.notify() + const end = performance.now() + notifyTimes.push({ start, end, duration: end - start }) + resultCounts.push(results) + } + + function handleChangeWrapper() { + if (subscription.onStateChange) { + subscription.onStateChange() + } + } + + function isSubscribed() { + return Boolean(unsubscribe) + } + + function trySubscribe(options: AddNestedSubOptions = { trigger: 'always' }) { + if (!unsubscribe) { + //console.log('trySubscribe, parentSub: ', parentSub) + unsubscribe = parentSub + ? parentSub.addNestedSub(handleChangeWrapper, options) + : store.subscribe(handleChangeWrapper) + + listeners = createListenerCollection() + } + } + + function tryUnsubscribe() { + if (unsubscribe) { + unsubscribe() + unsubscribe = undefined + listeners.clear() + listeners = nullListeners + } + } + + const subscription: Subscription = { + addNestedSub, + notifyNestedSubs, + handleChangeWrapper, + isSubscribed, + trySubscribe, + tryUnsubscribe, + getListeners: () => listeners, + } + + return subscription +} diff --git a/.yalc/react-redux/src/utils/autotracking/autotracking.ts b/.yalc/react-redux/src/utils/autotracking/autotracking.ts old mode 100644 new mode 100755 index eca3353..92e9954 --- a/.yalc/react-redux/src/utils/autotracking/autotracking.ts +++ b/.yalc/react-redux/src/utils/autotracking/autotracking.ts @@ -1,237 +1,237 @@ -// Original autotracking implementation source: -// - https://gist.github.com/pzuraq/79bf862e0f8cd9521b79c4b6eccdc4f9 -// Additional references: -// - https://www.pzuraq.com/blog/how-autotracking-works -// - https://v5.chriskrycho.com/journal/autotracking-elegant-dx-via-cutting-edge-cs/ -import { assert } from './utils' - -// The global revision clock. Every time state changes, the clock increments. -export let $REVISION = 0 - -// The current dependency tracker. Whenever we compute a cache, we create a Set -// to track any dependencies that are used while computing. If no cache is -// computing, then the tracker is null. -let CURRENT_TRACKER: Set | TrackingCache> | null = null - -type EqualityFn = (a: any, b: any) => boolean - -// Storage represents a root value in the system - the actual state of our app. -export class Cell { - revision = $REVISION - - _value: T - _lastValue: T - _isEqual: EqualityFn = tripleEq - _name: string | undefined - - constructor(initialValue: T, isEqual: EqualityFn = tripleEq, name?: string) { - this._value = this._lastValue = initialValue - this._isEqual = isEqual - this._name = name - } - - // Whenever a storage value is read, it'll add itself to the current tracker if - // one exists, entangling its state with that cache. - get value() { - CURRENT_TRACKER?.add(this) - - return this._value - } - - // Whenever a storage value is updated, we bump the global revision clock, - // assign the revision for this storage to the new value, _and_ we schedule a - // rerender. This is important, and it's what makes autotracking _pull_ - // based. We don't actively tell the caches which depend on the storage that - // anything has happened. Instead, we recompute the caches when needed. - set value(newValue) { - if (this.value === newValue) return - - this._value = newValue - this.revision = ++$REVISION - } -} - -function tripleEq(a: unknown, b: unknown) { - return a === b -} - -// Caches represent derived state in the system. They are ultimately functions -// that are memoized based on what state they use to produce their output, -// meaning they will only rerun IFF a storage value that could affect the output -// has changed. Otherwise, they'll return the cached value. -export class TrackingCache { - _cachedValue: any - _cachedRevision = -1 - _deps: Cell[] = [] - hits = 0 - _needsRecalculation = false - - fn: (...args: any[]) => any - - constructor(fn: (...args: any[]) => any) { - this.fn = fn - } - - clear() { - this._cachedValue = undefined - this._cachedRevision = -1 - this._deps = [] - this.hits = 0 - this._needsRecalculation = false - } - - getValue = () => { - //console.log('TrackedCache getValue') - return this.value - } - - needsRecalculation() { - if (!this._needsRecalculation) { - this._needsRecalculation = this.revision > this._cachedRevision - } - // console.log( - // 'Needs recalculation: ', - // this._needsRecalculation, - // this._cachedRevision, - // this._cachedValue - // ) - return this._needsRecalculation - } - - /* - getWithArgs = (...args: any[]) => { - // console.log( - // `TrackingCache value: revision = ${this.revision}, cachedRevision = ${this._cachedRevision}, value = ${this._cachedValue}` - // ) - // When getting the value for a Cache, first we check all the dependencies of - // the cache to see what their current revision is. If the current revision is - // greater than the cached revision, then something has changed. - //if (this.revision > this._cachedRevision) { - if (this.needsRecalculation()) { - const { fn } = this - - // We create a new dependency tracker for this cache. As the cache runs - // its function, any Storage or Cache instances which are used while - // computing will be added to this tracker. In the end, it will be the - // full list of dependencies that this Cache depends on. - const currentTracker = new Set>() - const prevTracker = CURRENT_TRACKER - - CURRENT_TRACKER = currentTracker - - // try { - this._cachedValue = fn.apply(null, args) - // } finally { - CURRENT_TRACKER = prevTracker - this.hits++ - this._deps = Array.from(currentTracker) - - // Set the cached revision. This is the current clock count of all the - // dependencies. If any dependency changes, this number will be less - // than the new revision. - this._cachedRevision = this.revision - // } - } - - // If there is a current tracker, it means another Cache is computing and - // using this one, so we add this one to the tracker. - CURRENT_TRACKER?.add(this) - - // Always return the cached value. - return this._cachedValue - } -*/ - get value() { - // console.log( - // `TrackingCache value: revision = ${this.revision}, cachedRevision = ${this._cachedRevision}, value = ${this._cachedValue}` - // ) - // When getting the value for a Cache, first we check all the dependencies of - // the cache to see what their current revision is. If the current revision is - // greater than the cached revision, then something has changed. - if (this.needsRecalculation()) { - const { fn } = this - - // We create a new dependency tracker for this cache. As the cache runs - // its function, any Storage or Cache instances which are used while - // computing will be added to this tracker. In the end, it will be the - // full list of dependencies that this Cache depends on. - const currentTracker = new Set>() - const prevTracker = CURRENT_TRACKER - - CURRENT_TRACKER = currentTracker - - // try { - this._cachedValue = fn() - // } finally { - CURRENT_TRACKER = prevTracker - this.hits++ - this._deps = Array.from(currentTracker) - - // Set the cached revision. This is the current clock count of all the - // dependencies. If any dependency changes, this number will be less - // than the new revision. - this._cachedRevision = this.revision - this._needsRecalculation = false - - // console.log('Value: ', this._cachedValue, 'deps: ', this._deps) - // } - } - - // If there is a current tracker, it means another Cache is computing and - // using this one, so we add this one to the tracker. - CURRENT_TRACKER?.add(this) - - // Always return the cached value. - return this._cachedValue - } - - get revision() { - // console.log('Calculating revision: ', { - // value: this._cachedValue, - // deps: this._deps.map((d) => d._name), - // }) - // The current revision is the max of all the dependencies' revisions. - return Math.max(...this._deps.map((d) => d.revision), 0) - } -} - -export function getValue(cell: Cell): T { - if (!(cell instanceof Cell)) { - console.warn('Not a valid cell! ', cell) - } - - return cell.value -} - -type CellValue> = T extends Cell ? U : never - -export function setValue>( - storage: T, - value: CellValue -): void { - assert( - storage instanceof Cell, - 'setValue must be passed a tracked store created with `createStorage`.' - ) - - storage.value = storage._lastValue = value -} - -export function createCell( - initialValue: T, - isEqual: EqualityFn = tripleEq, - name?: string -): Cell { - return new Cell(initialValue, isEqual, name) -} - -export function createCache( - fn: (...args: any[]) => T -): TrackingCache { - assert( - typeof fn === 'function', - 'the first parameter to `createCache` must be a function' - ) - - return new TrackingCache(fn) -} +// Original autotracking implementation source: +// - https://gist.github.com/pzuraq/79bf862e0f8cd9521b79c4b6eccdc4f9 +// Additional references: +// - https://www.pzuraq.com/blog/how-autotracking-works +// - https://v5.chriskrycho.com/journal/autotracking-elegant-dx-via-cutting-edge-cs/ +import { assert } from './utils' + +// The global revision clock. Every time state changes, the clock increments. +export let $REVISION = 0 + +// The current dependency tracker. Whenever we compute a cache, we create a Set +// to track any dependencies that are used while computing. If no cache is +// computing, then the tracker is null. +let CURRENT_TRACKER: Set | TrackingCache> | null = null + +type EqualityFn = (a: any, b: any) => boolean + +// Storage represents a root value in the system - the actual state of our app. +export class Cell { + revision = $REVISION + + _value: T + _lastValue: T + _isEqual: EqualityFn = tripleEq + _name: string | undefined + + constructor(initialValue: T, isEqual: EqualityFn = tripleEq, name?: string) { + this._value = this._lastValue = initialValue + this._isEqual = isEqual + this._name = name + } + + // Whenever a storage value is read, it'll add itself to the current tracker if + // one exists, entangling its state with that cache. + get value() { + CURRENT_TRACKER?.add(this) + + return this._value + } + + // Whenever a storage value is updated, we bump the global revision clock, + // assign the revision for this storage to the new value, _and_ we schedule a + // rerender. This is important, and it's what makes autotracking _pull_ + // based. We don't actively tell the caches which depend on the storage that + // anything has happened. Instead, we recompute the caches when needed. + set value(newValue) { + if (this.value === newValue) return + + this._value = newValue + this.revision = ++$REVISION + } +} + +function tripleEq(a: unknown, b: unknown) { + return a === b +} + +// Caches represent derived state in the system. They are ultimately functions +// that are memoized based on what state they use to produce their output, +// meaning they will only rerun IFF a storage value that could affect the output +// has changed. Otherwise, they'll return the cached value. +export class TrackingCache { + _cachedValue: any + _cachedRevision = -1 + _deps: Cell[] = [] + hits = 0 + _needsRecalculation = false + + fn: (...args: any[]) => any + + constructor(fn: (...args: any[]) => any) { + this.fn = fn + } + + clear() { + this._cachedValue = undefined + this._cachedRevision = -1 + this._deps = [] + this.hits = 0 + this._needsRecalculation = false + } + + getValue = () => { + //console.log('TrackedCache getValue') + return this.value + } + + needsRecalculation() { + if (!this._needsRecalculation) { + this._needsRecalculation = this.revision > this._cachedRevision + } + // console.log( + // 'Needs recalculation: ', + // this._needsRecalculation, + // this._cachedRevision, + // this._cachedValue + // ) + return this._needsRecalculation + } + + /* + getWithArgs = (...args: any[]) => { + // console.log( + // `TrackingCache value: revision = ${this.revision}, cachedRevision = ${this._cachedRevision}, value = ${this._cachedValue}` + // ) + // When getting the value for a Cache, first we check all the dependencies of + // the cache to see what their current revision is. If the current revision is + // greater than the cached revision, then something has changed. + //if (this.revision > this._cachedRevision) { + if (this.needsRecalculation()) { + const { fn } = this + + // We create a new dependency tracker for this cache. As the cache runs + // its function, any Storage or Cache instances which are used while + // computing will be added to this tracker. In the end, it will be the + // full list of dependencies that this Cache depends on. + const currentTracker = new Set>() + const prevTracker = CURRENT_TRACKER + + CURRENT_TRACKER = currentTracker + + // try { + this._cachedValue = fn.apply(null, args) + // } finally { + CURRENT_TRACKER = prevTracker + this.hits++ + this._deps = Array.from(currentTracker) + + // Set the cached revision. This is the current clock count of all the + // dependencies. If any dependency changes, this number will be less + // than the new revision. + this._cachedRevision = this.revision + // } + } + + // If there is a current tracker, it means another Cache is computing and + // using this one, so we add this one to the tracker. + CURRENT_TRACKER?.add(this) + + // Always return the cached value. + return this._cachedValue + } +*/ + get value() { + // console.log( + // `TrackingCache value: revision = ${this.revision}, cachedRevision = ${this._cachedRevision}, value = ${this._cachedValue}` + // ) + // When getting the value for a Cache, first we check all the dependencies of + // the cache to see what their current revision is. If the current revision is + // greater than the cached revision, then something has changed. + if (this.needsRecalculation()) { + const { fn } = this + + // We create a new dependency tracker for this cache. As the cache runs + // its function, any Storage or Cache instances which are used while + // computing will be added to this tracker. In the end, it will be the + // full list of dependencies that this Cache depends on. + const currentTracker = new Set>() + const prevTracker = CURRENT_TRACKER + + CURRENT_TRACKER = currentTracker + + // try { + this._cachedValue = fn() + // } finally { + CURRENT_TRACKER = prevTracker + this.hits++ + this._deps = Array.from(currentTracker) + + // Set the cached revision. This is the current clock count of all the + // dependencies. If any dependency changes, this number will be less + // than the new revision. + this._cachedRevision = this.revision + this._needsRecalculation = false + + // console.log('Value: ', this._cachedValue, 'deps: ', this._deps) + // } + } + + // If there is a current tracker, it means another Cache is computing and + // using this one, so we add this one to the tracker. + CURRENT_TRACKER?.add(this) + + // Always return the cached value. + return this._cachedValue + } + + get revision() { + // console.log('Calculating revision: ', { + // value: this._cachedValue, + // deps: this._deps.map((d) => d._name), + // }) + // The current revision is the max of all the dependencies' revisions. + return Math.max(...this._deps.map((d) => d.revision), 0) + } +} + +export function getValue(cell: Cell): T { + if (!(cell instanceof Cell)) { + console.warn('Not a valid cell! ', cell) + } + + return cell.value +} + +type CellValue> = T extends Cell ? U : never + +export function setValue>( + storage: T, + value: CellValue +): void { + assert( + storage instanceof Cell, + 'setValue must be passed a tracked store created with `createStorage`.' + ) + + storage.value = storage._lastValue = value +} + +export function createCell( + initialValue: T, + isEqual: EqualityFn = tripleEq, + name?: string +): Cell { + return new Cell(initialValue, isEqual, name) +} + +export function createCache( + fn: (...args: any[]) => T +): TrackingCache { + assert( + typeof fn === 'function', + 'the first parameter to `createCache` must be a function' + ) + + return new TrackingCache(fn) +} diff --git a/.yalc/react-redux/src/utils/autotracking/proxy.ts b/.yalc/react-redux/src/utils/autotracking/proxy.ts old mode 100644 new mode 100755 index 682e354..4fcda45 --- a/.yalc/react-redux/src/utils/autotracking/proxy.ts +++ b/.yalc/react-redux/src/utils/autotracking/proxy.ts @@ -1,238 +1,238 @@ -// Original source: -// - https://github.com/simonihmig/tracked-redux/blob/master/packages/tracked-redux/src/-private/proxy.ts - -import { - consumeCollection, - dirtyCollection, - Node, - Tag, - consumeTag, - dirtyTag, - createTag, -} from './tracking' - -export const REDUX_PROXY_LABEL = Symbol() - -let nextId = 0 - -const proto = Object.getPrototypeOf({}) - -class ObjectTreeNode> implements Node { - proxy: T = new Proxy(this, objectProxyHandler) as unknown as T - tag = createTag('object') - tags = {} as Record - children = {} as Record - collectionTag = null - id = nextId++ - - constructor(public value: T) { - this.value = value - this.tag.value = value - } -} - -const objectProxyHandler = { - get(node: Node, key: string | symbol): unknown { - //console.log('Reading key: ', key, node.value) - - function calculateResult() { - const { value } = node - - const childValue = Reflect.get(value, key) - - if (typeof key === 'symbol') { - return childValue - } - - if (key in proto) { - return childValue - } - - if (typeof childValue === 'object' && childValue !== null) { - let childNode = node.children[key] - - if (childNode === undefined) { - childNode = node.children[key] = createNode(childValue) - } - - if (childNode.tag) { - consumeTag(childNode.tag) - } - - return childNode.proxy - } else { - let tag = node.tags[key] - - if (tag === undefined) { - tag = node.tags[key] = createTag(key) - tag.value = childValue - } - - consumeTag(tag) - - return childValue - } - } - const res = calculateResult() - return res - }, - - ownKeys(node: Node): ArrayLike { - consumeCollection(node) - return Reflect.ownKeys(node.value) - }, - - getOwnPropertyDescriptor( - node: Node, - prop: string | symbol - ): PropertyDescriptor | undefined { - return Reflect.getOwnPropertyDescriptor(node.value, prop) - }, - - has(node: Node, prop: string | symbol): boolean { - return Reflect.has(node.value, prop) - }, -} - -class ArrayTreeNode> implements Node { - proxy: T = new Proxy([this], arrayProxyHandler) as unknown as T - tag = createTag('array') - tags = {} - children = {} - collectionTag = null - id = nextId++ - - constructor(public value: T) { - this.value = value - this.tag.value = value - } -} - -const arrayProxyHandler = { - get([node]: [Node], key: string | symbol): unknown { - if (key === 'length') { - consumeCollection(node) - } - - return objectProxyHandler.get(node, key) - }, - - ownKeys([node]: [Node]): ArrayLike { - return objectProxyHandler.ownKeys(node) - }, - - getOwnPropertyDescriptor( - [node]: [Node], - prop: string | symbol - ): PropertyDescriptor | undefined { - return objectProxyHandler.getOwnPropertyDescriptor(node, prop) - }, - - has([node]: [Node], prop: string | symbol): boolean { - return objectProxyHandler.has(node, prop) - }, -} - -export function createNode | Record>( - value: T -): Node { - if (Array.isArray(value)) { - return new ArrayTreeNode(value) - } - - return new ObjectTreeNode(value) as Node -} - -const keysMap = new WeakMap< - Array | Record, - Set ->() - -export function updateNode | Record>( - node: Node, - newValue: T -): void { - const { value, tags, children } = node - - //console.log('Inside updateNode', newValue) - - node.value = newValue - - if ( - Array.isArray(value) && - Array.isArray(newValue) && - value.length !== newValue.length - ) { - dirtyCollection(node) - } else { - if (value !== newValue) { - let oldKeysSize = 0 - let newKeysSize = 0 - let anyKeysAdded = false - - for (const _key in value) { - oldKeysSize++ - } - - for (const key in newValue) { - newKeysSize++ - if (!(key in value)) { - anyKeysAdded = true - break - } - } - - const isDifferent = anyKeysAdded || oldKeysSize !== newKeysSize - - if (isDifferent) { - dirtyCollection(node) - } - } - } - - for (const key in tags) { - //console.log('Checking tag: ', key) - const childValue = (value as Record)[key] - const newChildValue = (newValue as Record)[key] - - if (childValue !== newChildValue) { - dirtyCollection(node) - dirtyTag(tags[key], newChildValue) - } - - if (typeof newChildValue === 'object' && newChildValue !== null) { - delete tags[key] - } - } - - for (const key in children) { - //console.log(`Checking node: key = ${key}, value = ${children[key]}`) - const childNode = children[key] - const newChildValue = (newValue as Record)[key] - - const childValue = childNode.value - - if (childValue === newChildValue) { - continue - } else if (typeof newChildValue === 'object' && newChildValue !== null) { - console.log('Updating node key: ', key) - updateNode(childNode, newChildValue as Record) - } else { - deleteNode(childNode) - delete children[key] - } - } -} - -function deleteNode(node: Node): void { - if (node.tag) { - dirtyTag(node.tag, null) - } - dirtyCollection(node) - for (const key in node.tags) { - dirtyTag(node.tags[key], null) - } - for (const key in node.children) { - deleteNode(node.children[key]) - } -} +// Original source: +// - https://github.com/simonihmig/tracked-redux/blob/master/packages/tracked-redux/src/-private/proxy.ts + +import { + consumeCollection, + dirtyCollection, + Node, + Tag, + consumeTag, + dirtyTag, + createTag, +} from './tracking' + +export const REDUX_PROXY_LABEL = Symbol() + +let nextId = 0 + +const proto = Object.getPrototypeOf({}) + +class ObjectTreeNode> implements Node { + proxy: T = new Proxy(this, objectProxyHandler) as unknown as T + tag = createTag('object') + tags = {} as Record + children = {} as Record + collectionTag = null + id = nextId++ + + constructor(public value: T) { + this.value = value + this.tag.value = value + } +} + +const objectProxyHandler = { + get(node: Node, key: string | symbol): unknown { + //console.log('Reading key: ', key, node.value) + + function calculateResult() { + const { value } = node + + const childValue = Reflect.get(value, key) + + if (typeof key === 'symbol') { + return childValue + } + + if (key in proto) { + return childValue + } + + if (typeof childValue === 'object' && childValue !== null) { + let childNode = node.children[key] + + if (childNode === undefined) { + childNode = node.children[key] = createNode(childValue) + } + + if (childNode.tag) { + consumeTag(childNode.tag) + } + + return childNode.proxy + } else { + let tag = node.tags[key] + + if (tag === undefined) { + tag = node.tags[key] = createTag(key) + tag.value = childValue + } + + consumeTag(tag) + + return childValue + } + } + const res = calculateResult() + return res + }, + + ownKeys(node: Node): ArrayLike { + consumeCollection(node) + return Reflect.ownKeys(node.value) + }, + + getOwnPropertyDescriptor( + node: Node, + prop: string | symbol + ): PropertyDescriptor | undefined { + return Reflect.getOwnPropertyDescriptor(node.value, prop) + }, + + has(node: Node, prop: string | symbol): boolean { + return Reflect.has(node.value, prop) + }, +} + +class ArrayTreeNode> implements Node { + proxy: T = new Proxy([this], arrayProxyHandler) as unknown as T + tag = createTag('array') + tags = {} + children = {} + collectionTag = null + id = nextId++ + + constructor(public value: T) { + this.value = value + this.tag.value = value + } +} + +const arrayProxyHandler = { + get([node]: [Node], key: string | symbol): unknown { + if (key === 'length') { + consumeCollection(node) + } + + return objectProxyHandler.get(node, key) + }, + + ownKeys([node]: [Node]): ArrayLike { + return objectProxyHandler.ownKeys(node) + }, + + getOwnPropertyDescriptor( + [node]: [Node], + prop: string | symbol + ): PropertyDescriptor | undefined { + return objectProxyHandler.getOwnPropertyDescriptor(node, prop) + }, + + has([node]: [Node], prop: string | symbol): boolean { + return objectProxyHandler.has(node, prop) + }, +} + +export function createNode | Record>( + value: T +): Node { + if (Array.isArray(value)) { + return new ArrayTreeNode(value) + } + + return new ObjectTreeNode(value) as Node +} + +const keysMap = new WeakMap< + Array | Record, + Set +>() + +export function updateNode | Record>( + node: Node, + newValue: T +): void { + const { value, tags, children } = node + + //console.log('Inside updateNode', newValue) + + node.value = newValue + + if ( + Array.isArray(value) && + Array.isArray(newValue) && + value.length !== newValue.length + ) { + dirtyCollection(node) + } else { + if (value !== newValue) { + let oldKeysSize = 0 + let newKeysSize = 0 + let anyKeysAdded = false + + for (const _key in value) { + oldKeysSize++ + } + + for (const key in newValue) { + newKeysSize++ + if (!(key in value)) { + anyKeysAdded = true + break + } + } + + const isDifferent = anyKeysAdded || oldKeysSize !== newKeysSize + + if (isDifferent) { + dirtyCollection(node) + } + } + } + + for (const key in tags) { + //console.log('Checking tag: ', key) + const childValue = (value as Record)[key] + const newChildValue = (newValue as Record)[key] + + if (childValue !== newChildValue) { + dirtyCollection(node) + dirtyTag(tags[key], newChildValue) + } + + if (typeof newChildValue === 'object' && newChildValue !== null) { + delete tags[key] + } + } + + for (const key in children) { + //console.log(`Checking node: key = ${key}, value = ${children[key]}`) + const childNode = children[key] + const newChildValue = (newValue as Record)[key] + + const childValue = childNode.value + + if (childValue === newChildValue) { + continue + } else if (typeof newChildValue === 'object' && newChildValue !== null) { + // console.log('Updating node key: ', key) + updateNode(childNode, newChildValue as Record) + } else { + deleteNode(childNode) + delete children[key] + } + } +} + +function deleteNode(node: Node): void { + if (node.tag) { + dirtyTag(node.tag, null) + } + dirtyCollection(node) + for (const key in node.tags) { + dirtyTag(node.tags[key], null) + } + for (const key in node.children) { + deleteNode(node.children[key]) + } +} diff --git a/.yalc/react-redux/src/utils/autotracking/tracking.ts b/.yalc/react-redux/src/utils/autotracking/tracking.ts old mode 100644 new mode 100755 index 5693ac5..9ab52a1 --- a/.yalc/react-redux/src/utils/autotracking/tracking.ts +++ b/.yalc/react-redux/src/utils/autotracking/tracking.ts @@ -1,50 +1,50 @@ -import { - createCell as createStorage, - getValue as consumeTag, - setValue, - Cell, -} from './autotracking' - -export type Tag = Cell - -const neverEq = (a: any, b: any): boolean => false - -export function createTag(name?: string): Tag { - return createStorage(null, neverEq, name) -} -export { consumeTag } -export function dirtyTag(tag: Tag, value: any): void { - setValue(tag, value) -} - -export interface Node< - T extends Array | Record = - | Array - | Record -> { - collectionTag: Tag | null - tag: Tag | null - tags: Record - children: Record - proxy: T - value: T - id: number -} - -export const consumeCollection = (node: Node): void => { - let tag = node.collectionTag - - if (tag === null) { - tag = node.collectionTag = createTag(node.collectionTag?._name || 'Unknown') - } - - consumeTag(tag) -} - -export const dirtyCollection = (node: Node): void => { - const tag = node.collectionTag - - if (tag !== null) { - dirtyTag(tag, null) - } -} +import { + createCell as createStorage, + getValue as consumeTag, + setValue, + Cell, +} from './autotracking' + +export type Tag = Cell + +const neverEq = (a: any, b: any): boolean => false + +export function createTag(name?: string): Tag { + return createStorage(null, neverEq, name) +} +export { consumeTag } +export function dirtyTag(tag: Tag, value: any): void { + setValue(tag, value) +} + +export interface Node< + T extends Array | Record = + | Array + | Record +> { + collectionTag: Tag | null + tag: Tag | null + tags: Record + children: Record + proxy: T + value: T + id: number +} + +export const consumeCollection = (node: Node): void => { + let tag = node.collectionTag + + if (tag === null) { + tag = node.collectionTag = createTag(node.collectionTag?._name || 'Unknown') + } + + consumeTag(tag) +} + +export const dirtyCollection = (node: Node): void => { + const tag = node.collectionTag + + if (tag !== null) { + dirtyTag(tag, null) + } +} diff --git a/.yalc/react-redux/src/utils/autotracking/utils.ts b/.yalc/react-redux/src/utils/autotracking/utils.ts old mode 100644 new mode 100755 index cef655a..01029c9 --- a/.yalc/react-redux/src/utils/autotracking/utils.ts +++ b/.yalc/react-redux/src/utils/autotracking/utils.ts @@ -1,9 +1,9 @@ -export function assert( - condition: any, - msg = 'Assertion failed!' -): asserts condition { - if (!condition) { - console.error(msg) - throw new Error(msg) - } -} +export function assert( + condition: any, + msg = 'Assertion failed!' +): asserts condition { + if (!condition) { + console.error(msg) + throw new Error(msg) + } +} diff --git a/.yalc/react-redux/src/utils/batch.ts b/.yalc/react-redux/src/utils/batch.ts old mode 100644 new mode 100755 index 2d116ea..53b9704 --- a/.yalc/react-redux/src/utils/batch.ts +++ b/.yalc/react-redux/src/utils/batch.ts @@ -1,13 +1,13 @@ -// Default to a dummy "batch" implementation that just runs the callback -function defaultNoopBatch(callback: () => void) { - callback() -} - -let batch = defaultNoopBatch - -// Allow injecting another batching function later -export const setBatch = (newBatch: typeof defaultNoopBatch) => - (batch = newBatch) - -// Supply a getter just to skip dealing with ESM bindings -export const getBatch = () => batch +// Default to a dummy "batch" implementation that just runs the callback +function defaultNoopBatch(callback: () => void) { + callback() +} + +let batch = defaultNoopBatch + +// Allow injecting another batching function later +export const setBatch = (newBatch: typeof defaultNoopBatch) => + (batch = newBatch) + +// Supply a getter just to skip dealing with ESM bindings +export const getBatch = () => batch diff --git a/.yalc/react-redux/src/utils/bindActionCreators.ts b/.yalc/react-redux/src/utils/bindActionCreators.ts old mode 100644 new mode 100755 index 0d5f3d8..9a8b74c --- a/.yalc/react-redux/src/utils/bindActionCreators.ts +++ b/.yalc/react-redux/src/utils/bindActionCreators.ts @@ -1,16 +1,16 @@ -import type { ActionCreatorsMapObject, Dispatch } from 'redux' - -export default function bindActionCreators( - actionCreators: ActionCreatorsMapObject, - dispatch: Dispatch -): ActionCreatorsMapObject { - const boundActionCreators: ActionCreatorsMapObject = {} - - for (const key in actionCreators) { - const actionCreator = actionCreators[key] - if (typeof actionCreator === 'function') { - boundActionCreators[key] = (...args) => dispatch(actionCreator(...args)) - } - } - return boundActionCreators -} +import type { ActionCreatorsMapObject, Dispatch } from 'redux' + +export default function bindActionCreators( + actionCreators: ActionCreatorsMapObject, + dispatch: Dispatch +): ActionCreatorsMapObject { + const boundActionCreators: ActionCreatorsMapObject = {} + + for (const key in actionCreators) { + const actionCreator = actionCreators[key] + if (typeof actionCreator === 'function') { + boundActionCreators[key] = (...args) => dispatch(actionCreator(...args)) + } + } + return boundActionCreators +} diff --git a/.yalc/react-redux/src/utils/isPlainObject.ts b/.yalc/react-redux/src/utils/isPlainObject.ts old mode 100644 new mode 100755 index 2157ea0..dbeae3e --- a/.yalc/react-redux/src/utils/isPlainObject.ts +++ b/.yalc/react-redux/src/utils/isPlainObject.ts @@ -1,17 +1,17 @@ -/** - * @param {any} obj The object to inspect. - * @returns {boolean} True if the argument appears to be a plain object. - */ -export default function isPlainObject(obj: unknown) { - if (typeof obj !== 'object' || obj === null) return false - - let proto = Object.getPrototypeOf(obj) - if (proto === null) return true - - let baseProto = proto - while (Object.getPrototypeOf(baseProto) !== null) { - baseProto = Object.getPrototypeOf(baseProto) - } - - return proto === baseProto -} +/** + * @param {any} obj The object to inspect. + * @returns {boolean} True if the argument appears to be a plain object. + */ +export default function isPlainObject(obj: unknown) { + if (typeof obj !== 'object' || obj === null) return false + + let proto = Object.getPrototypeOf(obj) + if (proto === null) return true + + let baseProto = proto + while (Object.getPrototypeOf(baseProto) !== null) { + baseProto = Object.getPrototypeOf(baseProto) + } + + return proto === baseProto +} diff --git a/.yalc/react-redux/src/utils/reactBatchedUpdates.native.ts b/.yalc/react-redux/src/utils/reactBatchedUpdates.native.ts old mode 100644 new mode 100755 index a92cd67..d34b930 --- a/.yalc/react-redux/src/utils/reactBatchedUpdates.native.ts +++ b/.yalc/react-redux/src/utils/reactBatchedUpdates.native.ts @@ -1,5 +1,5 @@ -/* eslint-disable import/namespace */ -/* eslint-disable import/named */ -import { unstable_batchedUpdates } from 'react-native' - -export { unstable_batchedUpdates } +/* eslint-disable import/namespace */ +/* eslint-disable import/named */ +import { unstable_batchedUpdates } from 'react-native' + +export { unstable_batchedUpdates } diff --git a/.yalc/react-redux/src/utils/reactBatchedUpdates.ts b/.yalc/react-redux/src/utils/reactBatchedUpdates.ts old mode 100644 new mode 100755 index 0fca6d8..1170969 --- a/.yalc/react-redux/src/utils/reactBatchedUpdates.ts +++ b/.yalc/react-redux/src/utils/reactBatchedUpdates.ts @@ -1 +1 @@ -export { unstable_batchedUpdates } from 'react-dom' +export { unstable_batchedUpdates } from 'react-dom' diff --git a/.yalc/react-redux/src/utils/shallowEqual.ts b/.yalc/react-redux/src/utils/shallowEqual.ts old mode 100644 new mode 100755 index e50c6be..fb2de3f --- a/.yalc/react-redux/src/utils/shallowEqual.ts +++ b/.yalc/react-redux/src/utils/shallowEqual.ts @@ -1,36 +1,36 @@ -function is(x: unknown, y: unknown) { - if (x === y) { - return x !== 0 || y !== 0 || 1 / x === 1 / y - } else { - return x !== x && y !== y - } -} - -export default function shallowEqual(objA: any, objB: any) { - if (is(objA, objB)) return true - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - const keysA = Object.keys(objA) - const keysB = Object.keys(objB) - - if (keysA.length !== keysB.length) return false - - for (let i = 0; i < keysA.length; i++) { - if ( - !Object.prototype.hasOwnProperty.call(objB, keysA[i]) || - !is(objA[keysA[i]], objB[keysA[i]]) - ) { - return false - } - } - - return true -} +function is(x: unknown, y: unknown) { + if (x === y) { + return x !== 0 || y !== 0 || 1 / x === 1 / y + } else { + return x !== x && y !== y + } +} + +export default function shallowEqual(objA: any, objB: any) { + if (is(objA, objB)) return true + + if ( + typeof objA !== 'object' || + objA === null || + typeof objB !== 'object' || + objB === null + ) { + return false + } + + const keysA = Object.keys(objA) + const keysB = Object.keys(objB) + + if (keysA.length !== keysB.length) return false + + for (let i = 0; i < keysA.length; i++) { + if ( + !Object.prototype.hasOwnProperty.call(objB, keysA[i]) || + !is(objA[keysA[i]], objB[keysA[i]]) + ) { + return false + } + } + + return true +} diff --git a/.yalc/react-redux/src/utils/useIsomorphicLayoutEffect.native.ts b/.yalc/react-redux/src/utils/useIsomorphicLayoutEffect.native.ts old mode 100644 new mode 100755 index e80393a..ae5cb36 --- a/.yalc/react-redux/src/utils/useIsomorphicLayoutEffect.native.ts +++ b/.yalc/react-redux/src/utils/useIsomorphicLayoutEffect.native.ts @@ -1,5 +1,5 @@ -import { useLayoutEffect } from 'react' - -// Under React Native, we know that we always want to use useLayoutEffect - -export const useIsomorphicLayoutEffect = useLayoutEffect +import { useLayoutEffect } from 'react' + +// Under React Native, we know that we always want to use useLayoutEffect + +export const useIsomorphicLayoutEffect = useLayoutEffect diff --git a/.yalc/react-redux/src/utils/useIsomorphicLayoutEffect.ts b/.yalc/react-redux/src/utils/useIsomorphicLayoutEffect.ts old mode 100644 new mode 100755 index d60f51c..b7a10fc --- a/.yalc/react-redux/src/utils/useIsomorphicLayoutEffect.ts +++ b/.yalc/react-redux/src/utils/useIsomorphicLayoutEffect.ts @@ -1,19 +1,19 @@ -import { useEffect, useLayoutEffect } from 'react' - -// React currently throws a warning when using useLayoutEffect on the server. -// To get around it, we can conditionally useEffect on the server (no-op) and -// useLayoutEffect in the browser. We need useLayoutEffect to ensure the store -// subscription callback always has the selector from the latest render commit -// available, otherwise a store update may happen between render and the effect, -// which may cause missed updates; we also must ensure the store subscription -// is created synchronously, otherwise a store update may occur before the -// subscription is created and an inconsistent state may be observed - -// Matches logic in React's `shared/ExecutionEnvironment` file -export const canUseDOM = !!( - typeof window !== 'undefined' && - typeof window.document !== 'undefined' && - typeof window.document.createElement !== 'undefined' -) - -export const useIsomorphicLayoutEffect = canUseDOM ? useLayoutEffect : useEffect +import { useEffect, useLayoutEffect } from 'react' + +// React currently throws a warning when using useLayoutEffect on the server. +// To get around it, we can conditionally useEffect on the server (no-op) and +// useLayoutEffect in the browser. We need useLayoutEffect to ensure the store +// subscription callback always has the selector from the latest render commit +// available, otherwise a store update may happen between render and the effect, +// which may cause missed updates; we also must ensure the store subscription +// is created synchronously, otherwise a store update may occur before the +// subscription is created and an inconsistent state may be observed + +// Matches logic in React's `shared/ExecutionEnvironment` file +export const canUseDOM = !!( + typeof window !== 'undefined' && + typeof window.document !== 'undefined' && + typeof window.document.createElement !== 'undefined' +) + +export const useIsomorphicLayoutEffect = canUseDOM ? useLayoutEffect : useEffect diff --git a/.yalc/react-redux/src/utils/useSyncExternalStore.ts b/.yalc/react-redux/src/utils/useSyncExternalStore.ts old mode 100644 new mode 100755 index b7f7773..23b619d --- a/.yalc/react-redux/src/utils/useSyncExternalStore.ts +++ b/.yalc/react-redux/src/utils/useSyncExternalStore.ts @@ -1,9 +1,9 @@ -import type { useSyncExternalStore } from 'use-sync-external-store' -import type { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector' - -export const notInitialized = () => { - throw new Error('uSES not initialized!') -} - -export type uSES = typeof useSyncExternalStore -export type uSESWS = typeof useSyncExternalStoreWithSelector +import type { useSyncExternalStore } from 'use-sync-external-store' +import type { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector' + +export const notInitialized = () => { + throw new Error('uSES not initialized!') +} + +export type uSES = typeof useSyncExternalStore +export type uSESWS = typeof useSyncExternalStoreWithSelector diff --git a/.yalc/react-redux/src/utils/verifyPlainObject.ts b/.yalc/react-redux/src/utils/verifyPlainObject.ts old mode 100644 new mode 100755 index 212d511..9f21f6f --- a/.yalc/react-redux/src/utils/verifyPlainObject.ts +++ b/.yalc/react-redux/src/utils/verifyPlainObject.ts @@ -1,14 +1,14 @@ -import isPlainObject from './isPlainObject' -import warning from './warning' - -export default function verifyPlainObject( - value: unknown, - displayName: string, - methodName: string -) { - if (!isPlainObject(value)) { - warning( - `${methodName}() in ${displayName} must return a plain object. Instead received ${value}.` - ) - } -} +import isPlainObject from './isPlainObject' +import warning from './warning' + +export default function verifyPlainObject( + value: unknown, + displayName: string, + methodName: string +) { + if (!isPlainObject(value)) { + warning( + `${methodName}() in ${displayName} must return a plain object. Instead received ${value}.` + ) + } +} diff --git a/.yalc/react-redux/src/utils/warning.ts b/.yalc/react-redux/src/utils/warning.ts old mode 100644 new mode 100755 index 92d3d63..8fcaf9d --- a/.yalc/react-redux/src/utils/warning.ts +++ b/.yalc/react-redux/src/utils/warning.ts @@ -1,21 +1,21 @@ -/** - * Prints a warning in the console if it exists. - * - * @param {String} message The warning message. - * @returns {void} - */ -export default function warning(message: string) { - /* eslint-disable no-console */ - if (typeof console !== 'undefined' && typeof console.error === 'function') { - console.error(message) - } - /* eslint-enable no-console */ - try { - // This error was thrown as a convenience so that if you enable - // "break on all exceptions" in your console, - // it would pause the execution at this line. - throw new Error(message) - /* eslint-disable no-empty */ - } catch (e) {} - /* eslint-enable no-empty */ -} +/** + * Prints a warning in the console if it exists. + * + * @param {String} message The warning message. + * @returns {void} + */ +export default function warning(message: string) { + /* eslint-disable no-console */ + if (typeof console !== 'undefined' && typeof console.error === 'function') { + console.error(message) + } + /* eslint-enable no-console */ + try { + // This error was thrown as a convenience so that if you enable + // "break on all exceptions" in your console, + // it would pause the execution at this line. + throw new Error(message) + /* eslint-disable no-empty */ + } catch (e) {} + /* eslint-enable no-empty */ +} diff --git a/.yalc/react-redux/yalc.sig b/.yalc/react-redux/yalc.sig index cc44405..864cd43 100644 --- a/.yalc/react-redux/yalc.sig +++ b/.yalc/react-redux/yalc.sig @@ -1 +1 @@ -ea764a3365bc9f3c88f6e77bbb637f94 \ No newline at end of file +b9f845048d76117633fd4e318d90540d \ No newline at end of file diff --git a/runBenchmarks.ts b/runBenchmarks.ts index 78f44bc..646dc7d 100644 --- a/runBenchmarks.ts +++ b/runBenchmarks.ts @@ -2,7 +2,6 @@ 'use strict' import path from 'path' -import puppeteer from 'puppeteer' import playwright from 'playwright' import fs from 'fs' import Table from 'cli-table2' @@ -10,6 +9,7 @@ import _ from 'lodash' import glob from 'glob' import yargs from 'yargs/yargs' import chalk from 'chalk' +import { devices as replayDevices } from '@replayio/playwright' import { capturePageStats, @@ -59,6 +59,12 @@ const args = yargs(process.argv.slice(2)) type: 'boolean', default: true, }) + .option('record', { + alias: 'r', + describe: 'Make a Replay recording of each benchmark run', + type: 'boolean', + default: false, + }) .help('h') .alias('h', 'help') @@ -208,17 +214,23 @@ async function runBenchmarks({ length, trace, headless, + record, }: { scenarios: string[] versions: string[] length: number trace: boolean headless: boolean + record: boolean }) { console.log('Scenarios: ', scenarios) const distFolder = path.resolve('dist') const server = await runServer(9999, distFolder) + const launchOptions: Partial = record + ? replayDevices['Replay Chromium'].launchOptions + : {} + for (let scenario of scenarios) { const versionPerfEntries = {} @@ -226,8 +238,10 @@ async function runBenchmarks({ for (let version of versions) { console.log(` React-Redux version: ${version}`) + const browser = await playwright.chromium.launch({ headless, + ...launchOptions, }) const folderPath = path.join(distFolder, version, scenario) diff --git a/src/scenarios/many-components-many-slices/App.tsx b/src/scenarios/many-components-many-slices/App.tsx index 3853326..6f16420 100644 --- a/src/scenarios/many-components-many-slices/App.tsx +++ b/src/scenarios/many-components-many-slices/App.tsx @@ -10,7 +10,10 @@ const createComponent = (index: number) => { const sliceName = getSliceName(index) return function Component() { - const counter = useSelector((state: RootState) => state[sliceName].counter) + const counter = useSelector( + (state: RootState) => + state[sliceName].deeply.nested.really.deeply.nested.counter.value + ) return

} diff --git a/src/scenarios/many-components-many-slices/constants.ts b/src/scenarios/many-components-many-slices/constants.ts index 5cdcbfb..409230c 100644 --- a/src/scenarios/many-components-many-slices/constants.ts +++ b/src/scenarios/many-components-many-slices/constants.ts @@ -1 +1 @@ -export const NUMBER_OF_COMPONENTS = 5000 +export const NUMBER_OF_COMPONENTS = 1000 diff --git a/src/scenarios/many-components-many-slices/state.ts b/src/scenarios/many-components-many-slices/state.ts index 8483fe6..043dff3 100644 --- a/src/scenarios/many-components-many-slices/state.ts +++ b/src/scenarios/many-components-many-slices/state.ts @@ -7,11 +7,21 @@ const createStateSlice = (index: number) => { return createSlice({ name: getSliceName(index), initialState: { - counter: 0, + deeply: { + nested: { + really: { + deeply: { + nested: { + counter: { value: 0 }, + }, + }, + }, + }, + }, }, reducers: { increment(state) { - state.counter += 1 + state.deeply.nested.really.deeply.nested.counter.value += 1 }, }, }) diff --git a/src/scenarios/rtkq-separate-queries/App.tsx b/src/scenarios/rtkq-separate-queries/App.tsx new file mode 100644 index 0000000..5c4d582 --- /dev/null +++ b/src/scenarios/rtkq-separate-queries/App.tsx @@ -0,0 +1,143 @@ +import React, { ChangeEventHandler, useState } from 'react' +import { api, config, useSomeQuery } from './api' +import { StrictMode, Fragment, Profiler } from 'react' +import { Child, Invalidate } from './rtk-query' + +import { NUMBER_OF_COMPONENTS } from './constants' + +function useNumberValue( + initialValue: number, + afterChange?: (value: number) => void +) { + const [value, setState] = useState(initialValue) + const onChange: ChangeEventHandler = (e) => { + const value = e.currentTarget.valueAsNumber + setState(value) + afterChange?.(value) + } + return { value, onChange } +} + +function useBooleanValue(initialValue: boolean) { + const [checked, setState] = useState(initialValue) + const onChange: ChangeEventHandler = (e) => { + setState(e.currentTarget.checked) + } + return { checked, onChange } +} + +function useStringValue(initialValue: string) { + const [value, setState] = useState(initialValue) + const onChange: ChangeEventHandler = (e) => { + setState(e.currentTarget.value) + } + return { value, onChange } +} + +const onRenderCallback: React.ProfilerOnRenderCallback = ( + id, // the "id" prop of the Profiler tree that has just committed + phase, // either "mount" (if the tree just mounted) or "update" (if it re-rendered) + actualDuration, // time spent rendering the committed update + baseDuration, // estimated time to render the entire subtree without memoization + startTime, // when React began rendering this update + commitTime, // when React committed this update + interactions // the Set of interactions belonging to this update +) => { + // console.log(`update of type ${phase} took ${actualDuration}ms`, { + // id, + // phase, + // actualDuration, + // baseDuration, + // startTime, + // commitTime, + // interactions, + // }) +} + +export default function App() { + const numberOfChildren = useNumberValue(NUMBER_OF_COMPONENTS) + const strictMode = useBooleanValue(false) + const individualQueries = useBooleanValue(true) + const responseTimesFrom = useNumberValue( + config.minimumRequestDuration, + (v) => (config.minimumRequestDuration = v) + ) + const responseTimesTo = useNumberValue( + config.maximumRequestDuration, + (v) => (config.maximumRequestDuration = v) + ) + const childrenMounted = useBooleanValue(true) + const skip = useBooleanValue(false) + const prefix = useStringValue('test') + const StrictWrapper = strictMode ? StrictMode : Fragment + + return ( +
+

RTK Perf test

+ + + + + + + +
+ response times: (ms)  + + +
+ + + +
+ {childrenMounted.checked && + new Array(numberOfChildren.value).fill(null).map((_, idx) => { + return ( + + ) + })} +
+
+
+
+ ) +} diff --git a/src/scenarios/rtkq-separate-queries/api.ts b/src/scenarios/rtkq-separate-queries/api.ts new file mode 100644 index 0000000..1608d4b --- /dev/null +++ b/src/scenarios/rtkq-separate-queries/api.ts @@ -0,0 +1,34 @@ +import { createApi, TagDescription } from '@reduxjs/toolkit/query/react' + +export const config = { + minimumRequestDuration: 50, + maximumRequestDuration: 100, + requestsPerArg: {} as Record, +} + +const t: TagDescription<'abcd'> = 'abcd' + +export const baseQuery = (arg: string) => { + return new Promise<{ data: string }>((resolve) => { + config.requestsPerArg[arg] ??= 0 + const nextNumber = ++config.requestsPerArg[arg] + const duration = + config.minimumRequestDuration + + Math.random() * + (config.maximumRequestDuration - config.minimumRequestDuration) + setTimeout(() => resolve({ data: `${arg}${nextNumber}` }), duration) + }) +} + +export const api = createApi({ + baseQuery, + tagTypes: ['QUERY'], + endpoints: (build) => ({ + some: build.query({ + query: (arg: string) => arg, + providesTags: ['QUERY'], + }), + }), +}) + +export const { useSomeQuery } = api diff --git a/src/scenarios/rtkq-separate-queries/constants.ts b/src/scenarios/rtkq-separate-queries/constants.ts new file mode 100644 index 0000000..0156bf2 --- /dev/null +++ b/src/scenarios/rtkq-separate-queries/constants.ts @@ -0,0 +1 @@ +export const NUMBER_OF_COMPONENTS = 1000 diff --git a/src/scenarios/rtkq-separate-queries/index.tsx b/src/scenarios/rtkq-separate-queries/index.tsx new file mode 100644 index 0000000..0f34ad9 --- /dev/null +++ b/src/scenarios/rtkq-separate-queries/index.tsx @@ -0,0 +1,6 @@ +import { store } from './store' +import App from './App' + +import { renderApp } from '../../common' + +renderApp(App, store) diff --git a/src/scenarios/rtkq-separate-queries/listenerMiddleware.ts b/src/scenarios/rtkq-separate-queries/listenerMiddleware.ts new file mode 100644 index 0000000..7a12c9c --- /dev/null +++ b/src/scenarios/rtkq-separate-queries/listenerMiddleware.ts @@ -0,0 +1,43 @@ +// listenerMiddleware.ts +import { createListenerMiddleware, addListener } from '@reduxjs/toolkit' +import type { TypedStartListening, TypedAddListener } from '@reduxjs/toolkit' + +import type { RootState, AppDispatch } from './store' +import { api } from './api' +import { NUMBER_OF_COMPONENTS } from './constants' + +export const listenerMiddleware = createListenerMiddleware() + +export type AppStartListening = TypedStartListening + +export const startAppListening = + listenerMiddleware.startListening as AppStartListening + +export const addAppListener = addListener as TypedAddListener< + RootState, + AppDispatch +> + +startAppListening({ + matcher: api.endpoints.some.matchFulfilled, + effect: async (action, listenerApi) => { + const state = listenerApi.getState() + const { queries, subscriptions } = state.api + const allQueries = Object.values(queries) + + const allSubscriptions = Object.values(subscriptions) + .map((entry) => Object.values(entry!)) + .flat() + + console.log('Number of subscriptions: ', allSubscriptions.length) + + if ( + allSubscriptions.length === NUMBER_OF_COMPONENTS && + allQueries.every((query) => query?.status === 'fulfilled') + ) { + console.log('All queries fulfilled, re-fetching') + await listenerApi.delay(10) + listenerApi.dispatch(api.util.invalidateTags(['QUERY'])) + } + }, +}) diff --git a/src/scenarios/rtkq-separate-queries/rtk-query.tsx b/src/scenarios/rtkq-separate-queries/rtk-query.tsx new file mode 100644 index 0000000..575e1a3 --- /dev/null +++ b/src/scenarios/rtkq-separate-queries/rtk-query.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import { api, useSomeQuery } from './api' +import { useDispatch } from 'react-redux' + +export function Child({ arg, skip }: { arg: string; skip: boolean }) { + const result = useSomeQuery(arg, { skip }) + return ( +
+ {result.isUninitialized + ? 'uninitialized' + : result.isLoading + ? 'isLoading' + : `${result.data} ${result.isFetching ? '(fetching)' : ''}`} +
+ ) +} + +export const Invalidate = () => { + const dispatch = useDispatch() + return ( + + ) +} diff --git a/src/scenarios/rtkq-separate-queries/store.ts b/src/scenarios/rtkq-separate-queries/store.ts new file mode 100644 index 0000000..10409eb --- /dev/null +++ b/src/scenarios/rtkq-separate-queries/store.ts @@ -0,0 +1,24 @@ +import { + configureStore, + autoBatchEnhancer, + createListenerMiddleware, +} from '@reduxjs/toolkit' + +import { listenerMiddleware } from './listenerMiddleware' +import { api } from './api' + +export const store = configureStore({ + reducer: { + [api.reducerPath]: api.reducer, + }, + middleware(gDm) { + return gDm({ immutableCheck: false, serializableCheck: false }) + .prepend(listenerMiddleware.middleware) + .concat(api.middleware) + }, + enhancers: (existingEnhancers) => + existingEnhancers.concat(autoBatchEnhancer({ type: 'raf' })), +}) + +export type RootState = ReturnType +export type AppDispatch = typeof store.dispatch diff --git a/yalc.lock b/yalc.lock index 577dbf3..775994d 100644 --- a/yalc.lock +++ b/yalc.lock @@ -2,7 +2,7 @@ "version": "v1", "packages": { "react-redux": { - "signature": "ea764a3365bc9f3c88f6e77bbb637f94", + "signature": "b9f845048d76117633fd4e318d90540d", "file": true, "replaced": "^8.1.0" } diff --git a/yarn.lock b/yarn.lock index ccf5d22..36f62ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3150,7 +3150,7 @@ __metadata: "react-redux-8.1.0-autotracking@file:.yalc/react-redux::locator=%40reduxjs%2Freact-redux-benchmarks%40workspace%3A.": version: 8.1.1 - resolution: "react-redux-8.1.0-autotracking@file:.yalc/react-redux#.yalc/react-redux::hash=4f3dbd&locator=%40reduxjs%2Freact-redux-benchmarks%40workspace%3A." + resolution: "react-redux-8.1.0-autotracking@file:.yalc/react-redux#.yalc/react-redux::hash=91b594&locator=%40reduxjs%2Freact-redux-benchmarks%40workspace%3A." dependencies: "@babel/runtime": ^7.12.1 "@types/hoist-non-react-statics": ^3.3.1 @@ -3176,7 +3176,7 @@ __metadata: optional: true redux: optional: true - checksum: 82c940becc5c6a0ef9d5e9cb559ed2289ae17e5cf7504cc9a8cf6e212aeed3e4ed02f62c1b4a03c11b9d80cdbaf5904fa9def7da37b4e3a5168b7d7a2b3aefb5 + checksum: 441a089f1e04b6645949588a5552f909c1ccf2765a909ec84fe8e1e9adad3caa0dc74c670e85b8031de26b0f51c788bfb467eba1ea2d985fdce75f1c0f963d6f languageName: node linkType: hard