├── .babelrc.js ├── .eslintignore ├── .eslintrc ├── .github └── ISSUE_TEMPLATE │ ├── ---bug-report.md │ └── ---support-usage-question.md ├── .gitignore ├── .prettierrc ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── codecov.yml ├── context.md ├── docs ├── README.md ├── api │ ├── Provider.md │ ├── connect-advanced.md │ └── connect.md ├── introduction │ ├── basic-tutorial.md │ ├── quick-start.md │ └── why-use-react-redux.md ├── troubleshooting.md └── using-react-redux │ ├── accessing-store.md │ ├── connect-dispatching-actions-with-mapDispatchToProps.md │ └── connect-extracting-data-with-mapStateToProps.md ├── package.json ├── rollup.config.js ├── src ├── components │ ├── Context.js │ ├── Provider.js │ └── connectAdvanced.js ├── connect │ ├── connect.js │ ├── mapDispatchToProps.js │ ├── mapStateToProps.js │ ├── mergeProps.js │ ├── selectorFactory.js │ ├── verifySubselectors.js │ └── wrapMapToProps.js ├── index.js └── utils │ ├── Subscription.js │ ├── isPlainObject.js │ ├── readContext.js │ ├── shallowEqual.js │ ├── verifyPlainObject.js │ ├── warning.js │ └── wrapActionCreators.js ├── test ├── .eslintrc ├── babel-transformer.jest.js ├── components │ ├── Provider.spec.js │ ├── connect.spec.js │ └── connectAdvanced.spec.js ├── install-test-deps.js ├── integration │ └── server-rendering.spec.js ├── react │ ├── 16.4 │ │ └── package.json │ ├── 16.5 │ │ ├── package-lock.json │ │ └── package.json │ ├── 16.6 │ │ ├── package-lock.json │ │ └── package.json │ └── 16.8 │ │ └── package.json ├── run-tests.js └── utils │ ├── isPlainObject.spec.js │ ├── shallowEqual.spec.js │ └── wrapActionCreators.spec.js └── website ├── README.md ├── _redirects ├── core └── Footer.js ├── package.json ├── pages └── en │ ├── 404.js │ └── index.js ├── sidebars.json ├── siteConfig.js └── static ├── css ├── 404.css ├── codeblock.css └── custom.css ├── img ├── external-link-square-alt-solid.svg ├── favicon │ └── favicon.ico ├── github-brands.svg ├── noun_Box_1664404.svg ├── noun_Certificate_1945625.svg ├── noun_Check_1870817.svg ├── noun_Rocket_1245262.svg ├── redux-logo-landscape.png ├── redux-logo-twitter.png ├── redux.svg └── redux_white.svg └── scripts ├── codeblock.js └── sidebarScroll.js /.babelrc.js: -------------------------------------------------------------------------------- 1 | const { NODE_ENV, BABEL_ENV } = process.env 2 | const cjs = NODE_ENV === 'test' || BABEL_ENV === 'commonjs' 3 | const loose = true 4 | 5 | module.exports = { 6 | presets: [['@babel/env', { loose, modules: false }]], 7 | plugins: [ 8 | ['@babel/proposal-decorators', { legacy: true }], 9 | ['@babel/proposal-object-rest-spread', { loose }], 10 | '@babel/transform-react-jsx', 11 | cjs && ['@babel/transform-modules-commonjs', { loose }], 12 | ['@babel/transform-runtime', { useESModules: !cjs }], 13 | ].filter(Boolean), 14 | } 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:import/recommended", 6 | "plugin:react/recommended", 7 | "plugin:prettier/recommended" 8 | ], 9 | "parserOptions": { 10 | "ecmaVersion": 6, 11 | "sourceType": "module", 12 | "ecmaFeatures": { 13 | "jsx": true, 14 | "experimentalObjectRestSpread": true 15 | } 16 | }, 17 | "env": { 18 | "browser": true, 19 | "mocha": true, 20 | "node": true 21 | }, 22 | "rules": { 23 | "valid-jsdoc": 2, 24 | "react/jsx-uses-react": 1, 25 | "react/jsx-no-undef": 2, 26 | "react/jsx-wrap-multilines": 2, 27 | "react/no-string-refs": 0 28 | }, 29 | "plugins": [ 30 | "import", 31 | "react" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Something isn't working correctly. 4 | 5 | --- 6 | 7 | **Do you want to request a _feature_ or report a _bug_?** 8 | 9 | (If this is a _usage question_, please **do not post it here**—post it on [Stack Overflow](http://stackoverflow.com/questions/tagged/redux) instead. If this is not a “feature” or a “bug”, or the phrase “How do I...?” applies, then it's probably a usage question.) 10 | 11 | 12 | **What is the current behavior?** 13 | 14 | 15 | 16 | **If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to a CodeSandbox (https://codesandbox.io/s/new) or RN Snack (https://snack.expo.io/) example below:** 17 | 18 | 19 | 20 | **What is the expected behavior?** 21 | 22 | 23 | 24 | **Which versions of React, ReactDOM/React Native, Redux, and React Redux are you using? Which browser and OS are affected by this issue? Did this work in previous versions of React Redux?** 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---support-usage-question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F914 Support/Usage Question" 3 | about: For usage questions, please use Stack Overflow or Reactiflux! 4 | 5 | --- 6 | 7 | This is a bug tracker, not a support system. For usage questions, please use Stack Overflow or Reactiflux where there are a lot more people ready to help you out. Thanks! 8 | 9 | https://stackoverflow.com/questions/tagged/redux 10 | https://www.reactiflux.com/ 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | dist 5 | lib 6 | .nyc_output 7 | coverage 8 | es 9 | test/**/lcov.info 10 | test/**/lcov-report 11 | test/react/*/test/**/*.spec.js 12 | test/react/**/src 13 | test/jest-config.json 14 | lcov.info 15 | 16 | lib/core/metadata.js 17 | lib/core/MetadataBlog.js 18 | 19 | website/translated_docs 20 | website/build/ 21 | website/yarn.lock 22 | website/node_modules 23 | website/i18n/* 24 | .idea/ 25 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "tabWidth": 2 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | env: 5 | matrix: 6 | - REACT=16.4 7 | - REACT=16.5 8 | - REACT=16.6 9 | - REACT=16.8 10 | sudo: false 11 | script: 12 | - npm test 13 | after_success: 14 | - npm run coverage 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | v6.5.2 2 | *fix missing exports 3 | 4 | v6.5.1 5 | *fix warning on invalid mStP or mDtP 6 | 7 | v6.5.0 8 | *first react-redux-fork release 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. 6 | 7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 8 | 9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 10 | 11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 12 | 13 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 14 | 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | We are open to, and grateful for, any contributions made by the community. By contributing to React Redux, you agree to abide by the [code of conduct](https://github.com/reduxjs/react-redux/blob/master/CODE_OF_CONDUCT.md). 3 | 4 | ## Reporting Issues and Asking Questions 5 | Before opening an issue, please search the [issue tracker](https://github.com/reduxjs/react-redux/issues) to make sure your issue hasn't already been reported. 6 | 7 | Please ask any general and implementation specific questions on [Stack Overflow with a Redux tag](http://stackoverflow.com/questions/tagged/redux?sort=votes&pageSize=50) for support. 8 | 9 | ## Development 10 | 11 | Visit the [Issue tracker](https://github.com/reduxjs/react-redux/issues) to find a list of open issues that need attention. 12 | 13 | Fork, then clone the repo: 14 | ``` 15 | git clone https://github.com/your-username/react-redux.git 16 | ``` 17 | 18 | ### Building 19 | 20 | Running the `build` task will create both a CommonJS module-per-module build and a UMD build. 21 | ``` 22 | npm run build 23 | ``` 24 | 25 | To create just a CommonJS module-per-module build: 26 | ``` 27 | npm run build:lib 28 | ``` 29 | 30 | To create just a UMD build: 31 | ``` 32 | npm run build:umd 33 | npm run build:umd:min 34 | ``` 35 | 36 | ### Testing and Linting 37 | 38 | To run the tests in the latest React version: 39 | ``` 40 | npm run test 41 | ``` 42 | 43 | To run in explicit React versions (the number is the version, so `test:16.3` will run in React version `16.3`): 44 | ``` 45 | REACT=16.4 npm run test 46 | ``` 47 | 48 | To run tests in all supported React versions, `16.4`, 16.5`, 49 | ``` 50 | REACT=all npm run test 51 | ``` 52 | 53 | To continuously watch and run tests, run the following: 54 | ``` 55 | npm run test -- --watch 56 | ``` 57 | 58 | To perform linting with `eslint`, run the following: 59 | ``` 60 | npm run lint 61 | ``` 62 | 63 | #### Adding a new React version for testing 64 | 65 | To add a new version of React to test react-redux against, create a directory structure 66 | in this format for React version `XX`: 67 | 68 | ``` 69 | test/ 70 | react/ 71 | XX/ 72 | package.json 73 | test/ 74 | ``` 75 | 76 | So, for example, to test against React 15.4: 77 | 78 | 79 | ``` 80 | test/ 81 | react/ 82 | 15.4/ 83 | package.json 84 | test/ 85 | ``` 86 | 87 | The package.json must include the correct versions of `react` & `react-dom` 88 | as well as the needed `create-react-class` like this: 89 | 90 | ```json 91 | { 92 | "private": true, 93 | "devDependencies": { 94 | "create-react-class": "^15.6.3", 95 | "react": "15.4", 96 | "react-dom": "15.4" 97 | } 98 | } 99 | ``` 100 | 101 | Then you can run tests against this version with: 102 | 103 | ``` 104 | REACT=15.4 npm run test 105 | ``` 106 | 107 | and the new version will also be automatically included in 108 | 109 | ``` 110 | REACT=all npm run test 111 | ``` 112 | 113 | In addition, the new version should be added to the .travis.yml matrix list: 114 | 115 | ```yaml 116 | language: node_js 117 | node_js: 118 | - "8" 119 | before_install: 120 | - 'nvm install-latest-npm' 121 | env: 122 | matrix: 123 | - REACT=16.4 124 | - REACT=16.5 125 | sudo: false 126 | script: 127 | - npm run lint 128 | - npm run test 129 | after_success: 130 | - npm run coverage 131 | ``` 132 | 133 | ### New Features 134 | 135 | Please open an issue with a proposal for a new feature or refactoring before starting on the work. We don't want you to waste your efforts on a pull request that we won't want to accept. 136 | 137 | ## Submitting Changes 138 | 139 | * Open a new issue in the [Issue tracker](https://github.com/reduxjs/react-redux/issues). 140 | * Fork the repo. 141 | * Create a new feature branch based off the `master` branch. 142 | * Make sure all tests pass and there are no linting errors. 143 | * Submit a pull request, referencing any issues it addresses. 144 | 145 | Please try to keep your pull request focused in scope and avoid including unrelated commits. 146 | 147 | After you have submitted your pull request, we'll try to get back to you as soon as possible. We may suggest some changes or improvements. 148 | 149 | Thank you for contributing! 150 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present Dan Abramov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | React Redux Fork 2 | ========================= 3 | 4 | React Redux, but just up to 98x faster. (Forked from 6.0) 5 | 6 | ## Installation 7 | 8 | React Redux Fork requires **React 16.6 or later.** 9 | 10 | ``` 11 | npm install --save react-redux-fork 12 | ``` 13 | Using with yarn alias 14 | ``` 15 | yarn add react-redux@npm:react-redux-fork 16 | ``` 17 | Fast upgrade with alias your packages.json with yarn alias 18 | ``` 19 | "react-redux": "npm:react-redux-fork@^6.5.2" 20 | ``` 21 | 22 | ## React Native 23 | ``` 24 | npm install --save react-redux-fork-native 25 | ``` 26 | 27 | ## Benchmark 28 | Done with official tool : https://github.com/reduxjs/react-redux-benchmarks 29 | 30 | Results for benchmark deeptree: 31 | FPS Avg : 2.20x - Render Avg : **2.20x** 32 | ``` 33 | ┌────────────┬─────────┬──────────────┬───────────┬───────────┬──────────┬──────────────────────┐ 34 | │ Version │ Avg FPS │ Render │ Scripting │ Rendering │ Painting │ FPS Values │ 35 | │ │ │ (Mount, Avg) │ │ │ │ │ 36 | ├────────────┼─────────┼──────────────┼───────────┼───────────┼──────────┼──────────────────────┤ 37 | │ 6.0.0 │ 27.00 │ 68.91, 1.96 │ 5925.95 │ 2423.24 │ 1028.30 │ 27,27 │ 38 | ├────────────┼─────────┼──────────────┼───────────┼───────────┼──────────┼──────────────────────┤ 39 | │ 6.5.0-fork │ 59.26 │ 65.97, 0.89 │ 2836.54 │ 3903.35 │ 2043.18 │ 59,60,59,60,59,60,60 │ 40 | └────────────┴─────────┴──────────────┴───────────┴───────────┴──────────┴──────────────────────┘ 41 | ``` 42 | Results for benchmark forms: 43 | FPS Avg : 1.00x - Render Avg : **9.00x** 44 | ``` 45 | ┌────────────┬─────────┬──────────────┬───────────┬───────────┬──────────┬────────────────────────────┐ 46 | │ Version │ Avg FPS │ Render │ Scripting │ Rendering │ Painting │ FPS Values │ 47 | │ │ │ (Mount, Avg) │ │ │ │ │ 48 | ├────────────┼─────────┼──────────────┼───────────┼───────────┼──────────┼────────────────────────────┤ 49 | │ 6.0.0 │ 54.33 │ 752.01, 1.35 │ 3165.75 │ 275.90 │ 853.41 │ 53,55,56,54,55,51,51 │ 50 | ├────────────┼─────────┼──────────────┼───────────┼───────────┼──────────┼────────────────────────────┤ 51 | │ 6.5.0-fork │ 54.99 │ 752.70, 0.15 │ 1488.76 │ 260.11 │ 938.07 │ 58,55,54,55,54,55,54,55,55 │ 52 | └────────────┴─────────┴──────────────┴───────────┴───────────┴──────────┴────────────────────────────┘ 53 | ``` 54 | Results for benchmark stockticker: 55 | FPS Avg : 2.05x - Render Avg : **14.00x** 56 | ``` 57 | ┌────────────┬─────────┬──────────────┬───────────┬───────────┬──────────┬─────────────────────────┐ 58 | │ Version │ Avg FPS │ Render │ Scripting │ Rendering │ Painting │ FPS Values │ 59 | │ │ │ (Mount, Avg) │ │ │ │ │ 60 | ├────────────┼─────────┼──────────────┼───────────┼───────────┼──────────┼─────────────────────────┤ 61 | │ 6.0.0 │ 28.82 │ 129.28, 4.05 │ 7540.88 │ 1712.85 │ 547.74 │ 30,29,28,29,28,29,28,28 │ 62 | ├────────────┼─────────┼──────────────┼───────────┼───────────┼──────────┼─────────────────────────┤ 63 | │ 6.5.0-fork │ 59.35 │ 130.42, 0.29 │ 4144.02 │ 3913.76 │ 1455.73 │ 60,59,60,59,59 │ 64 | └────────────┴─────────┴──────────────┴───────────┴───────────┴──────────┴─────────────────────────┘ 65 | ``` 66 | Results for benchmark tree-view: 67 | FPS Avg : 1.15x - Render Avg : **98.70x** 68 | ``` 69 | ┌────────────┬─────────┬───────────────┬───────────┬───────────┬──────────┬────────────────────────────┐ 70 | │ Version │ Avg FPS │ Render │ Scripting │ Rendering │ Painting │ FPS Values │ 71 | │ │ │ (Mount, Avg) │ │ │ │ │ 72 | ├────────────┼─────────┼───────────────┼───────────┼───────────┼──────────┼────────────────────────────┤ 73 | │ 6.0.0 │ 44.55 │ 348.76, 12.83 │ 5351.81 │ 2675.88 │ 93.59 │ 46,52,40,43,52,37,39,38,38 │ 74 | ├────────────┼─────────┼───────────────┼───────────┼───────────┼──────────┼────────────────────────────┤ 75 | │ 6.5.0-fork │ 50.89 │ 348.82, 0.13 │ 1974.17 │ 4291.97 │ 164.02 │ 50,57,49,48,49,51,54,49,49 │ 76 | └────────────┴─────────┴───────────────┴───────────┴───────────┴──────────┴────────────────────────────┘ 77 | ``` 78 | Results for benchmark twitter-lite: 79 | FPS Avg : 1.03x - Render Avg : **12.70x** 80 | ``` 81 | ┌────────────┬─────────┬──────────────┬───────────┬───────────┬──────────┬─────────────────────────┐ 82 | │ Version │ Avg FPS │ Render │ Scripting │ Rendering │ Painting │ FPS Values │ 83 | │ │ │ (Mount, Avg) │ │ │ │ │ 84 | ├────────────┼─────────┼──────────────┼───────────┼───────────┼──────────┼─────────────────────────┤ 85 | │ 6.0.0 │ 56.91 │ 2.17, 1.91 │ 7469.43 │ 654.56 │ 127.39 │ 59,60,59,58,53,48,48 │ 86 | ├────────────┼─────────┼──────────────┼───────────┼───────────┼──────────┼─────────────────────────┤ 87 | │ 6.5.0-fork │ 59.38 │ 2.21, 0.15 │ 2271.48 │ 874.72 │ 141.36 │ 59,60,59,60,59,60,59,59 │ 88 | └────────────┴─────────┴──────────────┴───────────┴───────────┴──────────┴─────────────────────────┘ 89 | ``` 90 | 91 | ## Abstract 92 | If you are reading this page, probably you already know that React-Redux 6.x performance aren't very good. 93 | IMHO, this is due to some technical errors: 94 | 1) use new React 16 Context to propagate Store value: React Context is very slow, and the common use is the 95 | propagation of values that rarely change like theme or language 96 | 2) use of {value=>.... to read value of store : => render prop pattern, even if is a 97 | common and easy to use react pattern is slow 98 | 99 | This fork use the new React Context only for propagate store instance, and do not use Context.Consumer render Prop. 100 | 101 | These optimizations require to bump react version to 16.6 (but if you are using React 16.4 should be safe upgrade to 16.6) 102 | 103 | **In summary, react-redux-fork restore the performance of react-redux 5.x without Warnings for deprecated React lifycicles and legacy Context, 104 | (React 17 ready) and in some use cases is faster than old 5.x due _React batched updates_, that batched setState to do them in Top-Down mode.** 105 | 106 | ## Differences from React-Redux 6.0.0 107 | * React version > 16.6 instead of 16.4 108 | * Direct use of { storeValue => ... is no more supported, too slow. use connect instead. 109 | * using context prop on connected components is not recommended. Instead use connect(..., options.context:MyCustomContext) if you need custom context (rarely use-case, like libs). Read more about react-redux and context prop here: https://github.com/salvoravida/react-redux-fork/blob/master/context.md 110 | 111 | ## Why not just a PR to official react-redux project? 112 | Of Course i have done this before doing a fork, but rr manteiners do not seem to be opened to performances fixes :D 113 | 114 | Luckily it's an open source project! 115 | 116 | ## Note 117 | Someones think that "you may do not need Redux", and that's true for small apps, but if you need it, you are probably working on a large enterprise app, 118 | so not only you need Redux, but you need it with the best possible performances! 119 | That's why i decide to fork this project. 120 | 121 | ## redux-first-history 122 | If you need performances, i would like to suggest you to have a look to this project https://github.com/salvoravida/redux-first-history 123 | In summary, replace "connected-react-router" with "redux-first-history" to get benefits from the best possible Redux approach: 124 | * one way data-flow 125 | * one unique source of truth 126 | 127 | # Feedback 128 | Let me know what do you think!
129 | *Enjoy it? Star this project!* :D 130 | 131 | Contributors 132 | ------------ 133 | See [Contributors](https://github.com/salvoravida/redux-first-history/graphs/contributors). 134 | 135 | 136 | ## License 137 | 138 | MIT 139 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | -------------------------------------------------------------------------------- /context.md: -------------------------------------------------------------------------------- 1 | Some points about customContext and React-Redux 2 | 3 | ### 99% You do NOT need customContext on React-Redux 4 | In 99% of Apps you have One Store @ App Root. 5 | 6 | If it is your case just use 6.x the SAME EXACT WAY you have already used 5.x 7 | ```javascript 8 | 9 | 10 | 11 | 12 | 13 | //where 14 | const ConnectedComp = connect(mapState,mapDisp)(Component); 15 | ``` 16 | 17 | ### 1% You May need customContext on React-Redux 18 | In 1% of Apps you have to use Two or More Store on the same app. 19 | 20 | If it is your case, in both 6.0.0 and 6.5.0 you can have this code 21 | ```javascript 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | //where 31 | const ConnectedComponentWithContext1= connect(mapState,mapDispat,mergeProp, { context:CustomContext1 })(Component) 32 | const ConnectedComponentWithContext2= connect(mapState,mapDispat,mergeProp, { context:CustomContext2 })(Component) 33 | ``` 34 | Also in 6.0.0 you can write an EQUIVALENT way that is supported on 6.5.0 but only when you use React >16.8 (due to performance issue on 16.6 that do not have readContext api) 35 | 36 | ```javascript 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | //where 47 | const ConnectedComponent= connect(mapState,mapDispat)(Component) 48 | ``` 49 | 50 | That's all 51 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | 3 | - Introduction 4 | - [Quick Start: adding React Redux to a React todo app](./introduction/quick-start.md) 5 | - [Basic Tutorial](./introduction/basic-tutorial.md) 6 | - Using React Redux 7 | - [Connect: Extracting Data with `mapStateToProps`](./using-react-redux/connect-extracting-data-with-mapStateToProps.md) 8 | - API 9 | - [``](./api/Provider.md) 10 | - [`connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])`](./api/connect.md) 11 | - [`connectAdvanced(selectorFactory, [connectOptions])`](./api/connect-advanced.md) 12 | - [Troubleshooting](troubleshooting.md#troubleshooting) 13 | -------------------------------------------------------------------------------- /docs/api/Provider.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: provider 3 | title: Provider 4 | sidebar_label: Provider 5 | hide_title: true 6 | --- 7 | 8 | # `Provider` 9 | 10 | ## Overview 11 | 12 | The `` makes the Redux `store` available to any nested components that have been wrapped in the `connect()` function. 13 | 14 | Since any React component in a React Redux app can be connected, most applications will render a `` at the top level, with the entire app’s component tree inside of it. 15 | 16 | Normally, you can’t use a connected component unless it is nested inside of a ``. 17 | 18 | ### Props 19 | 20 | `store` ([Redux Store](https://redux.js.org/api/store)) 21 | The single Redux `store` in your application. 22 | 23 | `children` (ReactElement) 24 | The root of your component hierarchy. 25 | 26 | `context` 27 | You may provide a context instance. If you do so, you will need to provide the same context instance to all of your connected components as well. Failure to provide the correct context results in runtime error: 28 | 29 | > Invariant Violation 30 | > 31 | > Could not find "store" in the context of "Connect(MyComponent)". Either wrap the root component in a ``, or pass a custom React context provider to `` and the corresponding React context consumer to Connect(Todo) in connect options. 32 | 33 | **Note:** You do not need to provide custom context in order to access the store. 34 | React Redux exports the context instance it uses by default so that you can access the store by: 35 | 36 | ```js 37 | import { ReactReduxContext } from 'react-redux' 38 | 39 | // in your connected component 40 | render() { 41 | return ( 42 | 43 | {({ store }) => { 44 | // do something with the store here 45 | }} 46 | 47 | ) 48 | } 49 | ``` 50 | 51 | ### Example Usage 52 | 53 | In the example below, the `` component is our root-level component. This means it’s at the very top of our component hierarchy. 54 | 55 | **Vanilla React Example** 56 | 57 | ```jsx 58 | import React from 'react' 59 | import ReactDOM from 'react-dom' 60 | import { Provider } from 'react-redux' 61 | 62 | import { App } from './App' 63 | import createStore from './createReduxStore' 64 | 65 | const store = createStore() 66 | 67 | ReactDOM.render( 68 | 69 | 70 | , 71 | document.getElementById('root') 72 | ) 73 | ``` 74 | 75 | **Usage with React Router** 76 | 77 | ```jsx 78 | import React from 'react' 79 | import ReactDOM from 'react-dom' 80 | import { Provider } from 'react-redux' 81 | import { Router, Route } from 'react-router-dom' 82 | 83 | import { App } from './App' 84 | import { Foo } from './Foo' 85 | import { Bar } from './Bar' 86 | import createStore from './createReduxStore' 87 | 88 | const store = createStore() 89 | 90 | ReactDOM.render( 91 | 92 | 93 | 94 | 95 | 96 | 97 | , 98 | document.getElementById('root') 99 | ) 100 | ``` 101 | -------------------------------------------------------------------------------- /docs/api/connect-advanced.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: connect-advanced 3 | title: connectAdvanced 4 | sidebar_label: connectAdvanced() 5 | hide_title: true 6 | --- 7 | 8 | # `connectAdvanced()` 9 | 10 | ```js 11 | connectAdvanced(selectorFactory, connectOptions?) 12 | ``` 13 | 14 | Connects a React component to a Redux store. It is the base for `connect()` but is less opinionated about how to combine `state`, `props`, and `dispatch` into your final props. It makes no assumptions about defaults or memoization of results, leaving those responsibilities to the caller. 15 | 16 | It does not modify the component class passed to it; instead, it _returns_ a new, connected component class for you to use. 17 | 18 | Most applications will not need to use this, as the default behavior in `connect` is intended to work for most use cases. 19 | 20 | > Note: `connectAdvanced` was added in version 5.0, and `connect` was reimplemented as a specific set of parameters to `connectAdvanced`. 21 | 22 | ## Arguments 23 | 24 | - `selectorFactory(dispatch, factoryOptions): selector(state, ownProps): props` \(_Function_): Initializes a selector function (during each instance's constructor). That selector function is called any time the connector component needs to compute new props, as a result of a store state change or receiving new props. The result of `selector` is expected to be a plain object, which is passed as the props to the wrapped component. If a consecutive call to `selector` returns the same object (`===`) as its previous call, the component will not be re-rendered. It's the responsibility of `selector` to return that previous object when appropriate. 25 | 26 | - [`connectOptions`] _(Object)_ If specified, further customizes the behavior of the connector. 27 | 28 | - [`getDisplayName`] _(Function)_: computes the connector component's displayName property relative to that of the wrapped component. Usually overridden by wrapper functions. Default value: `name => 'ConnectAdvanced('+name+')'` 29 | 30 | - [`methodName`] _(String)_: shown in error messages. Usually overridden by wrapper functions. Default value: `'connectAdvanced'` 31 | 32 | - [`renderCountProp`] _(String)_: if defined, a property named this value will be added to the props passed to the wrapped component. Its value will be the number of times the component has been rendered, which can be useful for tracking down unnecessary re-renders. Default value: `undefined` 33 | 34 | - [`shouldHandleStateChanges`] _(Boolean)_: controls whether the connector component subscribes to redux store state changes. If set to false, it will only re-render when parent component re-renders. Default value: `true` 35 | 36 | - [`forwardRef`] _(Boolean)_: If true, adding a ref to the connected wrapper component will actually return the instance of the wrapped component. 37 | 38 | - Additionally, any extra options passed via `connectOptions` will be passed through to your `selectorFactory` in the `factoryOptions` argument. 39 | 40 | 41 | 42 | ## Returns 43 | 44 | A higher-order React component class that builds props from the store state and passes them to the wrapped component. A higher-order component is a function which accepts a component argument and returns a new component. 45 | 46 | ### Static Properties 47 | 48 | - `WrappedComponent` _(Component)_: The original component class passed to `connectAdvanced(...)(Component)`. 49 | 50 | ### Static Methods 51 | 52 | All the original static methods of the component are hoisted. 53 | 54 | ## Remarks 55 | 56 | - Since `connectAdvanced` returns a higher-order component, it needs to be invoked two times. The first time with its arguments as described above, and a second time, with the component: `connectAdvanced(selectorFactory)(MyComponent)`. 57 | 58 | - `connectAdvanced` does not modify the passed React component. It returns a new, connected component, that you should use instead. 59 | 60 | 61 | 62 | ### Examples 63 | 64 | ### Inject `todos` of a specific user depending on props, and inject `props.userId` into the action 65 | 66 | ```js 67 | import * as actionCreators from './actionCreators' 68 | import { bindActionCreators } from 'redux' 69 | 70 | function selectorFactory(dispatch) { 71 | let ownProps = {} 72 | let result = {} 73 | 74 | const actions = bindActionCreators(actionCreators, dispatch) 75 | const addTodo = text => actions.addTodo(ownProps.userId, text) 76 | 77 | return (nextState, nextOwnProps) => { 78 | const todos = nextState.todos[nextOwnProps.userId] 79 | const nextResult = { ...nextOwnProps, todos, addTodo } 80 | ownProps = nextOwnProps 81 | if (!shallowEqual(result, nextResult)) result = nextResult 82 | return result 83 | } 84 | } 85 | export default connectAdvanced(selectorFactory)(TodoApp) 86 | ``` 87 | -------------------------------------------------------------------------------- /docs/introduction/quick-start.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: quick-start 3 | title: Quick Start 4 | hide_title: true 5 | sidebar_label: Quick Start 6 | --- 7 | 8 | # Quick Start 9 | 10 | [React Redux](https://github.com/reduxjs/react-redux) is the official [React](https://reactjs.org/) binding for [Redux](https://redux.js.org/). It lets your React components read data from a Redux store, and dispatch actions to the store to update data. 11 | 12 | ## Installation 13 | 14 | React Redux 6.x requires **React 16.4 or later.** 15 | 16 | To use React Redux with your React app: 17 | 18 | ```bash 19 | npm install react-redux 20 | ``` 21 | 22 | or 23 | 24 | ```bash 25 | yarn add react-redux 26 | ``` 27 | 28 | You'll also need to [install Redux](https://redux-docs.netlify.com/introduction/installation) and [set up a Redux store](https://redux-docs.netlify.com/recipes/configuring-your-store) in your app. 29 | 30 | ## `Provider` 31 | 32 | React Redux provides ``, which makes the Redux store available to the rest of your app: 33 | 34 | ```js 35 | import React from 'react' 36 | import ReactDOM from 'react-dom' 37 | 38 | import { Provider } from 'react-redux' 39 | import store from './store' 40 | 41 | import App from './App' 42 | 43 | const rootElement = document.getElementById('root') 44 | ReactDOM.render( 45 | 46 | 47 | , 48 | rootElement 49 | ) 50 | ``` 51 | 52 | ## `connect()` 53 | 54 | React Redux provides a `connect` function for you to connect your component to the store. 55 | 56 | Normally, you’ll call `connect` in this way: 57 | 58 | ```js 59 | import { connect } from 'react-redux' 60 | import { increment, decrement, reset } from './actionCreators' 61 | 62 | // const Counter = ... 63 | 64 | const mapStateToProps = (state /*, ownProps*/) => { 65 | return { 66 | counter: state.counter 67 | } 68 | } 69 | 70 | const mapDispatchToProps = { increment, decrement, reset } 71 | 72 | export default connect( 73 | mapStateToProps, 74 | mapDispatchToProps 75 | )(Counter) 76 | ``` 77 | 78 | 79 | ## Help and Discussion 80 | 81 | The **[#redux channel](https://discord.gg/0ZcbPKXt5bZ6au5t)** of the **[Reactiflux Discord community](http://www.reactiflux.com)** is our official resource for all questions related to learning and using Redux. Reactiflux is a great place to hang out, ask questions, and learn - come join us! 82 | 83 | You can also ask questions on [Stack Overflow](https://stackoverflow.com) using the **[#redux tag](https://stackoverflow.com/questions/tagged/redux)**. 84 | -------------------------------------------------------------------------------- /docs/introduction/why-use-react-redux.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: why-use-react-redux 3 | title: Why Use React Redux? 4 | hide_title: true 5 | sidebar_label: Why Use React Redux? 6 | --- 7 | 8 | # Why Use React Redux? 9 | 10 | Redux itself is a standalone library that can be used with any UI layer or framework, including React, Angular, Vue, Ember, and vanilla JS. Although Redux and React are commonly used together, they are independent of each other. 11 | 12 | If you are using Redux with any kind of UI framework, you will normally use a "UI binding" library to tie Redux together with your UI framework, rather than directly interacting with the store from your UI code. 13 | 14 | **React Redux is the official Redux UI binding library for React**. If you are using Redux and React together, you should also use React Redux to bind these two libraries. 15 | 16 | To understand why you should use React Redux, it may help to understand what a "UI binding library" does. 17 | 18 | > **Note**: If you have questions about whether you should use Redux in general, please see these articles for discussion of when and why you might want to use Redux, and how it's intended to be used: 19 | > 20 | > - [Redux docs: Motivation](https://redux.js.org/introduction/motivation) 21 | > - [Redux docs: FAQ - When should I use Redux?](https://redux.js.org/faq/general#when-should-i-use-redux) 22 | > - [You Might Not Need Redux](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367) 23 | > - [Idiomatic Redux: The Tao of Redux, Part 1 - Implementation and Intent](https://blog.isquaredsoftware.com/2017/05/idiomatic-redux-tao-of-redux-part-1/) 24 | 25 | ## Integrating Redux with a UI 26 | 27 | Using Redux with _any_ UI layer requires [the same consistent set of steps](https://blog.isquaredsoftware.com/presentations/workshops/redux-fundamentals/ui-layer.html#/4): 28 | 29 | 1. Create a Redux store 30 | 2. Subscribe to updates 31 | 3. Inside the subscription callback: 32 | 1. Get the current store state 33 | 2. Extract the data needed by this piece of UI 34 | 3. Update the UI with the data 35 | 4. If necessary, render the UI with initial state 36 | 5. Respond to UI inputs by dispatching Redux actions 37 | 38 | While it is possible to write this logic by hand, doing so would become very repetitive. In addition, optimizing UI performance would require complicated logic. 39 | 40 | The process of subscribing to the store, checking for updated data, and triggering a re-render can be made more generic and reusable. **A UI binding library like React Redux handles the store interaction logic, so you don't have to write that code yourself.** 41 | 42 | > **Note**: For a deeper look at how React Redux works internally and how it handles the store interaction for you, see **[Idiomatic Redux: The History and Implementation of React Redux](https://blog.isquaredsoftware.com/2018/11/react-redux-history-implementation/)**. 43 | 44 | ## Reasons to Use React Redux 45 | 46 | ### It is the Official Redux UI Bindings for React 47 | 48 | While Redux can be used with any UI layer, it was originally designed and intended for use with React. There are [UI binding layers for many other frameworks](https://redux.js.org/introduction/ecosystem#library-integration-and-bindings), but React Redux is maintained directly by the Redux team. 49 | 50 | As the offical Redux binding for React, React Redux is kept up-to-date with any API changes from either library, to ensure that your React components behave as expected. Its intended usage adopts the design principles of React - writing declarative components. 51 | 52 | ### It Encourages Good React Architecture 53 | 54 | React components are a lot like functions. While it's possible to write all your code in a single function, it's usually better to split that logic into smaller functions that each handle a specific task, making them easier to understand. 55 | 56 | Similarly, while you can write large React components that handle many different tasks, it's usually better to split up components based on responsibilities. In particular, it is common to have "container" components that are responsible for collecting and managing some kind of data, and "presentational" components that simply display UI based on whatever data they've received as props. 57 | 58 | **The React Redux `connect` function generates "container" wrapper components that handle the process of interacting with the store for you**. That way, your own components can focus on other tasks, whether it be collecting other data, or just displaying a piece of the UI. In addition, **`connect` abstracts away the question of _which_ store is being used, making your own components more reusable**. 59 | 60 | As a general architectural principle, **we want to keep our own components "unaware" of Redux**. They should simply receive data and functions as props, just like any other React component. This ultimately makes it easier to test and reuse your own components. 61 | 62 | ### It Implements Performance Optimizations For You 63 | 64 | React is generally fast, but by default any updates to a component will cause React to re-render all of the components inside that part of the component tree. This does require work, and if the data for a given component hasn't changed, then re-rendering is likely some wasted effort because the requested UI output would be the same. 65 | 66 | If performance is a concern, the best way to improve performance is to skip unnecessary re-renders, so that components only re-render when their data has actually changed. **React Redux implements many performance optimizations internally, so that your own component only re-renders when it actually needs to.** 67 | 68 | In addition, by connecting multiple components in your React component tree, you can ensure that each connected component only extracts the specific pieces of data from the store state that are needed by that component. This means that your own component will need to re-render less often, because most of the time those specific pieces of data haven't changed. 69 | 70 | ### Community Support 71 | 72 | As the official binding library for React and Redux, React Redux has a large community of users. This makes it easier to ask for help, learn about best practices, use libraries that build on top of React Redux, and reuse your knowledge across different applications. 73 | 74 | ## Links and References 75 | 76 | ### Understanding React Redux 77 | 78 | - [Idiomatic Redux: The History and Implementation of React Redux](https://blog.isquaredsoftware.com/2018/11/react-redux-history-implementation/) 79 | - [`connect.js` Explained](https://gist.github.com/gaearon/1d19088790e70ac32ea636c025ba424e) 80 | - [Redux Fundamentals workshop slides](https://blog.isquaredsoftware.com/2018/06/redux-fundamentals-workshop-slides/) 81 | - [UI Layer Integration](https://blog.isquaredsoftware.com/presentations/workshops/redux-fundamentals/ui-layer.html) 82 | - [Using React Redux](https://blog.isquaredsoftware.com/presentations/workshops/redux-fundamentals/react-redux.html) 83 | 84 | ### Community Resources 85 | 86 | - Discord channel: [#redux on Reactiflux](https://gist.github.com/gaearon/1d19088790e70ac32ea636c025ba424e) ([Reactiflux invite link](https://reactiflux.com)) 87 | - Stack Overflow topics: [Redux](https://stackoverflow.com/questions/tagged/redux), [React Redux](https://stackoverflow.com/questions/tagged/redux) 88 | - Reddit: [/r/reactjs](https://www.reddit.com/r/reactjs/), [/r/reduxjs](https://www.reddit.com/r/reduxjs/) 89 | - Github issues (bug reports and feature requests): https://github.com/reduxjs/react-redux/issues 90 | - Tutorials, articles, and further resources: [React/Redux Links](https://www.reddit.com/r/reduxjs/) 91 | -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: troubleshooting 3 | title: Troubleshooting 4 | sidebar_label: Troubleshooting 5 | hide_title: true 6 | --- 7 | 8 | ## Troubleshooting 9 | 10 | Make sure to check out [Troubleshooting Redux](http://redux.js.org/docs/Troubleshooting.html) first. 11 | 12 | ### I'm getting the following alert: Accessing PropTypes via the main React package is deprecated. Use the prop-types package from npm instead. 13 | 14 | This warning is shown when using react 15.5.\*. Basically, now it's just a warning, but in react16 the application might break. the PropTypes should now be imported from 'prop-types' package, and not from the react package. 15 | 16 | Update to the latest version of react-redux. 17 | 18 | ### My views aren’t updating! 19 | 20 | See the link above. 21 | In short, 22 | 23 | - Reducers should never mutate state, they must return new objects, or React Redux won’t see the updates. 24 | - Make sure you either bind action creators with the `mapDispatchToProps` argument to `connect()` or with the `bindActionCreators()` method, or that you manually call `dispatch()`. Just calling your `MyActionCreators.addTodo()` function won’t work because it just _returns_ an action, but does not _dispatch_ it. 25 | 26 | ### My views aren’t updating on route change with React Router 0.13 27 | 28 | If you’re using React Router 0.13, you might [bump into this problem](https://github.com/reduxjs/react-redux/issues/43). The solution is simple: whenever you use `` or the `Handler` provided by `Router.run`, pass the router state to it. 29 | 30 | Root view: 31 | 32 | ```jsx 33 | Router.run(routes, Router.HistoryLocation, (Handler, routerState) => { 34 | // note "routerState" here 35 | ReactDOM.render( 36 | 37 | {/* note "routerState" here */} 38 | 39 | , 40 | document.getElementById('root') 41 | ) 42 | }) 43 | ``` 44 | 45 | Nested view: 46 | 47 | ```js 48 | render() { 49 | // Keep passing it down 50 | return 51 | } 52 | ``` 53 | 54 | Conveniently, this gives your components access to the router state! 55 | You can also upgrade to React Router 1.0 which shouldn’t have this problem. (Let us know if it does!) 56 | 57 | ### My views aren’t updating when something changes outside of Redux 58 | 59 | If your views depend on global state or [React “context”](http://facebook.github.io/react/docs/context.html), you might find that views decorated with `connect()` will fail to update. 60 | 61 | > This is because `connect()` implements [shouldComponentUpdate](https://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate) by default, assuming that your component will produce the same results given the same props and state. This is a similar concept to React’s [PureRenderMixin](https://facebook.github.io/react/docs/pure-render-mixin.html). 62 | 63 | The _best_ solution to this is to make sure that your components are pure and pass any external state to them via props. This will ensure that your views do not re-render unless they actually need to re-render and will greatly speed up your application. 64 | 65 | If that’s not practical for whatever reason (for example, if you’re using a library that depends heavily on React context), you may pass the `pure: false` option to `connect()`: 66 | 67 | ```js 68 | function mapStateToProps(state) { 69 | return { todos: state.todos } 70 | } 71 | 72 | export default connect( 73 | mapStateToProps, 74 | null, 75 | null, 76 | { 77 | pure: false 78 | } 79 | )(TodoApp) 80 | ``` 81 | 82 | This will remove the assumption that `TodoApp` is pure and cause it to update whenever its parent component renders. Note that this will make your application less performant, so only do this if you have no other option. 83 | 84 | ### Could not find "store" in either the context or props 85 | 86 | If you have context issues, 87 | 88 | 1. [Make sure you don’t have a duplicate instance of React](https://medium.com/@dan_abramov/two-weird-tricks-that-fix-react-7cf9bbdef375) on the page. 89 | 2. Make sure you didn’t forget to wrap your root or some other ancestor component in [``](#provider-store). 90 | 3. Make sure you’re running the latest versions of React and React Redux. 91 | 92 | ### Invariant Violation: addComponentAsRefTo(...): Only a ReactOwner can have refs. This usually means that you’re trying to add a ref to a component that doesn’t have an owner 93 | 94 | If you’re using React for web, this usually means you have a [duplicate React](https://medium.com/@dan_abramov/two-weird-tricks-that-fix-react-7cf9bbdef375). Follow the linked instructions to fix this. 95 | -------------------------------------------------------------------------------- /docs/using-react-redux/accessing-store.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: accessing-store 3 | title: Accessing the Store 4 | hide_title: true 5 | sidebar_label: Accessing the Store 6 | --- 7 | 8 | # Accessing the Store 9 | 10 | React Redux provides APIs that allow your components to dispatch actions and subscribe to data updates from the store. 11 | 12 | As part of that, React Redux abstracts away the details of which store you are using, and the exact details of how that 13 | store interaction is handled. In typical usage, your own components should never need to care about those details, and 14 | won't ever reference the store directly. React Redux also internally handles the details of how the store and state are 15 | propagated to connected components, so that this works as expected by default. 16 | 17 | However, there may be certain use cases where you may need to customize how the store and state are propagated to 18 | connected components, or access the store directly. Here are some examples of how to do this. 19 | 20 | ## Understanding Context Usage 21 | 22 | Internally, React Redux uses [React's "context" feature](https://reactjs.org/docs/context.html) to make the 23 | Redux store accessible to deeply nested connected components. As of React Redux version 6, this is normally handled 24 | by a single default context object instance generated by `React.createContext()`, called `ReactReduxContext`. 25 | 26 | React Redux's `` component uses `` to put the Redux store and the current store 27 | state into context, and `connect` uses `` to read those values and handle updates. 28 | 29 | ## Providing Custom Context 30 | 31 | Instead of using the default context instance from React Redux, you may supply your own custom context instance. 32 | 33 | ```js 34 | 35 | 36 | 37 | ``` 38 | 39 | If you supply a custom context, React Redux will use that context instance instead of the one it creates and exports by default. 40 | 41 | After you’ve supplied the custom context to ``, you will need to supply this context instance to all of your connected components that are expected to connect to the same store: 42 | 43 | ```js 44 | // You can pass the context as an option to connect 45 | export default connect( 46 | mapState, 47 | mapDispatch, 48 | null, 49 | { context: MyContext } 50 | )(MyComponent); 51 | 52 | // or, call connect as normal to start 53 | const ConnectedComponent = connect( 54 | mapState, 55 | mapDispatch 56 | )(MyComponent); 57 | 58 | // Later, pass the custom context as a prop to the connected component 59 | 60 | ``` 61 | 62 | The following runtime error occurs when React Redux does not find a store in the context it is looking. For example: 63 | 64 | - You provided a custom context instance to ``, but did not provide the same instance (or did not provide any) to your connected components. 65 | - You provided a custom context to your connected component, but did not provide the same instance (or did not provide any) to ``. 66 | 67 | > Invariant Violation 68 | > 69 | > Could not find "store" in the context of "Connect(MyComponent)". Either wrap the root component in a ``, or pass a custom React context provider to `` and the corresponding React context consumer to Connect(Todo) in connect options. 70 | 71 | ## Multiple Stores 72 | 73 | [Redux was designed to use a single store](https://redux.js.org/api/store#a-note-for-flux-users). 74 | However, if you are in an unavoidable position of needing to use multiple stores, with v6 you may do so by providing (multiple) custom contexts. 75 | This also provides a natural isolation of the stores as they live in separate context instances. 76 | 77 | ```js 78 | // a naive example 79 | const ContextA = React.createContext(); 80 | const ContextB = React.createContext(); 81 | 82 | // assuming reducerA and reducerB are proper reducer functions 83 | const storeA = createStore(reducerA); 84 | const storeB = createStore(reducerB); 85 | 86 | // supply the context instances to Provider 87 | function App() { 88 | return ( 89 | 90 | 91 | 92 | 93 | 94 | ); 95 | } 96 | 97 | // fetch the corresponding store with connected components 98 | // you need to use the correct context 99 | connect(mapStateA, null, null, { context: ContextA })(MyComponentA) 100 | 101 | // You may also pass the alternate context instance directly to the connected component instead 102 | 103 | 104 | // it is possible to chain connect() 105 | // in this case MyComponent will receive merged props from both stores 106 | compose( 107 | connect(mapStateA, null, null, { context: ContextA }), 108 | connect(mapStateB, null, null, { context: ContextB }) 109 | )(MyComponent); 110 | ``` 111 | 112 | ## Using `ReactReduxContext` Directly 113 | 114 | In rare cases, you may need to access the Redux store directly in your own components. This can be done by rendering 115 | the appropriate context consumer yourself, and accessing the `store` field out of the context value. 116 | 117 | > **Note**: This is **_not_ considered part of the React Redux public API, and may break without notice**. We do recognize 118 | > that the community has use cases where this is necessary, and will try to make it possible for users to build additional 119 | > functionality on top of React Redux, but our specific use of context is considered an implementation detail. 120 | > If you have additional use cases that are not sufficiently covered by the current APIs, please file an issue to discuss 121 | > possible API improvements. 122 | 123 | ```js 124 | import { ReactReduxContext } from 'react-redux' 125 | 126 | // in your connected component 127 | function MyConnectedComponent() { 128 | return ( 129 | 130 | {({ store }) => { 131 | // do something useful with the store, like passing it to a child 132 | // component where it can be used in lifecycle methods 133 | }} 134 | 135 | ); 136 | } 137 | ``` 138 | 139 | ## Further Resources 140 | 141 | - CodeSandbox example: [A reading list app with theme using a separate store](https://codesandbox.io/s/92pm9n2kl4), implemented by providing (multiple) custom context(s). 142 | - Related issues: 143 | - [#1132: Update docs for using a different store key](https://github.com/reduxjs/react-redux/issues/1132) 144 | - [#1126: `` misses state changes that occur between when its constructor runs and when it mounts](https://github.com/reduxjs/react-redux/issues/1126) 145 | -------------------------------------------------------------------------------- /docs/using-react-redux/connect-dispatching-actions-with-mapDispatchToProps.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: connect-mapdispatch 3 | title: Connect: Dispatching Actions with mapDispatchToProps 4 | hide_title: true 5 | sidebar_label: Connect: Dispatching Actions with mapDispatchToProps 6 | --- 7 | 8 | # Connect: Dispatching Actions with `mapDispatchToProps` 9 | 10 | As the second argument passed in to `connect`, `mapDispatchToProps` is used for dispatching actions to the store. 11 | 12 | `dispatch` is a function of the Redux store. You call `store.dispatch` to dispatch an action. 13 | This is the only way to trigger a state change. 14 | 15 | With React Redux, your components never access the store directly - `connect` does it for you. 16 | React Redux gives you two ways to let components dispatch actions: 17 | 18 | - By default, a connected component receives `props.dispatch` and can dispatch actions itself. 19 | - `connect` can accept an argument called `mapDispatchToProps`, which lets you create functions that dispatch when called, and pass those functions as props to your component. 20 | 21 | The `mapDispatchToProps` functions are normally referred to as `mapDispatch` for short, but the actual variable name used can be whatever you want. 22 | 23 | ## Approaches for Dispatching 24 | 25 | ### Default: `dispatch` as a Prop 26 | 27 | If you don't specify the second argument to `connect()`, your component will receive `dispatch` by default. For example: 28 | 29 | ```js 30 | connect()(MyComponent) 31 | // which is equivalent with 32 | connect( 33 | null, 34 | null 35 | )(MyComponent) 36 | 37 | // or 38 | connect(mapStateToProps /** no second argument */)(MyComponent) 39 | ``` 40 | 41 | Once you have connected your component in this way, your component receives `props.dispatch`. You may use it to dispatch actions to the store. 42 | 43 | ```js 44 | function Counter({ count, dispatch }) { 45 | return ( 46 |
47 | 48 | {count} 49 | 50 | 51 |
52 | ) 53 | } 54 | ``` 55 | 56 | ### Providing A `mapDispatchToProps` Parameter 57 | 58 | Providing a `mapDispatchToProps` allows you to specify which actions your component might need to dispatch. It lets you provide action dispatching functions as props. Therefore, instead of calling `props.dispatch(() => increment())`, you may call `props.increment()` directly. There are a few reasons why you might want to do that. 59 | 60 | #### More Declarative 61 | 62 | First, encapsulating the dispatch logic into function makes the implementation more declarative. 63 | Dispatching an action and letting the Redux store handle the data flow is _how to_ implement the behavior, rather than _what_ it does. 64 | 65 | A good example would be dispatching an action when a button is clicked. Connecting the button directly probably doesn't make sense conceptually, and neither does having the button reference `dispatch`. 66 | 67 | ```js 68 | // button needs to be aware of "dispatch" 69 | 193 | {count} 194 | 195 | 196 | 197 | ) 198 | } 199 | ``` 200 | 201 | (Full code of the Counter example is [in this CodeSandbox](https://codesandbox.io/s/yv6kqo1yw9)) 202 | 203 | ### Defining the `mapDispatchToProps` Function with `bindActionCreators` 204 | 205 | Wrapping these functions by hand is tedious, so Redux provides a function to simplify that. 206 | 207 | > `bindActionCreators` turns an object whose values are [action creators](https://redux.js.org/glossary#action-creator), into an object with the same keys, but with every action creator wrapped into a [`dispatch`](https://redux.js.org/api/store#dispatch) call so they may be invoked directly. See [Redux Docs on `bindActionCreators`](http://redux.js.org/docs/api/bindActionCreators.html) 208 | 209 | `bindActionCreators` accepts two parameters: 210 | 211 | 1. A **`function`** (an action creator) or an **`object`** (each field an action creator) 212 | 2. `dispatch` 213 | 214 | The wrapper functions generated by `bindActionCreators` will automatically forward all of their arguments, so you don't need to do that by hand. 215 | 216 | ```js 217 | import { bindActionCreators } from 'redux' 218 | 219 | const increment = () => ({ type: 'INCREMENT' }) 220 | const decrement = () => ({ type: 'DECREMENT' }) 221 | const reset = () => ({ type: 'RESET' }) 222 | 223 | // binding an action creator 224 | // returns (...args) => dispatch(increment(...args)) 225 | const boundIncrement = bindActionCreators(increment, dispatch) 226 | 227 | // binding an object full of action creators 228 | const boundActionCreators = bindActionCreators( 229 | { increment, decrement, reset }, 230 | dispatch 231 | ) 232 | // returns 233 | // { 234 | // increment: (...args) => dispatch(increment(...args)), 235 | // decrement: (...args) => dispatch(decrement(...args)), 236 | // reset: (...args) => dispatch(reset(...args)), 237 | // } 238 | ``` 239 | 240 | To use `bindActionCreators` in our `mapDispatchToProps` function: 241 | 242 | ```js 243 | import { bindActionCreators } from 'redux' 244 | // ... 245 | 246 | function mapDispatchToProps(dispatch) { 247 | return bindActionCreators({ increment, decrement, reset }, dispatch) 248 | } 249 | 250 | // component receives props.increment, props.decrement, props.reset 251 | connect( 252 | null, 253 | mapDispatchToProps 254 | )(Counter) 255 | ``` 256 | 257 | ### Manually Injecting `dispatch` 258 | 259 | If the `mapDispatchToProps` argument is supplied, the component will no longer receive the default `dispatch`. You may bring it back by adding it manually to the return of your `mapDispatchToProps`, although most of the time you shouldn’t need to do this: 260 | 261 | ```js 262 | import { bindActionCreators } from 'redux' 263 | // ... 264 | 265 | function mapDispatchToProps(dispatch) { 266 | return { 267 | dispatch, 268 | ...bindActionCreators({ increment, decrement, reset }, dispatch) 269 | } 270 | } 271 | ``` 272 | 273 | ## Defining `mapDispatchToProps` As An Object 274 | 275 | You’ve seen that the setup for dispatching Redux actions in a React component follows a very similar process: define an action creator, wrap it in another function that looks like `(…args) => dispatch(actionCreator(…args))`, and pass that wrapper function as a prop to your component. 276 | 277 | Because this is so common, `connect` supports an “object shorthand” form for the `mapDispatchToProps` argument: if you pass an object full of action creators instead of a function, `connect` will automatically call `bindActionCreators` for you internally. 278 | 279 | **We recommend always using the “object shorthand” form of `mapDispatchToProps`, unless you have a specific reason to customize the dispatching behavior.** 280 | 281 | Note that: 282 | 283 | - Each field of the `mapDispatchToProps` object is assumed to be an action creator 284 | - Your component will no longer receive `dispatch` as a prop 285 | 286 | ```js 287 | // React Redux does this for you automatically: 288 | dispatch => bindActionCreators(mapDispatchToProps, dispatch) 289 | ``` 290 | 291 | Therefore, our `mapDispatchToProps` can simply be: 292 | 293 | ```js 294 | const mapDispatchToProps = { 295 | increment, 296 | decrement, 297 | reset 298 | } 299 | ``` 300 | 301 | Since the actual name of the variable is up to you, you might want to give it a name like `actionCreators`, or even define the object inline in the call to `connect`: 302 | 303 | ```js 304 | import {increment, decrement, reset} from "./counterActions"; 305 | 306 | const actionCreators = { 307 | increment, 308 | decrement, 309 | reset 310 | } 311 | 312 | export default connect(mapState, actionCreators)(Counter); 313 | 314 | // or 315 | export default connect( 316 | mapState, 317 | { increment, decrement, reset } 318 | )(Counter); 319 | ``` 320 | 321 | ## Common Problems 322 | 323 | ### Why is my component not receiving `dispatch`? 324 | 325 | Also known as 326 | 327 | ```js 328 | TypeError: this.props.dispatch is not a function 329 | ``` 330 | 331 | This is a common error that happens when you try to call `this.props.dispatch` , but `dispatch` is not injected to your component. 332 | 333 | `dispatch` is injected to your component _only_ when: 334 | 335 | **1. You do not provide `mapDispatchToProps`** 336 | 337 | The default `mapDispatchToProps` is simply `dispatch => ({ dispatch })`. If you do not provide `mapDispatchToProps`, `dispatch` will be provided as mentioned above. 338 | 339 | In another words, if you do: 340 | 341 | ```js 342 | // component receives `dispatch` 343 | connect(mapStateToProps /** no second argument*/)(Component) 344 | ``` 345 | 346 | **2. Your customized `mapDispatchToProps` function return specifically contains `dispatch`** 347 | 348 | You may bring back `dispatch` by providing your customized `mapDispatchToProps` function: 349 | 350 | ```js 351 | const mapDispatchToProps = dispatch => { 352 | return { 353 | increment: () => dispatch(increment()), 354 | decrement: () => dispatch(decrement()), 355 | reset: () => dispatch(reset()), 356 | dispatch 357 | } 358 | } 359 | ``` 360 | 361 | Or alternatively, with `bindActionCreators`: 362 | 363 | ```js 364 | import { bindActionCreators } from 'redux' 365 | 366 | function mapDispatchToProps(dispatch) { 367 | return { 368 | dispatch, 369 | ...bindActionCreators({ increment, decrement, reset }, dispatch) 370 | } 371 | } 372 | ``` 373 | 374 | See [this error in action in Redux’s GitHub issue #255](https://github.com/reduxjs/react-redux/issues/255). 375 | 376 | There are discussions regarding whether to provide `dispatch` to your components when you specify `mapDispatchToProps` ( [Dan Abramov’s response to #255](https://github.com/reduxjs/react-redux/issues/255#issuecomment-172089874) ). You may read them for further understanding of the current implementation intention. 377 | 378 | ### Can I `mapDispatchToProps` without `mapStateToProps` in Redux? 379 | 380 | Yes. You can skip the first parameter by passing `undefined` or `null`. Your component will not subscribe to the store, and will still receive the dispatch props defined by `mapDispatchToProps`. 381 | 382 | ```js 383 | connect( 384 | null, 385 | mapDispatchToProps 386 | )(MyComponent) 387 | ``` 388 | 389 | ### Can I call `store.dispatch`? 390 | 391 | It's an anti-pattern to interact with the store directly in a React component, whether it's an explicit import of the store or accessing it via context (see the [Redux FAQ entry on store setup](https://redux.js.org/faq/storesetup#can-or-should-i-create-multiple-stores-can-i-import-my-store-directly-and-use-it-in-components-myself) for more details). Let React Redux’s `connect` handle the access to the store, and use the `dispatch` it passes to the props to dispatch actions. 392 | 393 | ## Links and References 394 | 395 | **Tutorials** 396 | 397 | - [You Might Not Need the `mapDispatchToProps` Function](https://daveceddia.com/redux-mapdispatchtoprops-object-form/) 398 | 399 | **Related Docs** 400 | 401 | - [Redux Doc on `bindActionCreators`](https://redux.js.org/api/bindactioncreators) 402 | 403 | **Q&A** 404 | 405 | - [How to get simple dispatch from `this.props` using connect with Redux?](https://stackoverflow.com/questions/34458261/how-to-get-simple-dispatch-from-this-props-using-connect-w-redux) 406 | - [`this.props.dispatch` is `undefined` if using `mapDispatchToProps`](https://github.com/reduxjs/react-redux/issues/255) 407 | - [Do not call `store.dispatch`, call `this.props.dispatch` injected by `connect` instead](https://github.com/reduxjs/redux/issues/916) 408 | - [Can I `mapDispatchToProps` without `mapStateToProps` in Redux?](https://stackoverflow.com/questions/47657365/can-i-mapdispatchtoprops-without-mapstatetoprops-in-redux) 409 | - [Redux Doc FAQ: React Redux](https://redux.js.org/faq/reactredux) 410 | -------------------------------------------------------------------------------- /docs/using-react-redux/connect-extracting-data-with-mapStateToProps.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: connect-mapstate 3 | title: Connect: Extracting Data with mapStateToProps 4 | hide_title: true 5 | sidebar_label: Connect: Extracting Data with mapStateToProps 6 | --- 7 | 8 | # Connect: Extracting Data with `mapStateToProps` 9 | 10 | As the first argument passed in to `connect`, `mapStateToProps` is used for selecting the part of the data from the store that the connected component needs. It’s frequently referred to as just `mapState` for short. 11 | 12 | - It is called every time the store state changes. 13 | - It receives the entire store state, and should return an object of data this component needs. 14 | 15 | ## Defining `mapStateToProps` 16 | 17 | `mapStateToProps` should be defined as a function: 18 | 19 | ```js 20 | function mapStateToProps(state, ownProps?) 21 | ``` 22 | 23 | It should take a first argument called `state`, optionally a second argument called `ownProps`, and return a plain object containing the data that the connected component needs. 24 | 25 | This function should be passed as the first argument to `connect`, and will be called every time when the Redux store state changes. If you do not wish to subscribe to the store, pass `null` or `undefined` to `connect` in place of `mapStateToProps`. 26 | 27 | **It does not matter if a `mapStateToProps` function is written using the `function` keyword (`function mapState(state) { }` ) or as an arrow function (`const mapState = (state) => { }` )** - it will work the same either way. 28 | 29 | ### Arguments 30 | 31 | 1. **`state`** 32 | 2. **`ownProps` (optional)** 33 | 34 | #### `state` 35 | 36 | The first argument to a `mapStateToProps` function is the entire Redux store state (the same value returned by a call to `store.getState()`). Because of this, the first argument is traditionally just called `state`. (While you can give the argument any name you want, calling it `store` would be incorrect - it's the "state value", not the "store instance".) 37 | 38 | The `mapStateToProps` function should always be written with at least `state` passed in. 39 | 40 | ```js 41 | // TodoList.js 42 | 43 | function mapStateToProps(state) { 44 | const { todos } = state 45 | return { todoList: todos.allIds } 46 | } 47 | 48 | export default connect(mapStateToProps)(TodoList) 49 | ``` 50 | 51 | #### `ownProps` (optional) 52 | 53 | You may define the function with a second argument, `ownProps`, if your component needs the data from its own props to retrieve data from the store. This argument will contain all of the props given to the wrapper component that was generated by `connect`. 54 | 55 | ```js 56 | // Todo.js 57 | 58 | function mapStateToProps(state, ownProps) { 59 | const { visibilityFilter } = state 60 | const { id } = ownProps 61 | const todo = getTodoById(state, id) 62 | 63 | // component receives additionally: 64 | return { todo, visibilityFilter } 65 | } 66 | 67 | // Later, in your application, a parent component renders: 68 | ; 69 | // and your component receives props.id, props.todo, and props.visibilityFilter 70 | ``` 71 | 72 | You do not need to include values from `ownProps` in the object returned from `mapStateToProps`. `connect` will automatically merge those different prop sources into a final set of props. 73 | 74 | ### Return 75 | 76 | Your `mapStateToProps` function should return a plain object that contains the data the component needs: 77 | 78 | - Each field in the object will become a prop for your actual component 79 | - The values in the fields will be used to determine if your component needs to re-render 80 | 81 | For example: 82 | 83 | ```js 84 | function mapStateToProps(state) { 85 | return { 86 | a: 42, 87 | todos: state.todos, 88 | filter: state.visibilityFilter 89 | } 90 | } 91 | 92 | // component will receive: props.a, props.todos, and props.filter 93 | ``` 94 | 95 | > Note: In advanced scenarios where you need more control over the rendering performance, `mapStateToProps` can also return a function. In this case, that function will be used as the final `mapStateToProps` for a particular component instance. This allows you to do per-instance memoization. See the [Advanced Usage]() section of the docs for more details, as well as [PR #279](https://github.com/reduxjs/react-redux/pull/279) and the tests it adds. Most apps never need this. 96 | 97 | ## Usage Guidelines 98 | 99 | ### Let `mapStateToProps` Reshape the Data from the Store 100 | 101 | `mapStateToProps` functions can, and should, do a lot more than just `return state.someSlice`. **They have the responsibility of "re-shaping" store data as needed for that component.** This may include returning a value as a specific prop name, combining pieces of data from different parts of the state tree, and transforming the store data in different ways. 102 | 103 | ### Use Selector Functions to Extract and Transform Data 104 | 105 | We highly encourage the use of "selector" functions to help encapsulate the process of extracting values from specific locations in the state tree. Memoized selector functions also play a key role in improving application performance (see the following sections in this page and the [Advanced Usage: Performance]() page for more details on why and how to use selectors.) 106 | 107 | ### `mapStateToProps` Functions Should Be Fast 108 | 109 | Whenever the store changes, all of the `mapStateToProps` functions of all of the connected components will run. Because of this, your `mapStateToProps` functions should run as fast as possible. This also means that a slow `mapStateToProps` function can be a potential bottleneck for your application. 110 | 111 | As part of the "re-shaping data" idea, `mapStateToProps` functions frequently need to transform data in various ways (such as filtering an array, mapping an array of IDs to their corresponding objects, or extracting plain JS values from Immutable.js objects). These transformations can often be expensive, both in terms of cost to execute the transformation, and whether the component re-renders as a result. If performance is a concern, ensure that these transformations are only run if the input values have changed. 112 | 113 | ### `mapStateToProps` Functions Should Be Pure and Synchronous 114 | 115 | Much like a Redux reducer, a `mapStateToProps` function should always be 100% pure and synchronous. It should simply take `state` (and `ownProps`) as arguments, and return the data the component needs as props. It should _not_ be used to trigger asynchronous behavior like AJAX calls for data fetching, and the functions should not be declared as `async`. 116 | 117 | ## `mapStateToProps` and Performance 118 | 119 | ### Return Values Determine If Your Component Re-Renders 120 | 121 | React Redux internally implements the `shouldComponentUpdate` method such that the wrapper component re-renders precisely when the data your component needs has changed. By default, React Redux decides whether the contents of the object returned from `mapStateToProps` are different using `===` comparison (a "shallow equality" check) on each fields of the returned object. If any of the fields have changed, then your component will be re-rendered so it can receive the updated values as props. Note that returning a mutated object of the same reference is a common mistake that can result in your component not re-rendering when expected. 122 | 123 | To summarize the behavior of the component wrapped by `connect` with `mapStateToProps` to extract data from the store: 124 | 125 | | | `(state) => stateProps` | `(state, ownProps) => stateProps` | 126 | | ---------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------- | 127 | | `mapStateToProps` runs when: | store `state` changes | store `state` changes
or
any field of `ownProps` is different | 128 | | component re-renders when: | any field of `stateProps` is different | any field of `stateProps` is different
or
any field of `ownProps` is different | 129 | 130 | ### Only Return New Object References If Needed 131 | 132 | React Redux does shallow comparisons to see if the `mapStateToProps` results have changed. It’s easy to accidentally return new object or array references every time, which would cause your component to re-render even if the data is actually the same. 133 | 134 | Many common operations result in new object or array references being created: 135 | 136 | - Creating new arrays with `someArray.map()` or `someArray.filter()` 137 | - Merging arrays with `array.concat` 138 | - Selecting portion of an array with `array.slice` 139 | - Copying values with `Object.assign` 140 | - Copying values with the spread operator `{ ...oldState, ...newData }` 141 | 142 | Put these operations in [memoized selector functions]() to ensure that they only run if the input values have changed. This will also ensure that if the input values _haven't_ changed, `mapStateToProps` will still return the same result values as before, and `connect` can skip re-rendering. 143 | 144 | ### Only Perform Expensive Operations When Data Changes 145 | 146 | Transforming data can often be expensive (_and_ usually results in new object references being created). In order for your `mapStateToProps` function to be as fast as possible, you should only re-run these complex transformations when the relevant data has changed. 147 | 148 | There are a few ways to approach this: 149 | 150 | - Some transformations could be calculated in an action creator or reducer, and the transformed data could be kept in the store 151 | - Transformations can also be done in a component's `render()` method 152 | - If the transformation does need to be done in a `mapStateToProps` function, then we recommend using [memoized selector functions]() to ensure the transformation is only run when the input values have changed. 153 | 154 | #### Immutable.js Performance Concerns 155 | 156 | Immutable.js author Lee Byron on Twitter [explicitly advises avoiding `toJS` when performance is a concern](https://twitter.com/leeb/status/746733697093668864?lang=en): 157 | 158 | > Perf tip for #immutablejs: avoid .toJS() .toObject() and .toArray() all slow full-copy operations which render structural sharing useless. 159 | 160 | There's several other performance concerns to take into consideration with Immutable.js - see the list of links at the end of this page for more information. 161 | 162 | ## Behavior and Gotchas 163 | 164 | ### `mapStateToProps` Will Not Run if the Store State is the Same 165 | 166 | The wrapper component generated by `connect` subscribes to the Redux store. Every time an action is dispatched, it calls `store.getState()` and checks to see if `lastState === currentState`. If the two state values are identical by reference, then it will _not_ re-run your `mapStateToProps` function, because it assumes that the rest of the store state hasn't changed either. 167 | 168 | The Redux `combineReducers` utility function tries to optimize for this. If none of the slice reducers returned a new value, then `combineReducers` returns the old state object instead of a new one. This means that mutation in a reducer can lead to the root state object not being updated, and thus the UI won't re-render. 169 | 170 | ### The Number of Declared Arguments Affects Behavior 171 | 172 | With just `(state)`, the function runs whenever the root store state object is different. With `(state, ownProps)`, it runs any time the store state is different and ALSO whenever the wrapper props have changed. 173 | 174 | This means that **you should not add the `ownProps` argument unless you actually need to use it**, or your `mapStateToProps` function will run more often than it needs to. 175 | 176 | There are some edge cases around this behavior. **The number of mandatory arguments determines whether `mapStateToProps` will receive `ownProps`**. 177 | 178 | If the formal definition of the function contains one mandatory parameter, `mapStateToProps` will _not_ receive `ownProps`: 179 | 180 | ```js 181 | function mapStateToProps(state) { 182 | console.log(state) // state 183 | console.log(arguments[1]) // undefined 184 | } 185 | const mapStateToProps = (state, ownProps = {}) => { 186 | console.log(state) // state 187 | console.log(ownProps) // undefined 188 | } 189 | ``` 190 | 191 | It _will_ receive `ownProps` when the formal definition of the function contains zero or two mandatory parameters: 192 | 193 | ```js 194 | function mapStateToProps(state, ownProps) { 195 | console.log(state) // state 196 | console.log(ownProps) // ownProps 197 | } 198 | 199 | function mapStateToProps() { 200 | console.log(arguments[0]) // state 201 | console.log(arguments[1]) // ownProps 202 | } 203 | 204 | function mapStateToProps(...args) { 205 | console.log(args[0]) // state 206 | console.log(args[1]) // ownProps 207 | } 208 | ``` 209 | 210 | ## Links and References 211 | 212 | **Tutorials** 213 | 214 | - [Practical Redux Series, Part 6: Connected Lists, Forms, and Performance](https://blog.isquaredsoftware.com/2017/01/practical-redux-part-6-connected-lists-forms-and-performance/) 215 | - [Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performance](https://blog.isquaredsoftware.com/2017/12/idiomatic-redux-using-reselect-selectors/) 216 | 217 | **Performance** 218 | 219 | - [Lee Byron's Tweet Suggesting to avoid `toJS`, `toArray` and `toObject` for Performance](https://twitter.com/leeb/status/746733697093668864) 220 | - [Improving React and Redux performance with Reselect](https://blog.rangle.io/react-and-redux-performance-with-reselect/) 221 | - [Immutable data performance links](https://github.com/markerikson/react-redux-links/blob/master/react-performance.md#immutable-data) 222 | 223 | **Q&A** 224 | 225 | - [Why Is My Component Re-Rendering Too Often?](https://redux.js.org/faq/reactredux#why-is-my-component-re-rendering-too-often) 226 | - [Why isn't my component re-rendering, or my mapStateToProps running](https://redux.js.org/faq/reactredux#why-isnt-my-component-re-rendering-or-my-mapstatetoprops-running) 227 | - [How can I speed up my mapStateToProps?](https://redux.js.org/faq/reactredux#why-is-my-component-re-rendering-too-often) 228 | - [Should I only connect my top component, or can I connect multiple components in my tree?](https://redux.js.org/faq/reactredux#why-is-my-component-re-rendering-too-often) 229 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-fork", 3 | "version": "6.5.2", 4 | "description": "Official React bindings for Redux - Forked from 6.0", 5 | "keywords": [ 6 | "react", 7 | "reactjs", 8 | "redux" 9 | ], 10 | "license": "MIT", 11 | "author": "Dan Abramov (https://github.com/gaearon)", 12 | "homepage": "https://github.com/salvoravida/react-redux-fork", 13 | "repository": "github:salvoravida/react-redux-fork", 14 | "bugs": "https://github.com/salvoravida/react-redux-fork/issues", 15 | "main": "./lib/index.js", 16 | "unpkg": "dist/react-redux.js", 17 | "module": "es/index.js", 18 | "files": [ 19 | "dist", 20 | "lib", 21 | "src", 22 | "es" 23 | ], 24 | "scripts": { 25 | "build:commonjs": "cross-env BABEL_ENV=commonjs babel src --out-dir lib", 26 | "build:es": "babel src --out-dir es", 27 | "build:umd": "cross-env NODE_ENV=development rollup -c -o dist/react-redux.js", 28 | "build:umd:min": "cross-env NODE_ENV=production rollup -c -o dist/react-redux.min.js", 29 | "build": "npm run build:commonjs && npm run build:es && npm run build:umd && npm run build:umd:min", 30 | "clean": "rimraf lib dist es coverage", 31 | "format": "prettier --write \"{src,test}/**/*.{js,ts}\" index.d.ts \"docs/**/*.md\"", 32 | "lint": "eslint src test/utils test/components", 33 | "prepare": "npm run clean && npm run build", 34 | "pretest": "npm run lint", 35 | "test": "node ./test/run-tests.js", 36 | "coverage": "codecov" 37 | }, 38 | "peerDependencies": { 39 | "react": "^16.6.0-0", 40 | "react-dom": "^16.6.0-0", 41 | "redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0" 42 | }, 43 | "dependencies": { 44 | "@babel/runtime": "^7.3.1", 45 | "hoist-non-react-statics": "^3.3.0", 46 | "invariant": "^2.2.4", 47 | "loose-envify": "^1.4.0", 48 | "prop-types": "^15.7.2", 49 | "react-is": "^16.8.2" 50 | }, 51 | "devDependencies": { 52 | "@babel/cli": "^7.2.3", 53 | "@babel/core": "^7.3.3", 54 | "@babel/plugin-proposal-decorators": "^7.3.0", 55 | "@babel/plugin-proposal-object-rest-spread": "^7.3.2", 56 | "@babel/plugin-transform-react-display-name": "^7.2.0", 57 | "@babel/plugin-transform-react-jsx": "^7.3.0", 58 | "@babel/plugin-transform-runtime": "^7.2.0", 59 | "@babel/preset-env": "^7.3.1", 60 | "babel-core": "^7.0.0-bridge.0", 61 | "babel-eslint": "^10.0.1", 62 | "babel-jest": "^24.1.0", 63 | "codecov": "^3.2.0", 64 | "create-react-class": "^15.6.3", 65 | "cross-env": "^5.2.0", 66 | "cross-spawn": "^6.0.5", 67 | "es3ify": "^0.2.0", 68 | "eslint": "^5.14.1", 69 | "eslint-config-prettier": "^4.0.0", 70 | "eslint-plugin-import": "^2.16.0", 71 | "eslint-plugin-prettier": "^3.0.1", 72 | "eslint-plugin-react": "^7.12.4", 73 | "glob": "^7.1.3", 74 | "jest": "^24.1.0", 75 | "jest-dom": "^3.1.2", 76 | "npm-run": "^5.0.1", 77 | "prettier": "^1.16.4", 78 | "react": "^16.8.2", 79 | "react-dom": "^16.8.2", 80 | "react-testing-library": "^5.9.0", 81 | "redux": "^4.0.1", 82 | "rimraf": "^2.6.3", 83 | "rollup": "^1.2.2", 84 | "rollup-plugin-babel": "^4.3.2", 85 | "rollup-plugin-commonjs": "^9.2.0", 86 | "rollup-plugin-node-resolve": "^4.0.0", 87 | "rollup-plugin-replace": "^2.1.0", 88 | "rollup-plugin-terser": "^4.0.4", 89 | "semver": "^5.6.0" 90 | }, 91 | "browserify": { 92 | "transform": [ 93 | "loose-envify" 94 | ] 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import nodeResolve from 'rollup-plugin-node-resolve' 2 | import babel from 'rollup-plugin-babel' 3 | import replace from 'rollup-plugin-replace' 4 | import commonjs from 'rollup-plugin-commonjs' 5 | import { terser } from 'rollup-plugin-terser' 6 | import pkg from './package.json' 7 | 8 | const env = process.env.NODE_ENV 9 | 10 | const config = { 11 | input: 'src/index.js', 12 | external: ['react', 'redux', 'react-dom'], //Object.keys(pkg.peerDependencies || {}), 13 | output: { 14 | format: 'umd', 15 | name: 'ReactRedux', 16 | globals: { 17 | react: 'React', 18 | redux: 'Redux', 19 | 'react-dom': 'ReactDOM' 20 | } 21 | }, 22 | plugins: [ 23 | nodeResolve(), 24 | babel({ 25 | exclude: '**/node_modules/**', 26 | runtimeHelpers: true 27 | }), 28 | replace({ 29 | 'process.env.NODE_ENV': JSON.stringify(env) 30 | }), 31 | commonjs({ 32 | namedExports: { 33 | 'node_modules/react-is/index.js': [ 34 | 'isValidElementType', 35 | 'isContextConsumer' 36 | ], 37 | 'node_modules/react-dom/index.js': ['unstable_batchedUpdates'] 38 | } 39 | }) 40 | ] 41 | } 42 | 43 | if (env === 'production') { 44 | config.plugins.push( 45 | terser({ 46 | compress: { 47 | pure_getters: true, 48 | unsafe: true, 49 | unsafe_comps: true, 50 | warnings: false 51 | } 52 | }) 53 | ) 54 | } 55 | 56 | export default config 57 | -------------------------------------------------------------------------------- /src/components/Context.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const ReactReduxContext = React.createContext(null) 4 | 5 | export default ReactReduxContext 6 | -------------------------------------------------------------------------------- /src/components/Provider.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { ReactReduxContext } from './Context' 4 | import Subscription from '../utils/Subscription' 5 | 6 | class Provider extends Component { 7 | constructor(props) { 8 | super(props) 9 | 10 | const { store } = props 11 | 12 | this.notifySubscribers = this.notifySubscribers.bind(this) 13 | const subscription = new Subscription(store) 14 | subscription.onStateChange = this.notifySubscribers 15 | 16 | this.state = { 17 | store, 18 | subscription 19 | } 20 | 21 | this.previousState = store.getState() 22 | } 23 | 24 | componentDidMount() { 25 | this._isMounted = true 26 | 27 | this.state.subscription.trySubscribe() 28 | 29 | if (this.previousState !== this.props.store.getState()) { 30 | this.state.subscription.notifyNestedSubs() 31 | } 32 | } 33 | 34 | componentWillUnmount() { 35 | if (this.unsubscribe) this.unsubscribe() 36 | 37 | this.state.subscription.tryUnsubscribe() 38 | 39 | this._isMounted = false 40 | } 41 | 42 | componentDidUpdate(prevProps) { 43 | if (this.props.store !== prevProps.store) { 44 | this.state.subscription.tryUnsubscribe() 45 | const subscription = new Subscription(this.props.store) 46 | subscription.onStateChange = this.notifySubscribers 47 | this.setState({ store: this.props.store, subscription }) 48 | } 49 | } 50 | 51 | notifySubscribers() { 52 | this.state.subscription.notifyNestedSubs() 53 | } 54 | 55 | render() { 56 | const Context = this.props.context || ReactReduxContext 57 | 58 | return ( 59 | 60 | {this.props.children} 61 | 62 | ) 63 | } 64 | } 65 | 66 | Provider.propTypes = { 67 | store: PropTypes.shape({ 68 | subscribe: PropTypes.func.isRequired, 69 | dispatch: PropTypes.func.isRequired, 70 | getState: PropTypes.func.isRequired 71 | }), 72 | context: PropTypes.object, 73 | children: PropTypes.any 74 | } 75 | 76 | export default Provider 77 | -------------------------------------------------------------------------------- /src/components/connectAdvanced.js: -------------------------------------------------------------------------------- 1 | import hoistStatics from 'hoist-non-react-statics' 2 | import invariant from 'invariant' 3 | import React, { Component } from 'react' 4 | import { isValidElementType, isContextConsumer } from 'react-is' 5 | 6 | import { ReactReduxContext } from './Context' 7 | import Subscription from '../utils/Subscription' 8 | import PropTypes from 'prop-types' 9 | import { unstable_readContext } from '../utils/readContext' 10 | 11 | const stringifyComponent = Comp => { 12 | try { 13 | return JSON.stringify(Comp) 14 | } catch (err) { 15 | return String(Comp) 16 | } 17 | } 18 | 19 | let hotReloadingVersion = 0 20 | const dummyState = {} 21 | function noop() {} 22 | 23 | function makeSelectorStateful(sourceSelector, store) { 24 | // wrap the selector in an object that tracks its results between runs. 25 | const selector = { 26 | run: function runComponentSelector(props) { 27 | try { 28 | selector.lastProcessedProps = props 29 | const nextProps = sourceSelector(store.getState(), props) 30 | if (nextProps !== selector.props || selector.error) { 31 | selector.shouldComponentUpdate = true 32 | selector.props = nextProps 33 | selector.error = null 34 | } 35 | } catch (error) { 36 | selector.shouldComponentUpdate = true 37 | selector.error = error 38 | } 39 | } 40 | } 41 | 42 | return selector 43 | } 44 | 45 | export default function connectAdvanced( 46 | /* 47 | selectorFactory is a func that is responsible for returning the selector function used to 48 | compute new props from state, props, and dispatch. For example: 49 | 50 | export default connectAdvanced((dispatch, options) => (state, props) => ({ 51 | thing: state.things[props.thingId], 52 | saveThing: fields => dispatch(actionCreators.saveThing(props.thingId, fields)), 53 | }))(YourComponent) 54 | 55 | Access to dispatch is provided to the factory so selectorFactories can bind actionCreators 56 | outside of their selector as an optimization. Options passed to connectAdvanced are passed to 57 | the selectorFactory, along with displayName and WrappedComponent, as the second argument. 58 | 59 | Note that selectorFactory is responsible for all caching/memoization of inbound and outbound 60 | props. Do not use connectAdvanced directly without memoizing results between calls to your 61 | selector, otherwise the Connect component will re-render on every state or props change. 62 | */ 63 | selectorFactory, 64 | // options object: 65 | { 66 | // the func used to compute this HOC's displayName from the wrapped component's displayName. 67 | // probably overridden by wrapper functions such as connect() 68 | getDisplayName = name => `ConnectAdvanced(${name})`, 69 | 70 | // shown in error messages 71 | // probably overridden by wrapper functions such as connect() 72 | methodName = 'connectAdvanced', 73 | 74 | // REMOVED: if defined, the name of the property passed to the wrapped element indicating the number of 75 | // calls to render. useful for watching in react devtools for unnecessary re-renders. 76 | renderCountProp = undefined, 77 | 78 | // determines whether this HOC subscribes to store changes 79 | shouldHandleStateChanges = true, 80 | 81 | // REMOVED: the key of props/context to get the store 82 | storeKey = 'store', 83 | 84 | // REMOVED: expose the wrapped component via refs 85 | withRef = false, 86 | 87 | // use React's forwardRef to expose a ref of the wrapped component 88 | forwardRef = false, 89 | 90 | // the context consumer to use 91 | context = ReactReduxContext, 92 | 93 | // additional options are passed through to the selectorFactory 94 | ...connectOptions 95 | } = {} 96 | ) { 97 | invariant( 98 | renderCountProp === undefined, 99 | `renderCountProp is removed. render counting is built into the latest React dev tools profiling extension` 100 | ) 101 | 102 | invariant( 103 | !withRef, 104 | 'withRef is removed. To access the wrapped instance, use a ref on the connected component' 105 | ) 106 | 107 | const customStoreWarningMessage = 108 | 'To use a custom Redux store for specific components, create a custom React context with ' + 109 | "React.createContext(), and pass the context object to React Redux's Provider and specific components" + 110 | ' like: where ConnectedComponent has been created' + 111 | ' with {context : MyContext} option in connect' 112 | 113 | invariant( 114 | storeKey === 'store', 115 | 'storeKey has been removed and does not do anything. ' + 116 | customStoreWarningMessage 117 | ) 118 | 119 | const version = hotReloadingVersion++ 120 | 121 | const Context = context 122 | 123 | return function wrapWithConnect(WrappedComponent) { 124 | if (process.env.NODE_ENV !== 'production') { 125 | invariant( 126 | isValidElementType(WrappedComponent), 127 | `You must pass a component to the function returned by ` + 128 | `${methodName}. Instead received ${stringifyComponent( 129 | WrappedComponent 130 | )}` 131 | ) 132 | } 133 | 134 | const wrappedComponentName = 135 | WrappedComponent.displayName || WrappedComponent.name || 'Component' 136 | 137 | const displayName = getDisplayName(wrappedComponentName) 138 | 139 | const selectorFactoryOptions = { 140 | ...connectOptions, 141 | getDisplayName, 142 | methodName, 143 | renderCountProp, 144 | shouldHandleStateChanges, 145 | storeKey, 146 | displayName, 147 | wrappedComponentName, 148 | WrappedComponent 149 | } 150 | 151 | const { pure } = connectOptions 152 | 153 | let ContextToUse = Context 154 | 155 | class Connect extends Component { 156 | constructor(props, context) { 157 | super(props) 158 | this.version = version 159 | 160 | this.firstContextRead = true 161 | this.prevContextValue = null 162 | 163 | this.readContext(props, context) 164 | this.initStore(props) 165 | this.initSelector() 166 | this.initSubscription() 167 | } 168 | 169 | readContext(props, context) { 170 | if ( 171 | props.context && 172 | props.context.Consumer && 173 | isContextConsumer() 174 | ) { 175 | ContextToUse = props.context 176 | this.contextValueToUse = unstable_readContext(ContextToUse) 177 | } else { 178 | //static classContext 179 | this.contextValueToUse = context 180 | } 181 | 182 | if (this.firstContextRead) { 183 | this.firstContextRead = false 184 | this.prevContextValue = this.contextValueToUse 185 | return 186 | } 187 | 188 | if (this.prevContextValue !== this.contextValueToUse) { 189 | this.prevContextValue = this.contextValueToUse 190 | //store or subscription changed from provider 191 | this.scheduleRefreshContextFromProvider = true 192 | } 193 | } 194 | 195 | initStore(props) { 196 | this.store = 197 | props.store || 198 | (this.contextValueToUse && this.contextValueToUse.store) //No props.store and No context 199 | 200 | this.propsMode = Boolean(props.store) 201 | invariant( 202 | this.store, 203 | `Could not find "${storeKey}" in either the context or props of ` + 204 | `"${displayName}". Either wrap the root component in a , ` + 205 | `or explicitly pass "${storeKey}" as a prop to "${displayName}".` 206 | ) 207 | 208 | this.contextSubscription = this.contextValueToUse 209 | ? this.contextValueToUse.subscription 210 | : undefined 211 | } 212 | 213 | componentDidMount() { 214 | this.selector.shouldComponentUpdate = false 215 | if (!shouldHandleStateChanges) return 216 | 217 | // componentWillMount fires during server side rendering, but componentDidMount and 218 | // componentWillUnmount do not. Because of this, trySubscribe happens during ...didMount. 219 | // Otherwise, unsubscription would never take place during SSR, causing a memory leak. 220 | // To handle the case where a child component may have triggered a state change by 221 | // dispatching an action in its componentWillMount, we have to re-run the select and maybe 222 | // re-render. 223 | 224 | if (this.scheduleRefreshContextFromProvider) { 225 | this.refreshContextFromProvider() 226 | } else { 227 | this.subscription.trySubscribe() 228 | this.selector.run(this.props) 229 | if (this.selector.shouldComponentUpdate) this.forceUpdate() 230 | } 231 | } 232 | 233 | componentWillUnmount() { 234 | this.cleanStoreSubscription() 235 | } 236 | 237 | cleanStoreSubscription() { 238 | if (this.subscription) this.subscription.tryUnsubscribe() 239 | this.subscription = null 240 | this.notifyNestedSubs = noop 241 | this.store = null 242 | this.selector.run = noop 243 | this.selector.shouldComponentUpdate = false 244 | } 245 | 246 | shouldComponentUpdate(nextProps) { 247 | if (!pure || nextProps !== this.props) this.selector.run(nextProps) 248 | return !pure || this.selector.shouldComponentUpdate 249 | } 250 | 251 | initSelector() { 252 | const sourceSelector = selectorFactory( 253 | this.store.dispatch, 254 | selectorFactoryOptions 255 | ) 256 | this.selector = makeSelectorStateful(sourceSelector, this.store) 257 | this.selector.run(this.props) 258 | } 259 | 260 | initSubscription() { 261 | if (!shouldHandleStateChanges) return 262 | 263 | // parentSub's source should match where store came from: props vs. context. A component 264 | // connected to the store via props shouldn't use subscription from context, or vice versa. 265 | 266 | //const parentSub = (this.propsMode ? this.props : this.context)[ 267 | // subscriptionKey 268 | //] 269 | const parentSub = this.propsMode ? undefined : this.contextSubscription 270 | 271 | this.subscription = new Subscription( 272 | this.store, 273 | parentSub, 274 | this.onStateChange.bind(this) 275 | ) 276 | 277 | //if store is from Props, we need to propagate new subscription via context (rare case-use) 278 | this.newContextValue = this.propsMode 279 | ? { 280 | store: this.store, 281 | subscription: this.subscription 282 | } 283 | : undefined 284 | 285 | // `notifyNestedSubs` is duplicated to handle the case where the component is unmounted in 286 | // the middle of the notification loop, where `this.subscription` will then be null. An 287 | // extra null check every change can be avoided by copying the method onto `this` and then 288 | // replacing it with a no-op on unmount. This can probably be avoided if Subscription's 289 | // listeners logic is changed to not call listeners that have been unsubscribed in the 290 | // middle of the notification loop. 291 | this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind( 292 | this.subscription 293 | ) 294 | } 295 | 296 | onStateChange() { 297 | this.selector.run(this.props) 298 | 299 | if (!this.selector.shouldComponentUpdate) { 300 | this.notifyNestedSubs() 301 | } else { 302 | this.notifyNestedSubsOnComponentDidUpdate = true 303 | this.setState(dummyState) 304 | } 305 | } 306 | 307 | refreshContextFromProvider() { 308 | this.scheduleRefreshContextFromProvider = false 309 | this.cleanStoreSubscription() 310 | this.initStore(this.props) 311 | this.initSelector() 312 | this.initSubscription() 313 | this.subscription.trySubscribe() 314 | this.selector.run(this.props) 315 | if (this.selector.shouldComponentUpdate) this.forceUpdate() 316 | } 317 | 318 | componentDidUpdate() { 319 | if (this.notifyNestedSubsOnComponentDidUpdate) { 320 | this.notifyNestedSubsOnComponentDidUpdate = false 321 | this.notifyNestedSubs() 322 | } 323 | 324 | if (this.scheduleRefreshContextFromProvider) { 325 | this.refreshContextFromProvider() 326 | } 327 | } 328 | 329 | render() { 330 | if (process.env.NODE_ENV !== 'production') { 331 | this.devCheckHotReload() 332 | } 333 | this.readContext(this.props, this.context) 334 | 335 | const selector = this.selector 336 | //forceUpdate from external 337 | if (selector.lastProcessedProps !== this.props) selector.run(this.props) 338 | selector.shouldComponentUpdate = false 339 | 340 | if (selector.error) { 341 | throw selector.error 342 | } else { 343 | if (!selector.props) { 344 | throw 'Invalid mapStateToProps or mapDispatchToProps on component ' + 345 | displayName 346 | } else { 347 | //clean wrapperProps and set ref if forwardRef 348 | const { context, store, forwardedRef, ...wrapperProps } = selector.props //eslint-disable-line 349 | if (forwardRef) wrapperProps.ref = forwardedRef 350 | 351 | return this.newContextValue ? ( 352 | 353 | 354 | 355 | ) : ( 356 | 357 | ) 358 | } 359 | } 360 | } 361 | } 362 | 363 | if (process.env.NODE_ENV !== 'production') { 364 | Connect.prototype.devCheckHotReload = function devCheckHotReload() { 365 | // We are hot reloading! 366 | if (this.version !== version) { 367 | this.version = version 368 | this.initSelector() 369 | 370 | // If any connected descendants don't hot reload (and resubscribe in the process), their 371 | // listeners will be lost when we unsubscribe. Unfortunately, by copying over all 372 | // listeners, this does mean that the old versions of connected descendants will still be 373 | // notified of state changes; however, their onStateChange function is a no-op so this 374 | // isn't a huge deal. 375 | let oldListeners = [] 376 | 377 | if (this.subscription) { 378 | oldListeners = this.subscription.listeners.get() 379 | this.subscription.tryUnsubscribe() 380 | } 381 | this.initSubscription() 382 | if (shouldHandleStateChanges) { 383 | this.subscription.trySubscribe() 384 | oldListeners.forEach(listener => 385 | this.subscription.listeners.subscribe(listener) 386 | ) 387 | } 388 | } 389 | } 390 | } 391 | 392 | Connect.WrappedComponent = WrappedComponent 393 | Connect.contextType = Context 394 | Connect.displayName = displayName 395 | 396 | Connect.propTypes = { 397 | store: PropTypes.shape({ 398 | subscribe: PropTypes.func.isRequired, 399 | dispatch: PropTypes.func.isRequired, 400 | getState: PropTypes.func.isRequired 401 | }), 402 | context: PropTypes.object, 403 | children: PropTypes.any 404 | } 405 | 406 | if (forwardRef) { 407 | const forwarded = React.forwardRef((props, ref) => ( 408 | 409 | )) 410 | 411 | forwarded.displayName = displayName 412 | forwarded.WrappedComponent = WrappedComponent 413 | return hoistStatics(forwarded, WrappedComponent) 414 | } 415 | 416 | return hoistStatics(Connect, WrappedComponent) 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /src/connect/connect.js: -------------------------------------------------------------------------------- 1 | import connectAdvanced from '../components/connectAdvanced' 2 | import shallowEqual from '../utils/shallowEqual' 3 | import defaultMapDispatchToPropsFactories from './mapDispatchToProps' 4 | import defaultMapStateToPropsFactories from './mapStateToProps' 5 | import defaultMergePropsFactories from './mergeProps' 6 | import defaultSelectorFactory from './selectorFactory' 7 | 8 | /* 9 | connect is a facade over connectAdvanced. It turns its args into a compatible 10 | selectorFactory, which has the signature: 11 | 12 | (dispatch, options) => (nextState, nextOwnProps) => nextFinalProps 13 | 14 | connect passes its args to connectAdvanced as options, which will in turn pass them to 15 | selectorFactory each time a Connect component instance is instantiated or hot reloaded. 16 | 17 | selectorFactory returns a final props selector from its mapStateToProps, 18 | mapStateToPropsFactories, mapDispatchToProps, mapDispatchToPropsFactories, mergeProps, 19 | mergePropsFactories, and pure args. 20 | 21 | The resulting final props selector is called by the Connect component instance whenever 22 | it receives new props or store state. 23 | */ 24 | 25 | function match(arg, factories, name) { 26 | for (let i = factories.length - 1; i >= 0; i--) { 27 | const result = factories[i](arg) 28 | if (result) return result 29 | } 30 | 31 | return (dispatch, options) => { 32 | throw new Error( 33 | `Invalid value of type ${typeof arg} for ${name} argument when connecting component ${ 34 | options.wrappedComponentName 35 | }.` 36 | ) 37 | } 38 | } 39 | 40 | function strictEqual(a, b) { 41 | return a === b 42 | } 43 | 44 | // createConnect with default args builds the 'official' connect behavior. Calling it with 45 | // different options opens up some testing and extensibility scenarios 46 | export function createConnect({ 47 | connectHOC = connectAdvanced, 48 | mapStateToPropsFactories = defaultMapStateToPropsFactories, 49 | mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, 50 | mergePropsFactories = defaultMergePropsFactories, 51 | selectorFactory = defaultSelectorFactory 52 | } = {}) { 53 | return function connect( 54 | mapStateToProps, 55 | mapDispatchToProps, 56 | mergeProps, 57 | { 58 | pure = true, 59 | areStatesEqual = strictEqual, 60 | areOwnPropsEqual = shallowEqual, 61 | areStatePropsEqual = shallowEqual, 62 | areMergedPropsEqual = shallowEqual, 63 | ...extraOptions 64 | } = {} 65 | ) { 66 | const initMapStateToProps = match( 67 | mapStateToProps, 68 | mapStateToPropsFactories, 69 | 'mapStateToProps' 70 | ) 71 | const initMapDispatchToProps = match( 72 | mapDispatchToProps, 73 | mapDispatchToPropsFactories, 74 | 'mapDispatchToProps' 75 | ) 76 | const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps') 77 | 78 | return connectHOC(selectorFactory, { 79 | // used in error messages 80 | methodName: 'connect', 81 | 82 | // used to compute Connect's displayName from the wrapped component's displayName. 83 | getDisplayName: name => `Connect(${name})`, 84 | 85 | // if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes 86 | shouldHandleStateChanges: Boolean(mapStateToProps), 87 | 88 | // passed through to selectorFactory 89 | initMapStateToProps, 90 | initMapDispatchToProps, 91 | initMergeProps, 92 | pure, 93 | areStatesEqual, 94 | areOwnPropsEqual, 95 | areStatePropsEqual, 96 | areMergedPropsEqual, 97 | 98 | // any extra options args can override defaults of connect or connectAdvanced 99 | ...extraOptions 100 | }) 101 | } 102 | } 103 | 104 | export default createConnect() 105 | -------------------------------------------------------------------------------- /src/connect/mapDispatchToProps.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux' 2 | import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps' 3 | 4 | export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) { 5 | return typeof mapDispatchToProps === 'function' 6 | ? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps') 7 | : undefined 8 | } 9 | 10 | export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) { 11 | return !mapDispatchToProps 12 | ? wrapMapToPropsConstant(dispatch => ({ dispatch })) 13 | : undefined 14 | } 15 | 16 | export function whenMapDispatchToPropsIsObject(mapDispatchToProps) { 17 | return mapDispatchToProps && typeof mapDispatchToProps === 'object' 18 | ? wrapMapToPropsConstant(dispatch => 19 | bindActionCreators(mapDispatchToProps, dispatch) 20 | ) 21 | : undefined 22 | } 23 | 24 | export default [ 25 | whenMapDispatchToPropsIsFunction, 26 | whenMapDispatchToPropsIsMissing, 27 | whenMapDispatchToPropsIsObject 28 | ] 29 | -------------------------------------------------------------------------------- /src/connect/mapStateToProps.js: -------------------------------------------------------------------------------- 1 | import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps' 2 | 3 | export function whenMapStateToPropsIsFunction(mapStateToProps) { 4 | return typeof mapStateToProps === 'function' 5 | ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps') 6 | : undefined 7 | } 8 | 9 | export function whenMapStateToPropsIsMissing(mapStateToProps) { 10 | return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined 11 | } 12 | 13 | export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing] 14 | -------------------------------------------------------------------------------- /src/connect/mergeProps.js: -------------------------------------------------------------------------------- 1 | import verifyPlainObject from '../utils/verifyPlainObject' 2 | 3 | export function defaultMergeProps(stateProps, dispatchProps, ownProps) { 4 | return { ...ownProps, ...stateProps, ...dispatchProps } 5 | } 6 | 7 | export function wrapMergePropsFunc(mergeProps) { 8 | return function initMergePropsProxy( 9 | dispatch, 10 | { displayName, pure, areMergedPropsEqual } 11 | ) { 12 | let hasRunOnce = false 13 | let mergedProps 14 | 15 | return function mergePropsProxy(stateProps, dispatchProps, ownProps) { 16 | const nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps) 17 | 18 | if (hasRunOnce) { 19 | if (!pure || !areMergedPropsEqual(nextMergedProps, mergedProps)) 20 | mergedProps = nextMergedProps 21 | } else { 22 | hasRunOnce = true 23 | mergedProps = nextMergedProps 24 | 25 | if (process.env.NODE_ENV !== 'production') 26 | verifyPlainObject(mergedProps, displayName, 'mergeProps') 27 | } 28 | 29 | return mergedProps 30 | } 31 | } 32 | } 33 | 34 | export function whenMergePropsIsFunction(mergeProps) { 35 | return typeof mergeProps === 'function' 36 | ? wrapMergePropsFunc(mergeProps) 37 | : undefined 38 | } 39 | 40 | export function whenMergePropsIsOmitted(mergeProps) { 41 | return !mergeProps ? () => defaultMergeProps : undefined 42 | } 43 | 44 | export default [whenMergePropsIsFunction, whenMergePropsIsOmitted] 45 | -------------------------------------------------------------------------------- /src/connect/selectorFactory.js: -------------------------------------------------------------------------------- 1 | import verifySubselectors from './verifySubselectors' 2 | 3 | export function impureFinalPropsSelectorFactory( 4 | mapStateToProps, 5 | mapDispatchToProps, 6 | mergeProps, 7 | dispatch 8 | ) { 9 | return function impureFinalPropsSelector(state, ownProps) { 10 | return mergeProps( 11 | mapStateToProps(state, ownProps), 12 | mapDispatchToProps(dispatch, ownProps), 13 | ownProps 14 | ) 15 | } 16 | } 17 | 18 | export function pureFinalPropsSelectorFactory( 19 | mapStateToProps, 20 | mapDispatchToProps, 21 | mergeProps, 22 | dispatch, 23 | { areStatesEqual, areOwnPropsEqual, areStatePropsEqual } 24 | ) { 25 | let hasRunAtLeastOnce = false 26 | let state 27 | let ownProps 28 | let stateProps 29 | let dispatchProps 30 | let mergedProps 31 | 32 | function handleFirstCall(firstState, firstOwnProps) { 33 | state = firstState 34 | ownProps = firstOwnProps 35 | stateProps = mapStateToProps(state, ownProps) 36 | dispatchProps = mapDispatchToProps(dispatch, ownProps) 37 | mergedProps = mergeProps(stateProps, dispatchProps, ownProps) 38 | hasRunAtLeastOnce = true 39 | return mergedProps 40 | } 41 | 42 | function handleNewPropsAndNewState() { 43 | stateProps = mapStateToProps(state, ownProps) 44 | 45 | if (mapDispatchToProps.dependsOnOwnProps) 46 | dispatchProps = mapDispatchToProps(dispatch, ownProps) 47 | 48 | mergedProps = mergeProps(stateProps, dispatchProps, ownProps) 49 | return mergedProps 50 | } 51 | 52 | function handleNewProps() { 53 | if (mapStateToProps.dependsOnOwnProps) 54 | stateProps = mapStateToProps(state, ownProps) 55 | 56 | if (mapDispatchToProps.dependsOnOwnProps) 57 | dispatchProps = mapDispatchToProps(dispatch, ownProps) 58 | 59 | mergedProps = mergeProps(stateProps, dispatchProps, ownProps) 60 | return mergedProps 61 | } 62 | 63 | function handleNewState() { 64 | const nextStateProps = mapStateToProps(state, ownProps) 65 | const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps) 66 | stateProps = nextStateProps 67 | 68 | if (statePropsChanged) 69 | mergedProps = mergeProps(stateProps, dispatchProps, ownProps) 70 | 71 | return mergedProps 72 | } 73 | 74 | function handleSubsequentCalls(nextState, nextOwnProps) { 75 | const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps) 76 | const stateChanged = !areStatesEqual(nextState, state) 77 | state = nextState 78 | ownProps = nextOwnProps 79 | 80 | if (propsChanged && stateChanged) return handleNewPropsAndNewState() 81 | if (propsChanged) return handleNewProps() 82 | if (stateChanged) return handleNewState() 83 | return mergedProps 84 | } 85 | 86 | return function pureFinalPropsSelector(nextState, nextOwnProps) { 87 | return hasRunAtLeastOnce 88 | ? handleSubsequentCalls(nextState, nextOwnProps) 89 | : handleFirstCall(nextState, nextOwnProps) 90 | } 91 | } 92 | 93 | // TODO: Add more comments 94 | 95 | // If pure is true, the selector returned by selectorFactory will memoize its results, 96 | // allowing connectAdvanced's shouldComponentUpdate to return false if final 97 | // props have not changed. If false, the selector will always return a new 98 | // object and shouldComponentUpdate will always return true. 99 | 100 | export default function finalPropsSelectorFactory( 101 | dispatch, 102 | { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options } 103 | ) { 104 | const mapStateToProps = initMapStateToProps(dispatch, options) 105 | const mapDispatchToProps = initMapDispatchToProps(dispatch, options) 106 | const mergeProps = initMergeProps(dispatch, options) 107 | 108 | if (process.env.NODE_ENV !== 'production') { 109 | verifySubselectors( 110 | mapStateToProps, 111 | mapDispatchToProps, 112 | mergeProps, 113 | options.displayName 114 | ) 115 | } 116 | 117 | const selectorFactory = options.pure 118 | ? pureFinalPropsSelectorFactory 119 | : impureFinalPropsSelectorFactory 120 | 121 | return selectorFactory( 122 | mapStateToProps, 123 | mapDispatchToProps, 124 | mergeProps, 125 | dispatch, 126 | options 127 | ) 128 | } 129 | -------------------------------------------------------------------------------- /src/connect/verifySubselectors.js: -------------------------------------------------------------------------------- 1 | import warning from '../utils/warning' 2 | 3 | function verify(selector, methodName, displayName) { 4 | if (!selector) { 5 | throw new Error(`Unexpected value for ${methodName} in ${displayName}.`) 6 | } else if ( 7 | methodName === 'mapStateToProps' || 8 | methodName === 'mapDispatchToProps' 9 | ) { 10 | if (!selector.hasOwnProperty('dependsOnOwnProps')) { 11 | warning( 12 | `The selector for ${methodName} of ${displayName} did not specify a value for dependsOnOwnProps.` 13 | ) 14 | } 15 | } 16 | } 17 | 18 | export default function verifySubselectors( 19 | mapStateToProps, 20 | mapDispatchToProps, 21 | mergeProps, 22 | displayName 23 | ) { 24 | verify(mapStateToProps, 'mapStateToProps', displayName) 25 | verify(mapDispatchToProps, 'mapDispatchToProps', displayName) 26 | verify(mergeProps, 'mergeProps', displayName) 27 | } 28 | -------------------------------------------------------------------------------- /src/connect/wrapMapToProps.js: -------------------------------------------------------------------------------- 1 | import verifyPlainObject from '../utils/verifyPlainObject' 2 | 3 | export function wrapMapToPropsConstant(getConstant) { 4 | return function initConstantSelector(dispatch, options) { 5 | const constant = getConstant(dispatch, options) 6 | 7 | function constantSelector() { 8 | return constant 9 | } 10 | constantSelector.dependsOnOwnProps = false 11 | return constantSelector 12 | } 13 | } 14 | 15 | // dependsOnOwnProps is used by createMapToPropsProxy to determine whether to pass props as args 16 | // to the mapToProps function being wrapped. It is also used by makePurePropsSelector to determine 17 | // whether mapToProps needs to be invoked when props have changed. 18 | // 19 | // A length of one signals that mapToProps does not depend on props from the parent component. 20 | // A length of zero is assumed to mean mapToProps is getting args via arguments or ...args and 21 | // therefore not reporting its length accurately.. 22 | export function getDependsOnOwnProps(mapToProps) { 23 | return mapToProps.dependsOnOwnProps !== null && 24 | mapToProps.dependsOnOwnProps !== undefined 25 | ? Boolean(mapToProps.dependsOnOwnProps) 26 | : mapToProps.length !== 1 27 | } 28 | 29 | // Used by whenMapStateToPropsIsFunction and whenMapDispatchToPropsIsFunction, 30 | // this function wraps mapToProps in a proxy function which does several things: 31 | // 32 | // * Detects whether the mapToProps function being called depends on props, which 33 | // is used by selectorFactory to decide if it should reinvoke on props changes. 34 | // 35 | // * On first call, handles mapToProps if returns another function, and treats that 36 | // new function as the true mapToProps for subsequent calls. 37 | // 38 | // * On first call, verifies the first result is a plain object, in order to warn 39 | // the developer that their mapToProps function is not returning a valid result. 40 | // 41 | export function wrapMapToPropsFunc(mapToProps, methodName) { 42 | return function initProxySelector(dispatch, { displayName }) { 43 | const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) { 44 | return proxy.dependsOnOwnProps 45 | ? proxy.mapToProps(stateOrDispatch, ownProps) 46 | : proxy.mapToProps(stateOrDispatch) 47 | } 48 | 49 | // allow detectFactoryAndVerify to get ownProps 50 | proxy.dependsOnOwnProps = true 51 | 52 | proxy.mapToProps = function detectFactoryAndVerify( 53 | stateOrDispatch, 54 | ownProps 55 | ) { 56 | proxy.mapToProps = mapToProps 57 | proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps) 58 | let props = proxy(stateOrDispatch, ownProps) 59 | 60 | if (typeof props === 'function') { 61 | proxy.mapToProps = props 62 | proxy.dependsOnOwnProps = getDependsOnOwnProps(props) 63 | props = proxy(stateOrDispatch, ownProps) 64 | } 65 | 66 | if (process.env.NODE_ENV !== 'production') 67 | verifyPlainObject(props, displayName, methodName) 68 | 69 | return props 70 | } 71 | 72 | return proxy 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Provider from './components/Provider' 2 | import connectAdvanced from './components/connectAdvanced' 3 | import { ReactReduxContext } from './components/Context' 4 | import connect from './connect/connect' 5 | 6 | export { Provider, connectAdvanced, ReactReduxContext, connect } 7 | -------------------------------------------------------------------------------- /src/utils/Subscription.js: -------------------------------------------------------------------------------- 1 | import { unstable_batchedUpdates } from 'react-dom' 2 | 3 | // encapsulates the subscription logic for connecting a component to the redux store, as 4 | // well as nesting subscriptions of descendant components, so that we can ensure the 5 | // ancestor components re-render before descendants 6 | 7 | const CLEARED = null 8 | const nullListeners = { notify() {} } 9 | 10 | function createListenerCollection() { 11 | // the current/next pattern is copied from redux's createStore code. 12 | // TODO: refactor+expose that code to be reusable here? 13 | let current = [] 14 | let next = [] 15 | 16 | return { 17 | clear() { 18 | next = CLEARED 19 | current = CLEARED 20 | }, 21 | 22 | notify() { 23 | const listeners = (current = next) 24 | unstable_batchedUpdates(() => { 25 | for (let i = 0; i < listeners.length; i++) { 26 | listeners[i]() 27 | } 28 | }) 29 | }, 30 | 31 | get() { 32 | return next 33 | }, 34 | 35 | subscribe(listener) { 36 | let isSubscribed = true 37 | if (next === current) next = current.slice() 38 | next.push(listener) 39 | 40 | return function unsubscribe() { 41 | if (!isSubscribed || current === CLEARED) return 42 | isSubscribed = false 43 | 44 | if (next === current) next = current.slice() 45 | next.splice(next.indexOf(listener), 1) 46 | } 47 | } 48 | } 49 | } 50 | 51 | export default class Subscription { 52 | constructor(store, parentSub, onStateChange) { 53 | this.store = store 54 | this.parentSub = parentSub 55 | this.unsubscribe = null 56 | this.onStateChange = onStateChange 57 | this.listeners = nullListeners 58 | 59 | this.handleChangeWrapper = this.handleChangeWrapper.bind(this) 60 | } 61 | 62 | addNestedSub(listener) { 63 | this.trySubscribe() 64 | return this.listeners.subscribe(listener) 65 | } 66 | 67 | notifyNestedSubs() { 68 | this.listeners.notify() 69 | } 70 | 71 | handleChangeWrapper() { 72 | if (this.onStateChange) { 73 | this.onStateChange() 74 | } 75 | } 76 | 77 | isSubscribed() { 78 | return Boolean(this.unsubscribe) 79 | } 80 | 81 | trySubscribe() { 82 | if (!this.unsubscribe) { 83 | this.unsubscribe = this.parentSub 84 | ? this.parentSub.addNestedSub(this.handleChangeWrapper) 85 | : this.store.subscribe(this.handleChangeWrapper) 86 | 87 | this.listeners = createListenerCollection() 88 | } 89 | } 90 | 91 | tryUnsubscribe() { 92 | if (this.unsubscribe) { 93 | this.unsubscribe() 94 | this.unsubscribe = null 95 | this.listeners.clear() 96 | this.listeners = nullListeners 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/utils/isPlainObject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {any} obj The object to inspect. 3 | * @returns {boolean} True if the argument appears to be a plain object. 4 | */ 5 | export default function isPlainObject(obj) { 6 | if (typeof obj !== 'object' || obj === null) return false 7 | 8 | let proto = Object.getPrototypeOf(obj) 9 | if (proto === null) return true 10 | 11 | let baseProto = proto 12 | while (Object.getPrototypeOf(baseProto) !== null) { 13 | baseProto = Object.getPrototypeOf(baseProto) 14 | } 15 | 16 | return proto === baseProto 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/readContext.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const unstable_readContext = Context => { 4 | const s = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED 5 | return s.ReactCurrentDispatcher.current.readContext(Context) 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/shallowEqual.js: -------------------------------------------------------------------------------- 1 | const hasOwn = Object.prototype.hasOwnProperty 2 | 3 | function is(x, y) { 4 | if (x === y) { 5 | return x !== 0 || y !== 0 || 1 / x === 1 / y 6 | } else { 7 | return x !== x && y !== y 8 | } 9 | } 10 | 11 | export default function shallowEqual(objA, objB) { 12 | if (is(objA, objB)) return true 13 | 14 | if ( 15 | typeof objA !== 'object' || 16 | objA === null || 17 | typeof objB !== 'object' || 18 | objB === null 19 | ) { 20 | return false 21 | } 22 | 23 | const keysA = Object.keys(objA) 24 | const keysB = Object.keys(objB) 25 | 26 | if (keysA.length !== keysB.length) return false 27 | 28 | for (let i = 0; i < keysA.length; i++) { 29 | if (!hasOwn.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) { 30 | return false 31 | } 32 | } 33 | 34 | return true 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/verifyPlainObject.js: -------------------------------------------------------------------------------- 1 | import isPlainObject from './isPlainObject' 2 | import warning from './warning' 3 | 4 | export default function verifyPlainObject(value, displayName, methodName) { 5 | if (!isPlainObject(value)) { 6 | warning( 7 | `${methodName}() in ${displayName} must return a plain object. Instead received ${value}.` 8 | ) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/warning.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Prints a warning in the console if it exists. 3 | * 4 | * @param {String} message The warning message. 5 | * @returns {void} 6 | */ 7 | export default function warning(message) { 8 | /* eslint-disable no-console */ 9 | if (typeof console !== 'undefined' && typeof console.error === 'function') { 10 | console.error(message) 11 | } 12 | /* eslint-enable no-console */ 13 | try { 14 | // This error was thrown as a convenience so that if you enable 15 | // "break on all exceptions" in your console, 16 | // it would pause the execution at this line. 17 | throw new Error(message) 18 | /* eslint-disable no-empty */ 19 | } catch (e) {} 20 | /* eslint-enable no-empty */ 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/wrapActionCreators.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux' 2 | 3 | export default function wrapActionCreators(actionCreators) { 4 | return dispatch => bindActionCreators(actionCreators, dispatch) 5 | } 6 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/babel-transformer.jest.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { createTransformer } = require('babel-jest') 3 | 4 | module.exports = createTransformer({ 5 | configFile: path.resolve(__dirname, '../.babelrc.js') 6 | }) 7 | -------------------------------------------------------------------------------- /test/components/Provider.spec.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable react/prop-types*/ 2 | 3 | import React, { Component } from 'react' 4 | import ReactDOM from 'react-dom' 5 | import { createStore } from 'redux' 6 | import { Provider, connect } from '../../src/index.js' 7 | import * as rtl from 'react-testing-library' 8 | import 'jest-dom/extend-expect' 9 | 10 | const createExampleTextReducer = () => (state = 'example text') => state 11 | 12 | describe('React', () => { 13 | describe('Provider', () => { 14 | afterEach(() => rtl.cleanup()) 15 | 16 | const createChild = (storeKey = 'store') => { 17 | class Child extends Component { 18 | render() { 19 | return ( 20 |
21 | {storeKey} - {this.props.text} 22 |
23 | ) 24 | } 25 | } 26 | 27 | return connect(s => ({ text: s }))(Child) 28 | } 29 | const Child = createChild() 30 | 31 | it('should not enforce a single child', () => { 32 | const store = createStore(() => ({})) 33 | 34 | // Ignore propTypes warnings 35 | const propTypes = Provider.propTypes 36 | Provider.propTypes = {} 37 | 38 | const spy = jest.spyOn(console, 'error').mockImplementation(() => {}) 39 | 40 | expect(() => 41 | rtl.render( 42 | 43 |
44 | 45 | ) 46 | ).not.toThrow() 47 | 48 | expect(() => rtl.render()).not.toThrow( 49 | /children with exactly one child/ 50 | ) 51 | 52 | expect(() => 53 | rtl.render( 54 | 55 |
56 |
57 | 58 | ) 59 | ).not.toThrow(/a single React element child/) 60 | spy.mockRestore() 61 | Provider.propTypes = propTypes 62 | }) 63 | 64 | it('should add the store state to context', () => { 65 | const store = createStore(createExampleTextReducer()) 66 | 67 | const spy = jest.spyOn(console, 'error').mockImplementation(() => {}) 68 | const tester = rtl.render( 69 | 70 | 71 | 72 | ) 73 | expect(spy).toHaveBeenCalledTimes(0) 74 | spy.mockRestore() 75 | 76 | expect(tester.getByTestId('store')).toHaveTextContent( 77 | 'store - example text' 78 | ) 79 | }) 80 | 81 | it('accepts new store in props', () => { 82 | const store1 = createStore((state = 10) => state + 1) 83 | const store2 = createStore((state = 10) => state * 2) 84 | const store3 = createStore((state = 10) => state * state + 1) 85 | 86 | let externalSetState 87 | class ProviderContainer extends Component { 88 | constructor() { 89 | super() 90 | this.state = { store: store1 } 91 | externalSetState = this.setState.bind(this) 92 | } 93 | 94 | render() { 95 | return ( 96 | 97 | 98 | 99 | ) 100 | } 101 | } 102 | 103 | const tester = rtl.render() 104 | expect(tester.getByTestId('store')).toHaveTextContent('store - 11') 105 | store1.dispatch({ type: 'hi' }) 106 | expect(tester.getByTestId('store')).toHaveTextContent('store - 12') 107 | 108 | externalSetState({ store: store2 }) 109 | expect(tester.getByTestId('store')).toHaveTextContent('store - 20') 110 | store1.dispatch({ type: 'hi' }) 111 | expect(tester.getByTestId('store')).toHaveTextContent('store - 20') 112 | store2.dispatch({ type: 'hi' }) 113 | expect(tester.getByTestId('store')).toHaveTextContent('store - 40') 114 | 115 | externalSetState({ store: store3 }) 116 | expect(tester.getByTestId('store')).toHaveTextContent('store - 101') 117 | store1.dispatch({ type: 'hi' }) 118 | expect(tester.getByTestId('store')).toHaveTextContent('store - 101') 119 | store2.dispatch({ type: 'hi' }) 120 | expect(tester.getByTestId('store')).toHaveTextContent('store - 101') 121 | store3.dispatch({ type: 'hi' }) 122 | expect(tester.getByTestId('store')).toHaveTextContent('store - 10202') 123 | }) 124 | 125 | it('should handle subscriptions correctly when there is nested Providers', () => { 126 | const reducer = (state = 0, action) => 127 | action.type === 'INC' ? state + 1 : state 128 | 129 | const innerStore = createStore(reducer) 130 | const innerMapStateToProps = jest.fn(state => ({ count: state })) 131 | @connect(innerMapStateToProps) 132 | class Inner extends Component { 133 | render() { 134 | return
{this.props.count}
135 | } 136 | } 137 | 138 | const outerStore = createStore(reducer) 139 | @connect(state => ({ count: state })) 140 | class Outer extends Component { 141 | render() { 142 | return ( 143 | 144 | 145 | 146 | ) 147 | } 148 | } 149 | 150 | rtl.render( 151 | 152 | 153 | 154 | ) 155 | expect(innerMapStateToProps).toHaveBeenCalledTimes(1) 156 | 157 | innerStore.dispatch({ type: 'INC' }) 158 | expect(innerMapStateToProps).toHaveBeenCalledTimes(2) 159 | }) 160 | 161 | /* 162 | //this test is removed because now storeValue is not propagated via Context 163 | so even if mapState is running 2 times on Child, the rendering is batched 164 | and so Done one time only with correct ownProps 165 | // 166 | it('should pass state consistently to mapState', () => { 167 | function stringBuilder(prev = '', action) { 168 | return action.type === 'APPEND' ? prev + action.body : prev 169 | } 170 | 171 | const store = createStore(stringBuilder) 172 | 173 | store.dispatch({ type: 'APPEND', body: 'a' }) 174 | let childMapStateInvokes = 0 175 | 176 | @connect(state => ({ state })) 177 | class Container extends Component { 178 | emitChange() { 179 | store.dispatch({ type: 'APPEND', body: 'b' }) 180 | } 181 | 182 | render() { 183 | return ( 184 |
185 | 186 | 187 |
188 | ) 189 | } 190 | } 191 | 192 | const childCalls = [] 193 | @connect((state, parentProps) => { 194 | childMapStateInvokes++ 195 | childCalls.push([state, parentProps.parentState]) 196 | // The state from parent props should always be consistent with the current state 197 | return {} 198 | }) 199 | class ChildContainer extends Component { 200 | render() { 201 | return
202 | } 203 | } 204 | 205 | const tester = rtl.render( 206 | 207 | 208 | 209 | ) 210 | 211 | expect(childMapStateInvokes).toBe(1) 212 | 213 | // The store state stays consistent when setState calls are batched 214 | store.dispatch({ type: 'APPEND', body: 'c' }) 215 | expect(childMapStateInvokes).toBe(2) 216 | expect(childCalls).toEqual([['a', 'a'], ['ac', 'ac']]) 217 | 218 | // setState calls DOM handlers are batched 219 | const button = tester.getByText('change') 220 | rtl.fireEvent.click(button) 221 | expect(childMapStateInvokes).toBe(3) 222 | 223 | // Provider uses unstable_batchedUpdates() under the hood 224 | store.dispatch({ type: 'APPEND', body: 'd' }) 225 | expect(childCalls).toEqual([ 226 | ['a', 'a'], 227 | ['ac', 'ac'], // then store update is processed 228 | ['acb', 'acb'], // then store update is processed 229 | ['acbd', 'acbd'] // then store update is processed 230 | ]) 231 | expect(childMapStateInvokes).toBe(4) 232 | })*/ 233 | 234 | it('works in without warnings (React 16.3+)', () => { 235 | if (!React.StrictMode) { 236 | return 237 | } 238 | const spy = jest.spyOn(console, 'error').mockImplementation(() => {}) 239 | const store = createStore(() => ({})) 240 | 241 | rtl.render( 242 | 243 | 244 |
245 | 246 | 247 | ) 248 | 249 | expect(spy).not.toHaveBeenCalled() 250 | }) 251 | 252 | it('should unsubscribe before unmounting', () => { 253 | const store = createStore(createExampleTextReducer()) 254 | const subscribe = store.subscribe 255 | 256 | // Keep track of unsubscribe by wrapping subscribe() 257 | const spy = jest.fn(() => ({})) 258 | store.subscribe = listener => { 259 | const unsubscribe = subscribe(listener) 260 | return () => { 261 | spy() 262 | return unsubscribe() 263 | } 264 | } 265 | 266 | const div = document.createElement('div') 267 | ReactDOM.render( 268 | 269 |
270 | , 271 | div 272 | ) 273 | 274 | expect(spy).toHaveBeenCalledTimes(0) 275 | ReactDOM.unmountComponentAtNode(div) 276 | expect(spy).toHaveBeenCalledTimes(1) 277 | }) 278 | }) 279 | }) 280 | -------------------------------------------------------------------------------- /test/components/connectAdvanced.spec.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import * as rtl from 'react-testing-library' 3 | import { Provider as ProviderMock, connectAdvanced } from '../../src/index.js' 4 | import { createStore } from 'redux' 5 | import 'jest-dom/extend-expect' 6 | 7 | describe('React', () => { 8 | describe('connectAdvanced', () => { 9 | it('should map state and render twice on mount if pure', () => { 10 | const initialState = { 11 | foo: 'bar' 12 | } 13 | 14 | let mapCount = 0 15 | let renderCount = 0 16 | 17 | const store = createStore(() => initialState) 18 | 19 | function Inner(props) { 20 | renderCount++ 21 | return
{JSON.stringify(props)}
22 | } 23 | 24 | const Container = connectAdvanced( 25 | () => { 26 | return state => { 27 | mapCount++ 28 | return state 29 | } 30 | }, 31 | { pure: true } 32 | )(Inner) 33 | 34 | const tester = rtl.render( 35 | 36 | 37 | 38 | ) 39 | 40 | expect(tester.getByTestId('foo')).toHaveTextContent('bar') 41 | 42 | expect(mapCount).toEqual(2) 43 | expect(renderCount).toEqual(1) 44 | }) 45 | 46 | it('should render on reference change', () => { 47 | let mapCount = 0 48 | let renderCount = 0 49 | 50 | // force new reference on each dispatch 51 | const store = createStore(() => ({ 52 | foo: 'bar' 53 | })) 54 | 55 | function Inner(props) { 56 | renderCount++ 57 | return
{JSON.stringify(props)}
58 | } 59 | 60 | const Container = connectAdvanced( 61 | () => { 62 | return state => { 63 | mapCount++ 64 | return state 65 | } 66 | }, 67 | { pure: true } 68 | )(Inner) 69 | 70 | rtl.render( 71 | 72 | 73 | 74 | ) 75 | 76 | rtl.act(() => { 77 | store.dispatch({ type: 'NEW_REFERENCE' }) 78 | }) 79 | 80 | // Should have mapped the state on init, mount, and on the dispatch 81 | expect(mapCount).toEqual(3) 82 | 83 | // Should have rendered on mount and after the dispatch bacause the map 84 | // state returned new reference 85 | expect(renderCount).toEqual(2) 86 | }) 87 | 88 | it('should not render when the returned reference does not change', () => { 89 | const staticReference = { 90 | foo: 'bar' 91 | } 92 | 93 | let mapCount = 0 94 | let renderCount = 0 95 | 96 | // force new reference on each dispatch 97 | const store = createStore(() => ({ 98 | foo: 'bar' 99 | })) 100 | 101 | function Inner(props) { 102 | renderCount++ 103 | return
{JSON.stringify(props)}
104 | } 105 | 106 | const Container = connectAdvanced( 107 | () => { 108 | return () => { 109 | mapCount++ 110 | // but return static reference 111 | return staticReference 112 | } 113 | }, 114 | { pure: true } 115 | )(Inner) 116 | 117 | const tester = rtl.render( 118 | 119 | 120 | 121 | ) 122 | 123 | rtl.act(() => { 124 | store.dispatch({ type: 'NEW_REFERENCE' }) 125 | }) 126 | 127 | expect(tester.getByTestId('foo')).toHaveTextContent('bar') 128 | 129 | // The state should have been mapped twice: on mount and on the dispatch 130 | expect(mapCount).toEqual(3) 131 | 132 | // But the render should have been called only on mount since the map state 133 | // did not return a new reference 134 | expect(renderCount).toEqual(1) 135 | }) 136 | 137 | it('should map state on own props change but not render when the reference does not change', () => { 138 | const staticReference = { 139 | foo: 'bar' 140 | } 141 | 142 | let mapCount = 0 143 | let renderCount = 0 144 | 145 | const store = createStore(() => staticReference) 146 | 147 | function Inner(props) { 148 | renderCount++ 149 | return
{JSON.stringify(props)}
150 | } 151 | 152 | const Container = connectAdvanced( 153 | () => { 154 | return () => { 155 | mapCount++ 156 | // return the static reference 157 | return staticReference 158 | } 159 | }, 160 | { pure: true } 161 | )(Inner) 162 | 163 | class OuterComponent extends Component { 164 | constructor() { 165 | super() 166 | this.state = { foo: 'FOO' } 167 | } 168 | 169 | setFoo(foo) { 170 | this.setState({ foo }) 171 | } 172 | 173 | render() { 174 | return ( 175 |
176 | 177 |
178 | ) 179 | } 180 | } 181 | 182 | let outerComponent 183 | rtl.render( 184 | 185 | (outerComponent = c)} /> 186 | 187 | ) 188 | 189 | rtl.act(() => { 190 | outerComponent.setFoo('BAR') 191 | }) 192 | 193 | // map on mount and on prop change 194 | expect(mapCount).toEqual(3) 195 | 196 | // render only on mount but skip on prop change because no new 197 | // reference was returned 198 | expect(renderCount).toEqual(1) 199 | }) 200 | }) 201 | }) 202 | -------------------------------------------------------------------------------- /test/install-test-deps.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | 'use strict' 3 | 4 | const { readdirSync, existsSync, copyFile, mkdirSync } = require('fs') 5 | const rimraf = require('rimraf') 6 | const { join } = require('path') 7 | const spawn = require('cross-spawn') 8 | const reactVersion = process.env.REACT || '16.8' 9 | 10 | readdirSync(join(__dirname, 'react')).forEach(version => { 11 | if (reactVersion.toLowerCase() !== 'all' && version !== reactVersion) { 12 | console.log(`skipping ${version}, ${reactVersion} was specified`) 13 | return 14 | } 15 | const tests = [ 16 | join(__dirname, 'components'), 17 | join(__dirname, 'integration'), 18 | join(__dirname, 'utils') 19 | ] 20 | const srcs = [ 21 | join(__dirname, '..', 'src', 'components'), 22 | join(__dirname, '..', 'src', 'connect'), 23 | join(__dirname, '..', 'src', 'utils') 24 | ] 25 | const dest = [ 26 | join(__dirname, 'react', version, 'test', 'components'), 27 | join(__dirname, 'react', version, 'test', 'integration'), 28 | join(__dirname, 'react', version, 'test', 'utils') 29 | ] 30 | const srcDest = [ 31 | join(__dirname, 'react', version, 'src', 'components'), 32 | join(__dirname, 'react', version, 'src', 'connect'), 33 | join(__dirname, 'react', version, 'src', 'utils') 34 | ] 35 | 36 | if (!existsSync(join(__dirname, 'react', version, 'test'))) { 37 | mkdirSync(join(__dirname, 'react', version, 'test')) 38 | } 39 | 40 | if (!existsSync(join(__dirname, 'react', version, 'src'))) { 41 | mkdirSync(join(__dirname, 'react', version, 'src')) 42 | } 43 | 44 | console.log('Copying test files') 45 | tests.forEach((dir, i) => { 46 | if (existsSync(dest[i])) { 47 | console.log('clearing old tests in ' + dest[i]) 48 | rimraf.sync(join(dest[i], '*')) 49 | } else { 50 | mkdirSync(dest[i]) 51 | } 52 | readdirSync(dir).forEach(file => { 53 | copyFile(join(tests[i], file), join(dest[i], file), e => { 54 | if (e) console.log(e) 55 | }) 56 | }) 57 | }) 58 | console.log('Copying source files') 59 | srcs.forEach((dir, i) => { 60 | if (existsSync(srcDest[i])) { 61 | console.log('clearing old sources in ' + srcDest[i]) 62 | rimraf.sync(join(srcDest[i], '*')) 63 | } else { 64 | if (!existsSync(join(__dirname, 'react', version, 'src'))) { 65 | mkdirSync(join(__dirname, 'react', version, 'src')) 66 | } 67 | mkdirSync(srcDest[i]) 68 | } 69 | readdirSync(dir).forEach(file => { 70 | copyFile(join(srcs[i], file), join(srcDest[i], file), e => { 71 | if (e) console.log(e) 72 | }) 73 | }) 74 | copyFile( 75 | join(__dirname, '..', 'src', 'index.js'), 76 | join(__dirname, 'react', version, 'src', 'index.js'), 77 | e => { 78 | if (e) console.log(e) 79 | } 80 | ) 81 | }) 82 | const cwd = join(__dirname, 'react', version) 83 | if ( 84 | existsSync( 85 | join(__dirname, 'react', version, 'node_modules', 'react', 'package.json') 86 | ) 87 | ) { 88 | console.log(`Skipping React version ${version} ... (already installed)`) 89 | return 90 | } 91 | 92 | console.log(`Installing React version ${version}...`) 93 | 94 | const installTask = spawn.sync('npm', ['install'], { 95 | cwd, 96 | stdio: 'inherit' 97 | }) 98 | 99 | if (installTask.status > 0) { 100 | process.exit(installTask.status) 101 | } 102 | }) 103 | -------------------------------------------------------------------------------- /test/integration/server-rendering.spec.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable react/prop-types*/ 2 | 3 | import React from 'react' 4 | import { renderToString } from 'react-dom/server' 5 | import { createStore } from 'redux' 6 | import { Provider, connect } from '../../src/index.js' 7 | 8 | describe('React', () => { 9 | describe('server rendering', () => { 10 | function greetingReducer(state = { greeting: 'Hello' }, action) { 11 | return action && action.payload ? action.payload : state 12 | } 13 | 14 | const Greeting = ({ greeting, greeted }) => greeting + ' ' + greeted 15 | const ConnectedGreeting = connect(state => state)(Greeting) 16 | 17 | const Greeter = props => ( 18 |
19 | 20 |
21 | ) 22 | 23 | it('should be able to render connected component with props and state from store', () => { 24 | const store = createStore(greetingReducer) 25 | 26 | const markup = renderToString( 27 | 28 | 29 | 30 | ) 31 | 32 | expect(markup).toContain('Hello world') 33 | }) 34 | 35 | it('should render with updated state if actions are dispatched before render', () => { 36 | const store = createStore(greetingReducer) 37 | 38 | store.dispatch({ type: 'Update', payload: { greeting: 'Hi' } }) 39 | 40 | const markup = renderToString( 41 | 42 | 43 | 44 | ) 45 | 46 | expect(markup).toContain('Hi world') 47 | expect(store.getState().greeting).toContain('Hi') 48 | }) 49 | 50 | it('should render children with original state even if actions are dispatched in ancestor', () => { 51 | /* 52 | Dispatching during construct, render or willMount is 53 | almost always a bug with SSR (or otherwise) 54 | 55 | This behaviour is undocumented and is likely to change between 56 | implementations, this test only verifies current behaviour 57 | */ 58 | const store = createStore(greetingReducer) 59 | 60 | class Dispatcher extends React.Component { 61 | constructor(props) { 62 | super(props) 63 | props.dispatch(props.action) 64 | } 65 | UNSAFE_componentWillMount() { 66 | this.props.dispatch(this.props.action) 67 | } 68 | render() { 69 | this.props.dispatch(this.props.action) 70 | 71 | return 72 | } 73 | } 74 | const ConnectedDispatcher = connect()(Dispatcher) 75 | 76 | const action = { type: 'Update', payload: { greeting: 'Hi' } } 77 | 78 | const markup = renderToString( 79 | 80 | 81 | 82 | ) 83 | 84 | expect(markup).toContain('
Hi world
') 85 | expect(store.getState().greeting).toContain('Hi') 86 | }) 87 | 88 | it('should render children with changed state if actions are dispatched in ancestor and new Provider wraps children', () => { 89 | /* 90 | Dispatching during construct, render or willMount is 91 | almost always a bug with SSR (or otherwise) 92 | 93 | This behaviour is undocumented and is likely to change between 94 | implementations, this test only verifies current behaviour 95 | */ 96 | const store = createStore(greetingReducer) 97 | 98 | class Dispatcher extends React.Component { 99 | constructor(props) { 100 | super(props) 101 | if (props.constructAction) { 102 | props.dispatch(props.constructAction) 103 | } 104 | } 105 | UNSAFE_componentWillMount() { 106 | if (this.props.willMountAction) { 107 | this.props.dispatch(this.props.willMountAction) 108 | } 109 | } 110 | render() { 111 | if (this.props.renderAction) { 112 | this.props.dispatch(this.props.renderAction) 113 | } 114 | 115 | return ( 116 | 117 | 118 | 119 | ) 120 | } 121 | } 122 | const ConnectedDispatcher = connect()(Dispatcher) 123 | 124 | const constructAction = { type: 'Update', payload: { greeting: 'Hi' } } 125 | const willMountAction = { type: 'Update', payload: { greeting: 'Hiya' } } 126 | const renderAction = { type: 'Update', payload: { greeting: 'Hey' } } 127 | 128 | const markup = renderToString( 129 | 130 | 134 | 138 | 139 | 140 | ) 141 | 142 | expect(markup).toContain('Hi world') 143 | expect(markup).toContain('Hiya world') 144 | expect(markup).toContain('Hey world') 145 | expect(store.getState().greeting).toContain('Hey') 146 | }) 147 | }) 148 | }) 149 | -------------------------------------------------------------------------------- /test/react/16.4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "create-react-class": "^15.6.3", 5 | "react": "16.4", 6 | "react-dom": "16.4" 7 | }, 8 | "dependencies": { 9 | "jest-dom": "^1.12.0", 10 | "react-testing-library": "^5.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/react/16.5/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "@sheerun/mutationobserver-shim": { 6 | "version": "0.3.2", 7 | "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz", 8 | "integrity": "sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q==" 9 | }, 10 | "ansi-regex": { 11 | "version": "3.0.0", 12 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 13 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" 14 | }, 15 | "ansi-styles": { 16 | "version": "3.2.1", 17 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 18 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 19 | "requires": { 20 | "color-convert": "^1.9.0" 21 | } 22 | }, 23 | "asap": { 24 | "version": "2.0.6", 25 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", 26 | "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", 27 | "dev": true 28 | }, 29 | "atob": { 30 | "version": "2.1.2", 31 | "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", 32 | "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" 33 | }, 34 | "chalk": { 35 | "version": "2.4.1", 36 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", 37 | "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", 38 | "requires": { 39 | "ansi-styles": "^3.2.1", 40 | "escape-string-regexp": "^1.0.5", 41 | "supports-color": "^5.3.0" 42 | } 43 | }, 44 | "color-convert": { 45 | "version": "1.9.3", 46 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 47 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 48 | "requires": { 49 | "color-name": "1.1.3" 50 | } 51 | }, 52 | "color-name": { 53 | "version": "1.1.3", 54 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 55 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 56 | }, 57 | "core-js": { 58 | "version": "1.2.7", 59 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", 60 | "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", 61 | "dev": true 62 | }, 63 | "create-react-class": { 64 | "version": "15.6.3", 65 | "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", 66 | "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", 67 | "dev": true, 68 | "requires": { 69 | "fbjs": "^0.8.9", 70 | "loose-envify": "^1.3.1", 71 | "object-assign": "^4.1.1" 72 | } 73 | }, 74 | "css": { 75 | "version": "2.2.4", 76 | "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", 77 | "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", 78 | "requires": { 79 | "inherits": "^2.0.3", 80 | "source-map": "^0.6.1", 81 | "source-map-resolve": "^0.5.2", 82 | "urix": "^0.1.0" 83 | } 84 | }, 85 | "decode-uri-component": { 86 | "version": "0.2.0", 87 | "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", 88 | "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" 89 | }, 90 | "diff": { 91 | "version": "3.5.0", 92 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 93 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" 94 | }, 95 | "dom-testing-library": { 96 | "version": "3.11.0", 97 | "resolved": "https://registry.npmjs.org/dom-testing-library/-/dom-testing-library-3.11.0.tgz", 98 | "integrity": "sha512-YOrbercTdYvfwkKdiSQXwrQKHbuSYz2DF4f9t9zXCTg+KThWQ15T2MOzWun8GuLx8JaToEsMsoZG3KAdapDzTA==", 99 | "requires": { 100 | "@sheerun/mutationobserver-shim": "^0.3.2", 101 | "pretty-format": "^23.6.0", 102 | "wait-for-expect": "^1.0.0" 103 | } 104 | }, 105 | "encoding": { 106 | "version": "0.1.12", 107 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", 108 | "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", 109 | "dev": true, 110 | "requires": { 111 | "iconv-lite": "~0.4.13" 112 | } 113 | }, 114 | "escape-string-regexp": { 115 | "version": "1.0.5", 116 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 117 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 118 | }, 119 | "fbjs": { 120 | "version": "0.8.17", 121 | "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", 122 | "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", 123 | "dev": true, 124 | "requires": { 125 | "core-js": "^1.0.0", 126 | "isomorphic-fetch": "^2.1.1", 127 | "loose-envify": "^1.0.0", 128 | "object-assign": "^4.1.0", 129 | "promise": "^7.1.1", 130 | "setimmediate": "^1.0.5", 131 | "ua-parser-js": "^0.7.18" 132 | } 133 | }, 134 | "has-flag": { 135 | "version": "3.0.0", 136 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 137 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 138 | }, 139 | "iconv-lite": { 140 | "version": "0.4.24", 141 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 142 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 143 | "dev": true, 144 | "requires": { 145 | "safer-buffer": ">= 2.1.2 < 3" 146 | } 147 | }, 148 | "indent-string": { 149 | "version": "3.2.0", 150 | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", 151 | "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" 152 | }, 153 | "inherits": { 154 | "version": "2.0.3", 155 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 156 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 157 | }, 158 | "is-stream": { 159 | "version": "1.1.0", 160 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 161 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", 162 | "dev": true 163 | }, 164 | "isomorphic-fetch": { 165 | "version": "2.2.1", 166 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", 167 | "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", 168 | "dev": true, 169 | "requires": { 170 | "node-fetch": "^1.0.1", 171 | "whatwg-fetch": ">=0.10.0" 172 | } 173 | }, 174 | "jest-diff": { 175 | "version": "22.4.3", 176 | "resolved": "http://registry.npmjs.org/jest-diff/-/jest-diff-22.4.3.tgz", 177 | "integrity": "sha512-/QqGvCDP5oZOF6PebDuLwrB2BMD8ffJv6TAGAdEVuDx1+uEgrHpSFrfrOiMRx2eJ1hgNjlQrOQEHetVwij90KA==", 178 | "requires": { 179 | "chalk": "^2.0.1", 180 | "diff": "^3.2.0", 181 | "jest-get-type": "^22.4.3", 182 | "pretty-format": "^22.4.3" 183 | }, 184 | "dependencies": { 185 | "pretty-format": { 186 | "version": "22.4.3", 187 | "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-22.4.3.tgz", 188 | "integrity": "sha512-S4oT9/sT6MN7/3COoOy+ZJeA92VmOnveLHgrwBE3Z1W5N9S2A1QGNYiE1z75DAENbJrXXUb+OWXhpJcg05QKQQ==", 189 | "requires": { 190 | "ansi-regex": "^3.0.0", 191 | "ansi-styles": "^3.2.0" 192 | } 193 | } 194 | } 195 | }, 196 | "jest-dom": { 197 | "version": "1.12.1", 198 | "resolved": "https://registry.npmjs.org/jest-dom/-/jest-dom-1.12.1.tgz", 199 | "integrity": "sha512-QZGnuUT3sK6YNcJq6kyrK1PE1WrkYaaXtOJD4WrT6RIv6qyrUS76xYbWGUxXAZykcqhqaTmCSuCpn//0GDtgqA==", 200 | "requires": { 201 | "chalk": "^2.4.1", 202 | "css": "^2.2.3", 203 | "jest-diff": "^22.4.3", 204 | "jest-matcher-utils": "^22.4.3", 205 | "pretty-format": "^23.0.1", 206 | "redent": "^2.0.0" 207 | } 208 | }, 209 | "jest-get-type": { 210 | "version": "22.4.3", 211 | "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", 212 | "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==" 213 | }, 214 | "jest-matcher-utils": { 215 | "version": "22.4.3", 216 | "resolved": "http://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-22.4.3.tgz", 217 | "integrity": "sha512-lsEHVaTnKzdAPR5t4B6OcxXo9Vy4K+kRRbG5gtddY8lBEC+Mlpvm1CJcsMESRjzUhzkz568exMV1hTB76nAKbA==", 218 | "requires": { 219 | "chalk": "^2.0.1", 220 | "jest-get-type": "^22.4.3", 221 | "pretty-format": "^22.4.3" 222 | }, 223 | "dependencies": { 224 | "pretty-format": { 225 | "version": "22.4.3", 226 | "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-22.4.3.tgz", 227 | "integrity": "sha512-S4oT9/sT6MN7/3COoOy+ZJeA92VmOnveLHgrwBE3Z1W5N9S2A1QGNYiE1z75DAENbJrXXUb+OWXhpJcg05QKQQ==", 228 | "requires": { 229 | "ansi-regex": "^3.0.0", 230 | "ansi-styles": "^3.2.0" 231 | } 232 | } 233 | } 234 | }, 235 | "js-tokens": { 236 | "version": "4.0.0", 237 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 238 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 239 | "dev": true 240 | }, 241 | "loose-envify": { 242 | "version": "1.4.0", 243 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 244 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 245 | "dev": true, 246 | "requires": { 247 | "js-tokens": "^3.0.0 || ^4.0.0" 248 | } 249 | }, 250 | "node-fetch": { 251 | "version": "1.7.3", 252 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", 253 | "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", 254 | "dev": true, 255 | "requires": { 256 | "encoding": "^0.1.11", 257 | "is-stream": "^1.0.1" 258 | } 259 | }, 260 | "object-assign": { 261 | "version": "4.1.1", 262 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 263 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 264 | "dev": true 265 | }, 266 | "pretty-format": { 267 | "version": "23.6.0", 268 | "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", 269 | "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", 270 | "requires": { 271 | "ansi-regex": "^3.0.0", 272 | "ansi-styles": "^3.2.0" 273 | } 274 | }, 275 | "promise": { 276 | "version": "7.3.1", 277 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", 278 | "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", 279 | "dev": true, 280 | "requires": { 281 | "asap": "~2.0.3" 282 | } 283 | }, 284 | "prop-types": { 285 | "version": "15.6.2", 286 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", 287 | "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", 288 | "dev": true, 289 | "requires": { 290 | "loose-envify": "^1.3.1", 291 | "object-assign": "^4.1.1" 292 | } 293 | }, 294 | "react": { 295 | "version": "16.5.2", 296 | "resolved": "https://registry.npmjs.org/react/-/react-16.5.2.tgz", 297 | "integrity": "sha512-FDCSVd3DjVTmbEAjUNX6FgfAmQ+ypJfHUsqUJOYNCBUp1h8lqmtC+0mXJ+JjsWx4KAVTkk1vKd1hLQPvEviSuw==", 298 | "dev": true, 299 | "requires": { 300 | "loose-envify": "^1.1.0", 301 | "object-assign": "^4.1.1", 302 | "prop-types": "^15.6.2", 303 | "schedule": "^0.5.0" 304 | } 305 | }, 306 | "react-dom": { 307 | "version": "16.5.2", 308 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.5.2.tgz", 309 | "integrity": "sha512-RC8LDw8feuZOHVgzEf7f+cxBr/DnKdqp56VU0lAs1f4UfKc4cU8wU4fTq/mgnvynLQo8OtlPC19NUFh/zjZPuA==", 310 | "dev": true, 311 | "requires": { 312 | "loose-envify": "^1.1.0", 313 | "object-assign": "^4.1.1", 314 | "prop-types": "^15.6.2", 315 | "schedule": "^0.5.0" 316 | } 317 | }, 318 | "react-testing-library": { 319 | "version": "5.2.0", 320 | "resolved": "https://registry.npmjs.org/react-testing-library/-/react-testing-library-5.2.0.tgz", 321 | "integrity": "sha512-H0V3uM4dpxi/4Hy8nRwrT4i38hYLlyr5oIpUyzk6U1++NOP6XVYmuG8y8xoDsvxb8olN/iTLQzg2SWphR+uvyQ==", 322 | "requires": { 323 | "dom-testing-library": "^3.8.1", 324 | "wait-for-expect": "^1.0.0" 325 | } 326 | }, 327 | "redent": { 328 | "version": "2.0.0", 329 | "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", 330 | "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", 331 | "requires": { 332 | "indent-string": "^3.0.0", 333 | "strip-indent": "^2.0.0" 334 | } 335 | }, 336 | "resolve-url": { 337 | "version": "0.2.1", 338 | "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", 339 | "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" 340 | }, 341 | "safer-buffer": { 342 | "version": "2.1.2", 343 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 344 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 345 | "dev": true 346 | }, 347 | "schedule": { 348 | "version": "0.5.0", 349 | "resolved": "https://registry.npmjs.org/schedule/-/schedule-0.5.0.tgz", 350 | "integrity": "sha512-HUcJicG5Ou8xfR//c2rPT0lPIRR09vVvN81T9fqfVgBmhERUbDEQoYKjpBxbueJnCPpSu2ujXzOnRQt6x9o/jw==", 351 | "dev": true, 352 | "requires": { 353 | "object-assign": "^4.1.1" 354 | } 355 | }, 356 | "setimmediate": { 357 | "version": "1.0.5", 358 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 359 | "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", 360 | "dev": true 361 | }, 362 | "source-map": { 363 | "version": "0.6.1", 364 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 365 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 366 | }, 367 | "source-map-resolve": { 368 | "version": "0.5.2", 369 | "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", 370 | "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", 371 | "requires": { 372 | "atob": "^2.1.1", 373 | "decode-uri-component": "^0.2.0", 374 | "resolve-url": "^0.2.1", 375 | "source-map-url": "^0.4.0", 376 | "urix": "^0.1.0" 377 | } 378 | }, 379 | "source-map-url": { 380 | "version": "0.4.0", 381 | "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", 382 | "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" 383 | }, 384 | "strip-indent": { 385 | "version": "2.0.0", 386 | "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", 387 | "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=" 388 | }, 389 | "supports-color": { 390 | "version": "5.5.0", 391 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 392 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 393 | "requires": { 394 | "has-flag": "^3.0.0" 395 | } 396 | }, 397 | "ua-parser-js": { 398 | "version": "0.7.28", 399 | "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", 400 | "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==", 401 | "dev": true 402 | }, 403 | "urix": { 404 | "version": "0.1.0", 405 | "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", 406 | "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" 407 | }, 408 | "wait-for-expect": { 409 | "version": "1.0.1", 410 | "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-1.0.1.tgz", 411 | "integrity": "sha512-TPZMSxGWUl2DWmqdspLDEy97/S1Mqq0pzbh2A7jTq0WbJurUb5GKli+bai6ayeYdeWTF0rQNWZmUvCVZ9gkrfA==" 412 | }, 413 | "whatwg-fetch": { 414 | "version": "3.0.0", 415 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", 416 | "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==", 417 | "dev": true 418 | } 419 | } 420 | } 421 | -------------------------------------------------------------------------------- /test/react/16.5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "create-react-class": "^15.6.3", 5 | "react": "16.5", 6 | "react-dom": "16.5" 7 | }, 8 | "dependencies": { 9 | "jest-dom": "^1.12.0", 10 | "react-testing-library": "^5.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/react/16.6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "create-react-class": "^15.6.3", 5 | "react": "16.6", 6 | "react-dom": "16.6" 7 | }, 8 | "dependencies": { 9 | "jest-dom": "^1.12.0", 10 | "react-testing-library": "^5.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/react/16.8/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "create-react-class": "^15.6.3", 5 | "react": "16.8", 6 | "react-dom": "16.8" 7 | }, 8 | "dependencies": { 9 | "jest-dom": "^3.1.2", 10 | "react-testing-library": "^5.9.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/run-tests.js: -------------------------------------------------------------------------------- 1 | const npmRun = require('npm-run') 2 | const fs = require('fs') 3 | const path = require('path') 4 | const LATEST_VERSION = '16.8' 5 | const version = process.env.REACT || LATEST_VERSION 6 | 7 | let jestConfig = { 8 | testURL: 'http://localhost', 9 | collectCoverage: true, 10 | coverageDirectory: `${__dirname}/coverage`, 11 | transform: { 12 | '.js$': `${__dirname}/babel-transformer.jest.js` 13 | } 14 | } 15 | 16 | require('./install-test-deps.js') 17 | 18 | if (version.toLowerCase() === 'all') { 19 | jestConfig = { 20 | ...jestConfig, 21 | rootDir: __dirname, 22 | // every directory has the same coverage, so we collect it only from one 23 | collectCoverageFrom: [`react/${LATEST_VERSION}/src/**.js`] 24 | } 25 | } else { 26 | jestConfig = { 27 | ...jestConfig, 28 | rootDir: `${__dirname}/react/${version}` 29 | } 30 | } 31 | 32 | const configFilePath = path.join(__dirname, 'jest-config.json') 33 | 34 | fs.writeFileSync(configFilePath, JSON.stringify(jestConfig)) 35 | 36 | const commandLine = `jest -c "${configFilePath}" ${process.argv.slice(2).join(' ')}` 37 | 38 | npmRun.execSync( 39 | commandLine, 40 | { stdio: 'inherit' } 41 | ) 42 | -------------------------------------------------------------------------------- /test/utils/isPlainObject.spec.js: -------------------------------------------------------------------------------- 1 | import isPlainObject from '../../src/utils/isPlainObject' 2 | import vm from 'vm' 3 | 4 | describe('isPlainObject', () => { 5 | it('returns true only if plain object', () => { 6 | function Test() { 7 | this.prop = 1 8 | } 9 | 10 | const sandbox = { fromAnotherRealm: false } 11 | vm.runInNewContext('fromAnotherRealm = {}', sandbox) 12 | 13 | expect(isPlainObject(sandbox.fromAnotherRealm)).toBe(true) 14 | expect(isPlainObject(new Test())).toBe(false) 15 | expect(isPlainObject(new Date())).toBe(false) 16 | expect(isPlainObject([1, 2, 3])).toBe(false) 17 | expect(isPlainObject(null)).toBe(false) 18 | expect(isPlainObject()).toBe(false) 19 | expect(isPlainObject({ x: 1, y: 2 })).toBe(true) 20 | expect(isPlainObject(Object.create(null))).toBe(true) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /test/utils/shallowEqual.spec.js: -------------------------------------------------------------------------------- 1 | import shallowEqual from '../../src/utils/shallowEqual' 2 | 3 | describe('Utils', () => { 4 | describe('shallowEqual', () => { 5 | it('should return true if arguments fields are equal', () => { 6 | expect( 7 | shallowEqual({ a: 1, b: 2, c: undefined }, { a: 1, b: 2, c: undefined }) 8 | ).toBe(true) 9 | 10 | expect(shallowEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 })).toBe( 11 | true 12 | ) 13 | 14 | const o = {} 15 | expect(shallowEqual({ a: 1, b: 2, c: o }, { a: 1, b: 2, c: o })).toBe( 16 | true 17 | ) 18 | 19 | const d = function() { 20 | return 1 21 | } 22 | expect( 23 | shallowEqual({ a: 1, b: 2, c: o, d }, { a: 1, b: 2, c: o, d }) 24 | ).toBe(true) 25 | }) 26 | 27 | it('should return false if arguments fields are different function identities', () => { 28 | expect( 29 | shallowEqual( 30 | { 31 | a: 1, 32 | b: 2, 33 | d: function() { 34 | return 1 35 | } 36 | }, 37 | { 38 | a: 1, 39 | b: 2, 40 | d: function() { 41 | return 1 42 | } 43 | } 44 | ) 45 | ).toBe(false) 46 | }) 47 | 48 | it('should return false if first argument has too many keys', () => { 49 | expect(shallowEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 })).toBe(false) 50 | }) 51 | 52 | it('should return false if second argument has too many keys', () => { 53 | expect(shallowEqual({ a: 1, b: 2 }, { a: 1, b: 2, c: 3 })).toBe(false) 54 | }) 55 | 56 | it('should return false if arguments have different keys', () => { 57 | expect( 58 | shallowEqual( 59 | { a: 1, b: 2, c: undefined }, 60 | { a: 1, bb: 2, c: undefined } 61 | ) 62 | ).toBe(false) 63 | }) 64 | 65 | it('should compare two NaN values', () => { 66 | expect(shallowEqual(NaN, NaN)).toBe(true) 67 | }) 68 | 69 | it('should compare empty objects, with false', () => { 70 | expect(shallowEqual({}, false)).toBe(false) 71 | expect(shallowEqual(false, {})).toBe(false) 72 | expect(shallowEqual([], false)).toBe(false) 73 | expect(shallowEqual(false, [])).toBe(false) 74 | }) 75 | 76 | it('should compare two zero values', () => { 77 | expect(shallowEqual(0, 0)).toBe(true) 78 | }) 79 | }) 80 | }) 81 | -------------------------------------------------------------------------------- /test/utils/wrapActionCreators.spec.js: -------------------------------------------------------------------------------- 1 | import wrapActionCreators from '../../src/utils/wrapActionCreators' 2 | 3 | describe('Utils', () => { 4 | describe('wrapActionCreators', () => { 5 | it('should return a function that wraps argument in a call to bindActionCreators', () => { 6 | function dispatch(action) { 7 | return { 8 | dispatched: action 9 | } 10 | } 11 | 12 | const actionResult = { an: 'action' } 13 | 14 | const actionCreators = { 15 | action: () => actionResult 16 | } 17 | 18 | const wrapped = wrapActionCreators(actionCreators) 19 | expect(wrapped).toBeInstanceOf(Function) 20 | expect(() => wrapped(dispatch)).not.toThrow() 21 | expect(() => wrapped().action()).toThrow() 22 | 23 | const bound = wrapped(dispatch) 24 | expect(bound.action).not.toThrow() 25 | expect(bound.action().dispatched).toBe(actionResult) 26 | }) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | This website was created with [Docusaurus](https://docusaurus.io/). 2 | 3 | # What's In This Document 4 | 5 | * [Get Started in 5 Minutes](#get-started-in-5-minutes) 6 | * [Directory Structure](#directory-structure) 7 | * [Editing Content](#editing-content) 8 | * [Adding Content](#adding-content) 9 | * [Full Documentation](#full-documentation) 10 | 11 | # Get Started in 5 Minutes 12 | 13 | 1. Make sure all the dependencies for the website are installed: 14 | 15 | ```sh 16 | # Install dependencies 17 | $ yarn 18 | ``` 19 | 2. Run your dev server: 20 | 21 | ```sh 22 | # Start the site 23 | $ yarn start 24 | ``` 25 | 26 | ## Directory Structure 27 | 28 | Your project file structure should look something like this 29 | 30 | ``` 31 | my-docusaurus/ 32 | docs/ 33 | doc-1.md 34 | doc-2.md 35 | doc-3.md 36 | website/ 37 | blog/ 38 | 2016-3-11-oldest-post.md 39 | 2017-10-24-newest-post.md 40 | core/ 41 | node_modules/ 42 | pages/ 43 | static/ 44 | css/ 45 | img/ 46 | package.json 47 | sidebar.json 48 | siteConfig.js 49 | ``` 50 | 51 | # Editing Content 52 | 53 | ## Editing an existing docs page 54 | 55 | Edit docs by navigating to `docs/` and editing the corresponding document: 56 | 57 | `docs/doc-to-be-edited.md` 58 | 59 | ```markdown 60 | --- 61 | id: page-needs-edit 62 | title: This Doc Needs To Be Edited 63 | --- 64 | 65 | Edit me... 66 | ``` 67 | 68 | For more information about docs, click [here](https://docusaurus.io/docs/en/navigation) 69 | 70 | ## Editing an existing blog post 71 | 72 | Edit blog posts by navigating to `website/blog` and editing the corresponding post: 73 | 74 | `website/blog/post-to-be-edited.md` 75 | ```markdown 76 | --- 77 | id: post-needs-edit 78 | title: This Blog Post Needs To Be Edited 79 | --- 80 | 81 | Edit me... 82 | ``` 83 | 84 | For more information about blog posts, click [here](https://docusaurus.io/docs/en/adding-blog) 85 | 86 | # Adding Content 87 | 88 | ## Adding a new docs page to an existing sidebar 89 | 90 | 1. Create the doc as a new markdown file in `/docs`, example `docs/newly-created-doc.md`: 91 | 92 | ```md 93 | --- 94 | id: newly-created-doc 95 | title: This Doc Needs To Be Edited 96 | --- 97 | 98 | My new content here.. 99 | ``` 100 | 101 | 1. Refer to that doc's ID in an existing sidebar in `website/sidebar.json`: 102 | 103 | ```javascript 104 | // Add newly-created-doc to the Getting Started category of docs 105 | { 106 | "docs": { 107 | "Getting Started": [ 108 | "quick-start", 109 | "newly-created-doc" // new doc here 110 | ], 111 | ... 112 | }, 113 | ... 114 | } 115 | ``` 116 | 117 | For more information about adding new docs, click [here](https://docusaurus.io/docs/en/navigation) 118 | 119 | ## Adding a new blog post 120 | 121 | 1. Make sure there is a header link to your blog in `website/siteConfig.js`: 122 | 123 | `website/siteConfig.js` 124 | ```javascript 125 | headerLinks: [ 126 | ... 127 | { blog: true, label: 'Blog' }, 128 | ... 129 | ] 130 | ``` 131 | 132 | 2. Create the blog post with the format `YYYY-MM-DD-My-Blog-Post-Title.md` in `website/blog`: 133 | 134 | `website/blog/2018-05-21-New-Blog-Post.md` 135 | 136 | ```markdown 137 | --- 138 | author: Frank Li 139 | authorURL: https://twitter.com/foobarbaz 140 | authorFBID: 503283835 141 | title: New Blog Post 142 | --- 143 | 144 | Lorem Ipsum... 145 | ``` 146 | 147 | For more information about blog posts, click [here](https://docusaurus.io/docs/en/adding-blog) 148 | 149 | ## Adding items to your site's top navigation bar 150 | 151 | 1. Add links to docs, custom pages or external links by editing the headerLinks field of `website/siteConfig.js`: 152 | 153 | `website/siteConfig.js` 154 | ```javascript 155 | { 156 | headerLinks: [ 157 | ... 158 | /* you can add docs */ 159 | { doc: 'my-examples', label: 'Examples' }, 160 | /* you can add custom pages */ 161 | { page: 'help', label: 'Help' }, 162 | /* you can add external links */ 163 | { href: 'https://github.com/facebook/Docusaurus', label: 'GitHub' }, 164 | ... 165 | ], 166 | ... 167 | } 168 | ``` 169 | 170 | For more information about the navigation bar, click [here](https://docusaurus.io/docs/en/navigation) 171 | 172 | ## Adding custom pages 173 | 174 | 1. Docusaurus uses React components to build pages. The components are saved as .js files in `website/pages/en`: 175 | 1. If you want your page to show up in your navigation header, you will need to update `website/siteConfig.js` to add to the `headerLinks` element: 176 | 177 | `website/siteConfig.js` 178 | ```javascript 179 | { 180 | headerLinks: [ 181 | ... 182 | { page: 'my-new-custom-page', label: 'My New Custom Page' }, 183 | ... 184 | ], 185 | ... 186 | } 187 | ``` 188 | 189 | For more information about custom pages, click [here](https://docusaurus.io/docs/en/custom-pages). 190 | 191 | # Full Documentation 192 | 193 | Full documentation can be found on the [website](https://docusaurus.io/). 194 | -------------------------------------------------------------------------------- /website/_redirects: -------------------------------------------------------------------------------- 1 | /docs/using-react-redux/connect-extracting-data-with-mapStateToProps /using-react-redux/connect-mapstate 2 | /docs/using-react-redux/connect-extracting-data-with-mapstatetoprops /using-react-redux/connect-mapstate 3 | /docs/using-react-redux/connect-dispatching-actions-with-mapDispatchToProps /using-react-redux/connect-mapdispatch 4 | /docs/using-react-redux/connect-dispatching-actions-with-mapdispatchtoprops /using-react-redux/connect-mapdispatch 5 | 6 | /docs/* /:splat 7 | -------------------------------------------------------------------------------- /website/core/Footer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const React = require("react"); 9 | 10 | class Footer extends React.Component { 11 | docUrl(doc, language) { 12 | const baseUrl = this.props.config.baseUrl; 13 | return `${baseUrl}${language ? `${language}/` : ""}${doc}`; 14 | } 15 | 16 | pageUrl(doc, language) { 17 | const baseUrl = this.props.config.baseUrl; 18 | return baseUrl + (language ? `${language}/` : "") + doc; 19 | } 20 | 21 | render() { 22 | return ( 23 | 107 | ); 108 | } 109 | } 110 | 111 | module.exports = Footer; 112 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "docusaurus-start", 4 | "build": "docusaurus-build", 5 | "publish-gh-pages": "docusaurus-publish", 6 | "write-translations": "docusaurus-write-translations", 7 | "version": "docusaurus-version", 8 | "rename-version": "docusaurus-rename-version" 9 | }, 10 | "devDependencies": { 11 | "docusaurus": "^1.6.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /website/pages/en/404.js: -------------------------------------------------------------------------------- 1 | const React = require("react"); 2 | const siteConfig = require(`${process.cwd()}/siteConfig.js`); 3 | 4 | class ErrorPage extends React.Component { 5 | getTrackingScript() { 6 | if (!siteConfig.gaTrackingId) { 7 | return null; 8 | } 9 | 10 | return {__html:` 11 | ga('create', "${siteConfig.gaTrackingId}"); 12 | ga('send', { 13 | hitType: 'event', 14 | eventCategory: '404 Response', 15 | eventAction: window.location.href, 16 | eventLabel: document.referrer 17 | });` 18 | } 19 | } 20 | 21 | render() { 22 | const trackingScript = this.getTrackingScript(); 23 | 24 | return ( 25 |
26 | {trackingScript &&