├── .codesandbox └── ci.json ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml └── workflows │ ├── publish.yaml │ ├── size.yaml │ └── test.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .release-it.json ├── .vscode └── settings.json ├── .yarn └── releases │ └── yarn-4.4.1.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── CNAME ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── api-extractor.dt-types.json ├── api-extractor.json ├── codecov.yml ├── docs ├── api │ ├── Provider.md │ ├── batch.md │ ├── connect.md │ └── hooks.md ├── introduction │ ├── getting-started.md │ └── why-use-react-redux.md ├── troubleshooting.md ├── tutorials │ ├── connect.md │ ├── quick-start.md │ └── typescript.md └── using-react-redux │ ├── accessing-store.md │ ├── connect-dispatching-actions-with-mapDispatchToProps.md │ ├── connect-extracting-data-with-mapStateToProps.md │ └── usage-with-typescript.md ├── etc ├── react-redux.api.md └── react-redux.dt-types.api.md ├── examples └── publish-ci │ └── rr-rsc-context │ ├── .gitignore │ ├── README.md │ ├── app │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── page.module.css │ └── page.tsx │ ├── next.config.js │ ├── package.json │ ├── public │ ├── next.svg │ └── vercel.svg │ ├── tsconfig.json │ └── yarn.lock ├── netlify.toml ├── package.json ├── src ├── components │ ├── Context.ts │ ├── Provider.tsx │ └── connect.tsx ├── connect │ ├── invalidArgFactory.ts │ ├── mapDispatchToProps.ts │ ├── mapStateToProps.ts │ ├── mergeProps.ts │ ├── selectorFactory.ts │ ├── verifySubselectors.ts │ └── wrapMapToProps.ts ├── exports.ts ├── hooks │ ├── useDispatch.ts │ ├── useReduxContext.ts │ ├── useSelector.ts │ └── useStore.ts ├── index-rsc.ts ├── index.ts ├── types.ts └── utils │ ├── Subscription.ts │ ├── batch.ts │ ├── bindActionCreators.ts │ ├── hoistStatics.ts │ ├── isPlainObject.ts │ ├── react-is.ts │ ├── react.ts │ ├── shallowEqual.ts │ ├── useIsomorphicLayoutEffect.ts │ ├── useSyncExternalStore.ts │ ├── verifyPlainObject.ts │ └── warning.ts ├── test ├── components │ ├── Provider.spec.tsx │ ├── connect.spec.tsx │ └── hooks.spec.tsx ├── hooks │ ├── hooks.withTypes.test.tsx │ ├── useDispatch.spec.tsx │ ├── useReduxContext.spec.tsx │ └── useSelector.spec.tsx ├── integration │ ├── dynamic-reducers.spec.tsx │ ├── server-rendering.spec.tsx │ └── ssr.spec.tsx ├── setup.ts ├── typeTestHelpers.ts ├── typetests │ ├── connect-mapstate-mapdispatch.test-d.tsx │ ├── connect-options-and-issues.test-d.tsx │ ├── counterApp.ts │ ├── hooks.test-d.tsx │ ├── hooks.withTypes.test-d.tsx │ ├── provider.test-d.tsx │ └── react-redux-types.test-d.tsx └── utils │ ├── Subscription.spec.ts │ ├── isPlainObject.spec.ts │ └── shallowEqual.spec.ts ├── tsconfig.base.json ├── tsconfig.build.json ├── tsconfig.json ├── tsconfig.test.json ├── tsup.config.ts ├── vitest.config.mts ├── website ├── .gitignore ├── README.md ├── _redirects ├── docusaurus.config.ts ├── package.json ├── sidebars.ts ├── src │ ├── pages │ │ ├── index.js │ │ └── styles.module.css │ └── theme │ │ └── NotFound.js ├── static │ ├── css │ │ ├── 404.css │ │ ├── codeblock.css │ │ └── custom.css │ ├── img │ │ ├── course-callout-mid.svg │ │ ├── course-callout-narrow.svg │ │ ├── course-callout-wide.svg │ │ ├── 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 │ │ ├── monokaiTheme.js │ │ └── sidebarScroll.js ├── versioned_docs │ ├── version-5.x │ │ ├── api.md │ │ ├── api │ │ │ ├── Provider.md │ │ │ ├── api.md │ │ │ ├── connect-advanced.md │ │ │ └── connect.md │ │ ├── introduction │ │ │ ├── basic-tutorial.md │ │ │ ├── quick-start.md │ │ │ └── why-use-react-redux.md │ │ ├── troubleshooting.md │ │ └── using-react-redux │ │ │ ├── connect-dispatching-actions-with-mapDispatchToProps.md │ │ │ └── connect-extracting-data-with-mapStateToProps.md │ ├── version-6.x │ │ ├── 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 │ ├── version-7.0 │ │ ├── api │ │ │ ├── Provider.md │ │ │ ├── batch.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 │ ├── version-7.1 │ │ ├── api │ │ │ ├── Provider.md │ │ │ ├── batch.md │ │ │ ├── connect-advanced.md │ │ │ ├── connect.md │ │ │ └── hooks.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 │ │ │ └── static-types.md │ └── version-7.2 │ │ ├── api │ │ ├── .DS_Store │ │ ├── Provider.md │ │ ├── batch.md │ │ ├── connect-advanced.md │ │ ├── connect.md │ │ └── hooks.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 │ │ └── static-types.md ├── versioned_sidebars │ ├── version-5.x-sidebars.json │ ├── version-6.x-sidebars.json │ ├── version-7.0-sidebars.json │ ├── version-7.1-sidebars.json │ └── version-7.2-sidebars.json └── yarn.lock └── yarn.lock /.codesandbox/ci.json: -------------------------------------------------------------------------------- 1 | { 2 | "sandboxes": ["vanilla", "vanilla-ts"], 3 | "node": "20", 4 | "buildCommand": "build", 5 | "packages": ["."] 6 | } 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/eslint-recommended", 9 | "plugin:import/recommended", 10 | "plugin:react/recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaFeatures": { "jsx": true }, 16 | "ecmaVersion": 2015, 17 | "project": true, 18 | "sourceType": "module" 19 | }, 20 | "plugins": ["@typescript-eslint", "import", "react"], 21 | "rules": { 22 | "valid-jsdoc": [ 23 | 2, 24 | { "requireReturnType": false, "requireParamType": false } 25 | ], 26 | "react/no-is-mounted": [0] 27 | }, 28 | "settings": { 29 | "import/ignore": ["react-native"], 30 | "import/resolver": { 31 | "typescript": { "extensions": [".js", ".ts", ".tsx"] } 32 | }, 33 | "react": { "version": "detect" } 34 | }, 35 | "overrides": [ 36 | { 37 | "files": ["**/*.{ts,tsx,cts}"], 38 | "extends": [ 39 | "eslint:recommended", 40 | "plugin:import/recommended", 41 | "plugin:react/recommended", 42 | "plugin:react/jsx-runtime", 43 | "plugin:@typescript-eslint/recommended" 44 | ], 45 | "rules": { 46 | "@typescript-eslint/ban-ts-comment": [0], 47 | "@typescript-eslint/no-redeclare": [2], 48 | "@typescript-eslint/no-explicit-any": [0], 49 | "@typescript-eslint/no-unused-vars": [0], 50 | "@typescript-eslint/ban-types": [0], 51 | "react/display-name": [0], 52 | "react/jsx-no-undef": [2], 53 | "react/jsx-uses-react": [1], 54 | "react/jsx-wrap-multilines": [2], 55 | "react/no-string-refs": [0], 56 | "@typescript-eslint/consistent-type-imports": [ 57 | 2, 58 | { "fixStyle": "separate-type-imports" } 59 | ], 60 | "@typescript-eslint/consistent-type-exports": [2], 61 | "valid-jsdoc": [ 62 | 2, 63 | { "requireReturnType": false, "requireParamType": false } 64 | ], 65 | "react/no-is-mounted": [0] 66 | } 67 | }, 68 | { 69 | "files": ["**/test/**/*.{ts,tsx}"], 70 | "parserOptions": { "project": true } 71 | } 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [markerikson, timdorr, phryneas] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug Report 2 | description: Something isn't working correctly. 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thank you for contributing to open source! 8 | 9 | Do you need some help? 10 | ====================== 11 | 12 | The issue tracker is meant for bug reports only. This isn't the best place for support or usage questions. Questions here don't have as much visibility as they do elsewhere. Before you ask a question, here are some resources to get help first: 13 | 14 | - Read the docs: https://react-redux.js.org/ 15 | - Check out the troubleshooting guide: https://react-redux.js.org/troubleshooting 16 | - Look for/ask questions on Stack Overflow: https://stackoverflow.com/questions/tagged/redux 17 | - Ask in chat: https://www.reactiflux.com/ 18 | 19 | Think you found a bug? 20 | ====================== 21 | The best bug report is a failing test in the repository as a pull request. Otherwise, please use the form below. 22 | - type: textarea 23 | attributes: 24 | label: What version of React, ReactDOM/React Native, Redux, and React Redux are you using? 25 | value: | 26 | - React: 27 | - ReactDOM/React Native: 28 | - Redux: 29 | - React Redux: 30 | validations: 31 | required: true 32 | - type: textarea 33 | attributes: 34 | label: What is the current behavior? 35 | description: | 36 | 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 37 | validations: 38 | required: true 39 | - type: textarea 40 | attributes: 41 | label: What is the expected behavior? 42 | validations: 43 | required: true 44 | - type: input 45 | attributes: 46 | label: Which browser and OS are affected by this issue? 47 | validations: 48 | required: false 49 | - type: checkboxes 50 | attributes: 51 | label: Did this work in previous versions of React Redux? 52 | options: 53 | - label: 'Yes' 54 | validations: 55 | required: false 56 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🤔 Questions and Help 4 | url: https://redux.js.org/introduction/getting-started#help-and-discussion 5 | about: This is a bug tracker, not a support system. For usage questions, please use our support resources. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 👍 Feature Request 2 | description: I'd like React Redux to do something new. 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thank you for contributing to open source! 8 | 9 | Do you need some help? 10 | ====================== 11 | 12 | The issue tracker is meant for bug reports only. This isn't the best place for support or usage questions. Questions here don't have as much visibility as they do elsewhere. Before you ask a question, here are some resources to get help first: 13 | 14 | - Read the docs: https://react-redux.js.org/ 15 | - Check out the troubleshooting guide: https://react-redux.js.org/troubleshooting 16 | - Look for/ask questions on Stack Overflow: https://stackoverflow.com/questions/tagged/redux 17 | - Ask in chat: https://www.reactiflux.com/ 18 | - type: textarea 19 | attributes: 20 | label: What is the new or updated feature that you are suggesting? 21 | description: | 22 | Please provide thoughtful commentary *and code samples* on what this feature means for your product. What will it allow you to do that you can't do today? How will it make current work-arounds straightforward? What potential bugs and edge cases does it help to avoid? etc. Please keep it product-centric. 23 | validations: 24 | required: true 25 | - type: textarea 26 | attributes: 27 | label: Why should this feature be included? 28 | validations: 29 | required: true 30 | - type: textarea 31 | attributes: 32 | label: What docs changes are needed to explain this? 33 | validations: 34 | required: true 35 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npmjs 2 | on: 3 | # keeping it purely manual for now as to not accidentally trigger a release 4 | #release: 5 | # types: [published] 6 | workflow_dispatch: 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | id-token: write 12 | contents: read 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: '22.x' 18 | registry-url: 'https://registry.npmjs.org' 19 | cache: 'yarn' 20 | - run: yarn install --frozen-lockfile 21 | - run: yarn test 22 | - run: npm publish --access public --provenance 23 | env: 24 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 25 | -------------------------------------------------------------------------------- /.github/workflows/size.yaml: -------------------------------------------------------------------------------- 1 | name: Bundle Size 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Check compressed size 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 1 14 | - uses: preactjs/compressed-size-action@v2 15 | with: 16 | repo-token: '${{ secrets.GITHUB_TOKEN }}' 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | lib 4 | coverage 5 | es 6 | temp/ 7 | react-redux-*/ 8 | redux-toolkit/ 9 | .vscode/ 10 | 11 | .cache/ 12 | .yarn/cache/ 13 | website/.yarn/ 14 | .yarnrc 15 | .yarn/* 16 | !.yarn/patches 17 | !.yarn/releases 18 | !.yarn/plugins 19 | !.yarn/sdks 20 | !.yarn/versions 21 | .pnp.* 22 | *.tgz 23 | 24 | .yalc 25 | yalc.lock 26 | yalc.sig 27 | 28 | lib/core/metadata.js 29 | lib/core/MetadataBlog.js 30 | 31 | website/translated_docs 32 | website/build/ 33 | website/node_modules 34 | website/i18n/* 35 | 36 | tsconfig.vitest-temp.json 37 | 38 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | build*/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "tabWidth": 2 5 | } 6 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "after:bump": "yarn && git add -u" 4 | }, 5 | "git": { 6 | "tagName": "v${version}" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: false 4 | 5 | nodeLinker: node-modules 6 | 7 | yarnPath: .yarn/releases/yarn-4.4.1.cjs 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | All notable changes are described on the [Releases](https://github.com/reduxjs/react-redux/releases) page. 2 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | react-redux.js.org 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 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). 4 | 5 | Please review the [Redux Style Guide](https://redux.js.org/style-guide/style-guide) in the Redux docs to keep track of our best practices. 6 | 7 | ## Reporting Issues and Asking Questions 8 | 9 | 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. 10 | 11 | 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. 12 | 13 | ## Development 14 | 15 | Visit the [Issue tracker](https://github.com/reduxjs/react-redux/issues) to find a list of open issues that need attention. 16 | 17 | Fork, then clone the repo: 18 | 19 | ``` 20 | git clone https://github.com/your-username/react-redux.git 21 | ``` 22 | 23 | This repository uses Yarn v2 to manage packages. You'll need to have Yarn v1.22 installed globally on your system first, as Yarn v2 depends on that being available first. Install dependencies with: 24 | 25 | ``` 26 | yarn install 27 | ``` 28 | 29 | ### Building 30 | 31 | Running the `build` task will create both a CommonJS module-per-module build and a UMD build. 32 | 33 | ``` 34 | yarn build 35 | ``` 36 | 37 | To create just a CommonJS module-per-module build: 38 | 39 | ``` 40 | yarn build:lib 41 | ``` 42 | 43 | To create just a UMD build: 44 | 45 | ``` 46 | yarn build:umd 47 | yarn build:umd:min 48 | ``` 49 | 50 | ### Testing and Linting 51 | 52 | To run the tests: 53 | 54 | ``` 55 | yarn test 56 | ``` 57 | 58 | To continuously watch and run tests, run the following: 59 | 60 | ``` 61 | yarn test --watch 62 | ``` 63 | 64 | To perform linting with `eslint`, run the following: 65 | 66 | ``` 67 | yarn lint 68 | ``` 69 | 70 | ### New Features 71 | 72 | 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. 73 | 74 | ## Submitting Changes 75 | 76 | - Open a new issue in the [Issue tracker](https://github.com/reduxjs/react-redux/issues). 77 | - Fork the repo. 78 | - Create a new feature branch based off the `master` branch. 79 | - Make sure all tests pass and there are no linting errors. 80 | - Submit a pull request, referencing any issues it addresses. 81 | 82 | Please try to keep your pull request focused in scope and avoid including unrelated commits. 83 | 84 | 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. 85 | 86 | Thank you for contributing! 87 | -------------------------------------------------------------------------------- /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 2 | 3 | Official React bindings for [Redux](https://github.com/reduxjs/redux). 4 | Performant and flexible. 5 | 6 | ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/reduxjs/react-redux/test.yml?style=flat-square) [![npm version](https://img.shields.io/npm/v/react-redux.svg?style=flat-square)](https://www.npmjs.com/package/react-redux) 7 | [![npm downloads](https://img.shields.io/npm/dm/react-redux.svg?style=flat-square)](https://www.npmjs.com/package/react-redux) 8 | [![#redux channel on Discord](https://img.shields.io/badge/discord-redux@reactiflux-61DAFB.svg?style=flat-square)](http://www.reactiflux.com) 9 | 10 | ## Installation 11 | 12 | ### Create a React Redux App 13 | 14 | The recommended way to start new apps with React and Redux is by using [our official Redux+TS template for Vite](https://github.com/reduxjs/redux-templates), or by creating a new Next.js project using [Next's `with-redux` template](https://github.com/vercel/next.js/tree/canary/examples/with-redux). 15 | 16 | Both of these already have Redux Toolkit and React-Redux configured appropriately for that build tool, and come with a small example app that demonstrates how to use several of Redux Toolkit's features. 17 | 18 | ```bash 19 | # Vite with our Redux+TS template 20 | # (using the `degit` tool to clone and extract the template) 21 | npx degit reduxjs/redux-templates/packages/vite-template-redux my-app 22 | 23 | # Next.js using the `with-redux` template 24 | npx create-next-app --example with-redux my-app 25 | ``` 26 | 27 | ### An Existing React App 28 | 29 | React Redux 8.0 requires **React 16.8.3 or later** (or React Native 0.59 or later). 30 | 31 | To use React Redux with your React app, install it as a dependency: 32 | 33 | ```bash 34 | # If you use npm: 35 | npm install react-redux 36 | 37 | # Or if you use Yarn: 38 | yarn add react-redux 39 | ``` 40 | 41 | You'll also need to [install Redux](https://redux.js.org/introduction/installation) and [set up a Redux store](https://redux.js.org/recipes/configuring-your-store/) in your app. 42 | 43 | This assumes that you’re using [npm](http://npmjs.com/) package manager 44 | with a module bundler like [Webpack](https://webpack.js.org/) or 45 | [Browserify](http://browserify.org/) to consume [CommonJS 46 | modules](https://webpack.js.org/api/module-methods/#commonjs). 47 | 48 | If you don’t yet use [npm](http://npmjs.com/) or a modern module bundler, and would rather prefer a single-file [UMD](https://github.com/umdjs/umd) build that makes `ReactRedux` available as a global object, you can grab a pre-built version from [cdnjs](https://cdnjs.com/libraries/react-redux). We _don’t_ recommend this approach for any serious application, as most of the libraries complementary to Redux are only available on [npm](http://npmjs.com/). 49 | 50 | ## Documentation 51 | 52 | The React Redux docs are published at **https://react-redux.js.org** . 53 | 54 | ## How Does It Work? 55 | 56 | The post [The History and Implementation of React-Redux](https://blog.isquaredsoftware.com/2018/11/react-redux-history-implementation/) 57 | explains what it does, how it works, and how the API and implementation have evolved over time. 58 | 59 | There's also a [Deep Dive into React-Redux](https://blog.isquaredsoftware.com/2019/06/presentation-react-redux-deep-dive/) talk that covers some of the same material at a higher level. 60 | 61 | ## License 62 | 63 | [MIT](LICENSE.md) 64 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: off 2 | coverage: 3 | status: 4 | project: 5 | default: 6 | threshold: 5% 7 | patch: off 8 | -------------------------------------------------------------------------------- /docs/api/Provider.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: provider 3 | title: Provider 4 | sidebar_label: Provider 5 | hide_title: true 6 | description: 'API > Provider: providing the Redux store to your React app' 7 | --- 8 | 9 |   10 | 11 | # `Provider` 12 | 13 | ## Overview 14 | 15 | The `` component makes the Redux `store` available to any nested components that need to access the Redux store. 16 | 17 | Since any React component in a React Redux app can be connected to the store, most applications will render a `` at the top level, with the entire app’s component tree inside of it. 18 | 19 | The [Hooks](./hooks.md) and [`connect`](./connect.md) APIs can then access the provided store instance via React's Context mechanism. 20 | 21 | ### Props 22 | 23 | ```ts 24 | interface ProviderProps { 25 | /** 26 | * The single Redux store in your application. 27 | */ 28 | store: Store 29 | 30 | /** 31 | * An optional server state snapshot. Will be used during initial hydration render 32 | * if available, to ensure that the UI output is consistent with the HTML generated on the server. 33 | * New in 8.0 34 | */ 35 | serverState?: S 36 | 37 | /** 38 | * Optional context to be used internally in react-redux. Use React.createContext() 39 | * to create a context to be used. 40 | * If this is used, you'll need to customize `connect` by supplying the same 41 | * context provided to the Provider. 42 | * Set the initial value to null, and the hooks will error 43 | * if this is not overwritten by Provider. 44 | */ 45 | context?: Context | null> 46 | 47 | /** Global configuration for the `useSelector` stability check */ 48 | stabilityCheck?: StabilityCheck 49 | 50 | /** The top-level React elements in your component tree, such as `` **/ 51 | children: ReactNode 52 | } 53 | ``` 54 | 55 | Typically, you only need to pass ``. 56 | 57 | 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 this runtime error: 58 | 59 | > Invariant Violation 60 | > 61 | > 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. 62 | 63 | ## React 18 SSR Usage 64 | 65 | As of React-Redux v8, `` now accepts a `serverState` prop for use in SSR hydration scenarios. This is necessary if you are calling `hydrateRoot` in order to avoid hydration mismatches. 66 | 67 | You should pass the entire serialized state from the server as the `serverState` prop, and React will use this state for the initial hydration render. After that, it will apply any updates from changes that occurred on the client during the setup process. 68 | 69 | ## Examples 70 | 71 | ### Basic Usage 72 | 73 | In the example below, the `` component is our root-level component. This means it’s at the very top of our component hierarchy. 74 | 75 | ```jsx 76 | import React from 'react' 77 | import ReactDOM from 'react-dom' 78 | import { Provider } from 'react-redux' 79 | 80 | import { App } from './App' 81 | import createStore from './createReduxStore' 82 | 83 | const store = createStore() 84 | 85 | // As of React 18 86 | const root = ReactDOM.createRoot(document.getElementById('root')) 87 | root.render( 88 | 89 | 90 | , 91 | ) 92 | ``` 93 | 94 | ### React 18 SSR Hydration 95 | 96 | In this example, the client has received HTML rendered by the server, as well as a serialized Redux state attached to `window`. The serialized state is used to both pre-fill the store's contents, _and_ passed as the `serverState` prop to `` 97 | 98 | ```tsx title="src/index.ts" 99 | import { hydrateRoot } from 'react-dom/client' 100 | import { configureStore } from '@reduxjs/toolkit' 101 | import { Provider } from 'react-redux' 102 | 103 | const preloadedState = window.__PRELOADED_STATE__ 104 | 105 | const clientStore = configureStore({ 106 | reducer: rootReducer, 107 | preloadedState, 108 | }) 109 | 110 | hydrateRoot( 111 | document.getElementById('root'), 112 | 113 | 114 | , 115 | ) 116 | ``` 117 | -------------------------------------------------------------------------------- /docs/api/batch.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: batch 3 | title: batch 4 | sidebar_label: batch() 5 | hide_title: true 6 | description: 'API > batch: batching React rendering updates' 7 | --- 8 | 9 |   10 | 11 | # `batch()` 12 | 13 | ```js 14 | batch((fn: () => void)) 15 | ``` 16 | 17 | _added in v7.0.0_ 18 | 19 | :::info 20 | 21 | **If you're using React 18, you do not need to use the `batch` API**. React 18 automatically batches _all_ state updates, no matter where they're queued. 22 | 23 | ::: 24 | 25 | React's `unstable_batchedUpdates()` API allows any React updates in an event loop tick to be batched together into a single render pass. React already uses this internally for its own event handler callbacks. This API is actually part of the renderer packages like ReactDOM and React Native, not the React core itself. 26 | 27 | Since React-Redux needs to work in both ReactDOM and React Native environments, we've taken care of importing this API from the correct renderer at build time for our own use. We also now re-export this function publicly ourselves, renamed to `batch()`. You can use it to ensure that multiple actions dispatched outside of React only result in a single render update, like this: 28 | 29 | ```ts 30 | import { batch } from 'react-redux' 31 | 32 | function myThunk() { 33 | return (dispatch, getState) => { 34 | // should only result in one combined re-render, not two 35 | batch(() => { 36 | dispatch(increment()) 37 | dispatch(increment()) 38 | }) 39 | } 40 | } 41 | ``` 42 | 43 | ## References 44 | 45 | - [`unstable_batchedUpdates()` API from React](https://github.com/facebook/react/commit/b41883fc708cd24d77dcaa767cde814b50b457fe) 46 | - [React 18 Working Group: Automatic Batching for Fewer Renders in React 18](https://github.com/reactwg/react-18/discussions/21) 47 | -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: troubleshooting 3 | title: Troubleshooting 4 | sidebar_label: Troubleshooting 5 | hide_title: true 6 | --- 7 | 8 |   9 | 10 | ## Troubleshooting 11 | 12 | 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! 13 | 14 | You can also ask questions on [Stack Overflow](https://stackoverflow.com) using the **[#redux tag](https://stackoverflow.com/questions/tagged/redux)**. 15 | 16 | ### My views aren’t updating! 17 | 18 | In short, 19 | 20 | - Reducers should never mutate state, they must return new objects, or React Redux won’t see the updates. 21 | - Make sure you are actually _dispatching_ actions. For example, if you have an action creator like `addTodo`, just calling the imported `addTodo()` function by itself won't do anything because it just _returns_ an action, but does not _dispatch_ it. You either need to call `dispatch(addTodo())` (if using the hooks API) or `props.addTodo()` (if using `connect` + `mapDispatch`). 22 | 23 | ### Could not find "store" in either the context or props 24 | 25 | If you have context issues, 26 | 27 | 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. 28 | 2. Make sure you don't have multiple instances/copies of React Redux in your project. 29 | 3. Make sure you didn’t forget to wrap your root or some other ancestor component in [``](#provider-store). 30 | 4. Make sure you’re running the latest versions of React and React Redux. 31 | 32 | ### 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 33 | 34 | 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. 35 | 36 | ### I'm getting a warning about useLayoutEffect in my unit tests 37 | 38 | ReactDOM emits this warning if `useLayoutEffect` is used "on the server". React Redux tries to get around the issue by detecting whether it is running within a browser context. Jest, by default, defines enough of the browser environment that React Redux thinks it's running in a browser, causing these warnings. 39 | 40 | You can prevent the warning by setting the `@jest-environment` for a single test file: 41 | 42 | ```jsx 43 | // my.test.jsx 44 | /** 45 | * @jest-environment node 46 | */ 47 | ``` 48 | 49 | Or by setting it globally: 50 | 51 | ```js 52 | // package.json 53 | { 54 | "name": "my-project", 55 | "jest": { 56 | "testEnvironment": "node" 57 | } 58 | } 59 | ``` 60 | 61 | See https://github.com/facebook/react/issues/14927#issuecomment-490426131 62 | -------------------------------------------------------------------------------- /examples/publish-ci/rr-rsc-context/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | 37 | 38 | .cache/ 39 | .yarn/cache/ 40 | website/.yarn/ 41 | .yarnrc 42 | .yarn/* 43 | !.yarn/patches 44 | !.yarn/releases 45 | !.yarn/plugins 46 | !.yarn/sdks 47 | !.yarn/versions 48 | .pnp.* 49 | *.tgz -------------------------------------------------------------------------------- /examples/publish-ci/rr-rsc-context/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 18 | 19 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /examples/publish-ci/rr-rsc-context/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reduxjs/react-redux/a8d654d2af7d1ac76724c4c8e38c37d5440a4f47/examples/publish-ci/rr-rsc-context/app/favicon.ico -------------------------------------------------------------------------------- /examples/publish-ci/rr-rsc-context/app/globals.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --max-width: 1100px; 3 | --border-radius: 12px; 4 | --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', 5 | 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', 6 | 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; 7 | 8 | --foreground-rgb: 0, 0, 0; 9 | --background-start-rgb: 214, 219, 220; 10 | --background-end-rgb: 255, 255, 255; 11 | 12 | --primary-glow: conic-gradient( 13 | from 180deg at 50% 50%, 14 | #16abff33 0deg, 15 | #0885ff33 55deg, 16 | #54d6ff33 120deg, 17 | #0071ff33 160deg, 18 | transparent 360deg 19 | ); 20 | --secondary-glow: radial-gradient( 21 | rgba(255, 255, 255, 1), 22 | rgba(255, 255, 255, 0) 23 | ); 24 | 25 | --tile-start-rgb: 239, 245, 249; 26 | --tile-end-rgb: 228, 232, 233; 27 | --tile-border: conic-gradient( 28 | #00000080, 29 | #00000040, 30 | #00000030, 31 | #00000020, 32 | #00000010, 33 | #00000010, 34 | #00000080 35 | ); 36 | 37 | --callout-rgb: 238, 240, 241; 38 | --callout-border-rgb: 172, 175, 176; 39 | --card-rgb: 180, 185, 188; 40 | --card-border-rgb: 131, 134, 135; 41 | } 42 | 43 | @media (prefers-color-scheme: dark) { 44 | :root { 45 | --foreground-rgb: 255, 255, 255; 46 | --background-start-rgb: 0, 0, 0; 47 | --background-end-rgb: 0, 0, 0; 48 | 49 | --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); 50 | --secondary-glow: linear-gradient( 51 | to bottom right, 52 | rgba(1, 65, 255, 0), 53 | rgba(1, 65, 255, 0), 54 | rgba(1, 65, 255, 0.3) 55 | ); 56 | 57 | --tile-start-rgb: 2, 13, 46; 58 | --tile-end-rgb: 2, 5, 19; 59 | --tile-border: conic-gradient( 60 | #ffffff80, 61 | #ffffff40, 62 | #ffffff30, 63 | #ffffff20, 64 | #ffffff10, 65 | #ffffff10, 66 | #ffffff80 67 | ); 68 | 69 | --callout-rgb: 20, 20, 20; 70 | --callout-border-rgb: 108, 108, 108; 71 | --card-rgb: 100, 100, 100; 72 | --card-border-rgb: 200, 200, 200; 73 | } 74 | } 75 | 76 | * { 77 | box-sizing: border-box; 78 | padding: 0; 79 | margin: 0; 80 | } 81 | 82 | html, 83 | body { 84 | max-width: 100vw; 85 | overflow-x: hidden; 86 | } 87 | 88 | body { 89 | color: rgb(var(--foreground-rgb)); 90 | background: linear-gradient( 91 | to bottom, 92 | transparent, 93 | rgb(var(--background-end-rgb)) 94 | ) 95 | rgb(var(--background-start-rgb)); 96 | } 97 | 98 | a { 99 | color: inherit; 100 | text-decoration: none; 101 | } 102 | 103 | @media (prefers-color-scheme: dark) { 104 | html { 105 | color-scheme: dark; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /examples/publish-ci/rr-rsc-context/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css' 2 | import { Inter } from 'next/font/google' 3 | import React from 'react' 4 | 5 | const inter = Inter({ subsets: ['latin'] }) 6 | 7 | export const metadata = { 8 | title: 'Create Next App', 9 | description: 'Generated by create next app', 10 | } 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: { 15 | children: React.ReactNode 16 | }) { 17 | return ( 18 | 19 | {children} 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /examples/publish-ci/rr-rsc-context/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | import styles from './page.module.css' 3 | import React from 'react' 4 | import { useSelector } from 'react-redux' 5 | console.log(useSelector) 6 | 7 | export default function Home() { 8 | return ( 9 |
10 |
11 |

12 | Get started by editing  13 | app/page.tsx 14 |

15 |
16 | 21 | By{' '} 22 | Vercel Logo 30 | 31 |
32 |
33 | 34 |
35 | Next.js Logo 43 |
44 | 45 |
46 | 52 |

53 | Docs -> 54 |

55 |

Find in-depth information about Next.js features and API.

56 |
57 | 58 | 64 |

65 | Learn -> 66 |

67 |

Learn about Next.js in an interactive course with quizzes!

68 |
69 | 70 | 76 |

77 | Templates -> 78 |

79 |

Explore the Next.js 13 playground.

80 |
81 | 82 | 88 |

89 | Deploy -> 90 |

91 |

92 | Instantly deploy your Next.js site to a shareable URL with Vercel. 93 |

94 |
95 |
96 |
97 | ) 98 | } 99 | -------------------------------------------------------------------------------- /examples/publish-ci/rr-rsc-context/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /examples/publish-ci/rr-rsc-context/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rr-rsc-context", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@types/node": "20.3.0", 13 | "@types/react": "18.2.12", 14 | "@types/react-dom": "18.2.5", 15 | "next": "13.4.5", 16 | "react": "18.2.0", 17 | "react-dom": "18.2.0", 18 | "react-redux": "^8.0.7", 19 | "typescript": "5.1.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/publish-ci/rr-rsc-context/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/publish-ci/rr-rsc-context/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/publish-ci/rr-rsc-context/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | base = "website" 3 | publish = "website/build" 4 | command = "yarn && yarn build && cp _redirects ./build" 5 | ignore = "git diff --quiet HEAD^ HEAD -- ../docs/ . ../netlify.toml" 6 | 7 | [build.environment] 8 | NODE_VERSION = "20" 9 | NODE_OPTIONS = "--max_old_space_size=4096" 10 | NETLIFY_USE_YARN = "true" 11 | YARN_VERSION = "1.22.10" 12 | 13 | 14 | [[plugins]] 15 | package = "netlify-plugin-cache" 16 | [plugins.inputs] 17 | paths = [ 18 | "node_modules/.cache", 19 | "website/node_modules/.cache", 20 | ".yarn/.cache" 21 | ] 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux", 3 | "version": "9.2.0", 4 | "description": "Official React bindings for Redux", 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/reduxjs/react-redux", 13 | "repository": "github:reduxjs/react-redux", 14 | "bugs": "https://github.com/reduxjs/react-redux/issues", 15 | "module": "dist/react-redux.legacy-esm.js", 16 | "main": "dist/cjs/index.js", 17 | "react-native": "./dist/react-redux.legacy-esm.js", 18 | "types": "dist/react-redux.d.ts", 19 | "exports": { 20 | "./package.json": "./package.json", 21 | ".": { 22 | "types": "./dist/react-redux.d.ts", 23 | "react-server": "./dist/rsc.mjs", 24 | "react-native": "./dist/react-redux.legacy-esm.js", 25 | "import": "./dist/react-redux.mjs", 26 | "default": "./dist/cjs/index.js" 27 | }, 28 | "./alternate-renderers": { 29 | "types": "./dist/react-redux.d.ts", 30 | "import": "./dist/react-redux.mjs", 31 | "default": "./dist/cjs/index.js" 32 | } 33 | }, 34 | "sideEffects": false, 35 | "files": [ 36 | "dist", 37 | "src" 38 | ], 39 | "scripts": { 40 | "build": "yarn clean && tsup", 41 | "clean": "rimraf lib dist es coverage", 42 | "api-types": "api-extractor run --local", 43 | "format": "prettier --write \"{src,test}/**/*.{js,ts,tsx}\" \"docs/**/*.md\"", 44 | "lint": "eslint src test", 45 | "lint:fix": "eslint src test --fix", 46 | "prepack": "yarn build", 47 | "pretest": "yarn lint", 48 | "test": "vitest --run --typecheck", 49 | "test:watch": "vitest --watch", 50 | "type-tests": "tsc --noEmit -p tsconfig.test.json", 51 | "coverage": "codecov" 52 | }, 53 | "peerDependencies": { 54 | "@types/react": "^18.2.25 || ^19", 55 | "react": "^18.0 || ^19", 56 | "redux": "^5.0.0" 57 | }, 58 | "peerDependenciesMeta": { 59 | "@types/react": { 60 | "optional": true 61 | }, 62 | "redux": { 63 | "optional": true 64 | } 65 | }, 66 | "dependencies": { 67 | "@types/use-sync-external-store": "^0.0.6", 68 | "use-sync-external-store": "^1.4.0" 69 | }, 70 | "devDependencies": { 71 | "@microsoft/api-extractor": "^7.47.0", 72 | "@reduxjs/toolkit": "^2.2.5", 73 | "@testing-library/dom": "^10.4.0", 74 | "@testing-library/jest-dom": "^6.6.3", 75 | "@testing-library/react": "^16.1.0", 76 | "@types/node": "^20.14.2", 77 | "@types/prop-types": "^15.7.12", 78 | "@types/react": "^19.0.1", 79 | "@types/react-dom": "^19.0.1", 80 | "codecov": "^3.8.3", 81 | "cross-env": "^7.0.3", 82 | "eslint": "^8.57.0", 83 | "eslint-config-prettier": "^9.1.0", 84 | "eslint-import-resolver-typescript": "^3.6.1", 85 | "eslint-plugin-import": "^2.29.1", 86 | "eslint-plugin-prettier": "^5.1.3", 87 | "eslint-plugin-react": "^7.34.2", 88 | "jsdom": "^25.0.1", 89 | "prettier": "^3.3.3", 90 | "react": "^19.0.0", 91 | "react-dom": "^19.0.0", 92 | "redux": "^5.0.1", 93 | "rimraf": "^5.0.7", 94 | "tsup": "^8.3.5", 95 | "typescript": "^5.8.2", 96 | "typescript-eslint": "^7.12.0", 97 | "vitest": "^1.6.0" 98 | }, 99 | "packageManager": "yarn@4.4.1" 100 | } 101 | -------------------------------------------------------------------------------- /src/components/Context.ts: -------------------------------------------------------------------------------- 1 | import type { Context } from 'react' 2 | import { React } from '../utils/react' 3 | import type { Action, Store, UnknownAction } from 'redux' 4 | import type { Subscription } from '../utils/Subscription' 5 | import type { ProviderProps } from './Provider' 6 | 7 | export interface ReactReduxContextValue< 8 | SS = any, 9 | A extends Action = UnknownAction, 10 | > extends Pick { 11 | store: Store 12 | subscription: Subscription 13 | getServerState?: () => SS 14 | } 15 | 16 | const ContextKey = /* @__PURE__ */ Symbol.for(`react-redux-context`) 17 | const gT: { 18 | [ContextKey]?: Map< 19 | typeof React.createContext, 20 | Context 21 | > 22 | } = ( 23 | typeof globalThis !== 'undefined' 24 | ? globalThis 25 | : /* fall back to a per-module scope (pre-8.1 behaviour) if `globalThis` is not available */ {} 26 | ) as any 27 | 28 | function getContext(): Context { 29 | if (!React.createContext) return {} as any 30 | 31 | const contextMap = (gT[ContextKey] ??= new Map< 32 | typeof React.createContext, 33 | Context 34 | >()) 35 | let realContext = contextMap.get(React.createContext) 36 | if (!realContext) { 37 | realContext = React.createContext( 38 | null as any, 39 | ) 40 | if (process.env.NODE_ENV !== 'production') { 41 | realContext.displayName = 'ReactRedux' 42 | } 43 | contextMap.set(React.createContext, realContext) 44 | } 45 | return realContext 46 | } 47 | 48 | export const ReactReduxContext = /*#__PURE__*/ getContext() 49 | 50 | export type ReactReduxContextInstance = typeof ReactReduxContext 51 | -------------------------------------------------------------------------------- /src/components/Provider.tsx: -------------------------------------------------------------------------------- 1 | import type { Context, ReactNode } from 'react' 2 | import { React } from '../utils/react' 3 | import type { Action, Store, UnknownAction } from 'redux' 4 | import type { DevModeCheckFrequency } from '../hooks/useSelector' 5 | import { createSubscription } from '../utils/Subscription' 6 | import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect' 7 | import type { ReactReduxContextValue } from './Context' 8 | import { ReactReduxContext } from './Context' 9 | 10 | export interface ProviderProps< 11 | A extends Action = UnknownAction, 12 | S = unknown, 13 | > { 14 | /** 15 | * The single Redux store in your application. 16 | */ 17 | store: Store 18 | 19 | /** 20 | * An optional server state snapshot. Will be used during initial hydration render if available, to ensure that the UI output is consistent with the HTML generated on the server. 21 | */ 22 | serverState?: S 23 | 24 | /** 25 | * Optional context to be used internally in react-redux. Use React.createContext() to create a context to be used. 26 | * If this is used, you'll need to customize `connect` by supplying the same context provided to the Provider. 27 | * Set the initial value to null, and the hooks will error 28 | * if this is not overwritten by Provider. 29 | */ 30 | context?: Context | null> 31 | 32 | /** 33 | * Determines the frequency of stability checks for all selectors. 34 | * This setting overrides the global configuration for 35 | * the `useSelector` stability check, allowing you to specify how often 36 | * these checks should occur in development mode. 37 | * 38 | * @since 8.1.0 39 | */ 40 | stabilityCheck?: DevModeCheckFrequency 41 | 42 | /** 43 | * Determines the frequency of identity function checks for all selectors. 44 | * This setting overrides the global configuration for 45 | * the `useSelector` identity function check, allowing you to specify how often 46 | * these checks should occur in development mode. 47 | * 48 | * **Note**: Previously referred to as `noopCheck`. 49 | * 50 | * @since 9.0.0 51 | */ 52 | identityFunctionCheck?: DevModeCheckFrequency 53 | 54 | children: ReactNode 55 | } 56 | 57 | function Provider = UnknownAction, S = unknown>( 58 | providerProps: ProviderProps, 59 | ) { 60 | const { children, context, serverState, store } = providerProps 61 | 62 | const contextValue = React.useMemo(() => { 63 | const subscription = createSubscription(store) 64 | 65 | const baseContextValue = { 66 | store, 67 | subscription, 68 | getServerState: serverState ? () => serverState : undefined, 69 | } 70 | 71 | if (process.env.NODE_ENV === 'production') { 72 | return baseContextValue 73 | } else { 74 | const { identityFunctionCheck = 'once', stabilityCheck = 'once' } = 75 | providerProps 76 | 77 | return /* @__PURE__ */ Object.assign(baseContextValue, { 78 | stabilityCheck, 79 | identityFunctionCheck, 80 | }) 81 | } 82 | }, [store, serverState]) 83 | 84 | const previousState = React.useMemo(() => store.getState(), [store]) 85 | 86 | useIsomorphicLayoutEffect(() => { 87 | const { subscription } = contextValue 88 | subscription.onStateChange = subscription.notifyNestedSubs 89 | subscription.trySubscribe() 90 | 91 | if (previousState !== store.getState()) { 92 | subscription.notifyNestedSubs() 93 | } 94 | return () => { 95 | subscription.tryUnsubscribe() 96 | subscription.onStateChange = undefined 97 | } 98 | }, [contextValue, previousState]) 99 | 100 | const Context = context || ReactReduxContext 101 | 102 | return {children} 103 | } 104 | 105 | export default Provider 106 | -------------------------------------------------------------------------------- /src/connect/invalidArgFactory.ts: -------------------------------------------------------------------------------- 1 | import type { Action, Dispatch } from 'redux' 2 | 3 | export function createInvalidArgFactory(arg: unknown, name: string) { 4 | return ( 5 | dispatch: Dispatch>, 6 | options: { readonly wrappedComponentName: string }, 7 | ) => { 8 | throw new Error( 9 | `Invalid value of type ${typeof arg} for ${name} argument when connecting component ${ 10 | options.wrappedComponentName 11 | }.`, 12 | ) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/connect/mapDispatchToProps.ts: -------------------------------------------------------------------------------- 1 | import type { Action, Dispatch } from 'redux' 2 | import bindActionCreators from '../utils/bindActionCreators' 3 | import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps' 4 | import { createInvalidArgFactory } from './invalidArgFactory' 5 | import type { MapDispatchToPropsParam } from './selectorFactory' 6 | 7 | export function mapDispatchToPropsFactory( 8 | mapDispatchToProps: 9 | | MapDispatchToPropsParam 10 | | undefined, 11 | ) { 12 | return mapDispatchToProps && typeof mapDispatchToProps === 'object' 13 | ? wrapMapToPropsConstant((dispatch: Dispatch>) => 14 | // @ts-ignore 15 | bindActionCreators(mapDispatchToProps, dispatch), 16 | ) 17 | : !mapDispatchToProps 18 | ? wrapMapToPropsConstant((dispatch: Dispatch>) => ({ 19 | dispatch, 20 | })) 21 | : typeof mapDispatchToProps === 'function' 22 | ? // @ts-ignore 23 | wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps') 24 | : createInvalidArgFactory(mapDispatchToProps, 'mapDispatchToProps') 25 | } 26 | -------------------------------------------------------------------------------- /src/connect/mapStateToProps.ts: -------------------------------------------------------------------------------- 1 | import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps' 2 | import { createInvalidArgFactory } from './invalidArgFactory' 3 | import type { MapStateToPropsParam } from './selectorFactory' 4 | 5 | export function mapStateToPropsFactory( 6 | mapStateToProps: MapStateToPropsParam, 7 | ) { 8 | return !mapStateToProps 9 | ? wrapMapToPropsConstant(() => ({})) 10 | : typeof mapStateToProps === 'function' 11 | ? // @ts-ignore 12 | wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps') 13 | : createInvalidArgFactory(mapStateToProps, 'mapStateToProps') 14 | } 15 | -------------------------------------------------------------------------------- /src/connect/mergeProps.ts: -------------------------------------------------------------------------------- 1 | import type { Action, Dispatch } from 'redux' 2 | import verifyPlainObject from '../utils/verifyPlainObject' 3 | import { createInvalidArgFactory } from './invalidArgFactory' 4 | import type { MergeProps } from './selectorFactory' 5 | import type { EqualityFn } from '../types' 6 | 7 | function defaultMergeProps< 8 | TStateProps, 9 | TDispatchProps, 10 | TOwnProps, 11 | TMergedProps, 12 | >( 13 | stateProps: TStateProps, 14 | dispatchProps: TDispatchProps, 15 | ownProps: TOwnProps, 16 | ): TMergedProps { 17 | // @ts-ignore 18 | return { ...ownProps, ...stateProps, ...dispatchProps } 19 | } 20 | 21 | function wrapMergePropsFunc< 22 | TStateProps, 23 | TDispatchProps, 24 | TOwnProps, 25 | TMergedProps, 26 | >( 27 | mergeProps: MergeProps, 28 | ): ( 29 | dispatch: Dispatch>, 30 | options: { 31 | readonly displayName: string 32 | readonly areMergedPropsEqual: EqualityFn 33 | }, 34 | ) => MergeProps { 35 | return function initMergePropsProxy( 36 | dispatch, 37 | { displayName, areMergedPropsEqual }, 38 | ) { 39 | let hasRunOnce = false 40 | let mergedProps: TMergedProps 41 | 42 | return function mergePropsProxy( 43 | stateProps: TStateProps, 44 | dispatchProps: TDispatchProps, 45 | ownProps: TOwnProps, 46 | ) { 47 | const nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps) 48 | 49 | if (hasRunOnce) { 50 | if (!areMergedPropsEqual(nextMergedProps, mergedProps)) 51 | mergedProps = nextMergedProps 52 | } else { 53 | hasRunOnce = true 54 | mergedProps = nextMergedProps 55 | 56 | if (process.env.NODE_ENV !== 'production') 57 | verifyPlainObject(mergedProps, displayName, 'mergeProps') 58 | } 59 | 60 | return mergedProps 61 | } 62 | } 63 | } 64 | 65 | export function mergePropsFactory< 66 | TStateProps, 67 | TDispatchProps, 68 | TOwnProps, 69 | TMergedProps, 70 | >( 71 | mergeProps?: MergeProps, 72 | ) { 73 | return !mergeProps 74 | ? () => defaultMergeProps 75 | : typeof mergeProps === 'function' 76 | ? wrapMergePropsFunc(mergeProps) 77 | : createInvalidArgFactory(mergeProps, 'mergeProps') 78 | } 79 | -------------------------------------------------------------------------------- /src/connect/verifySubselectors.ts: -------------------------------------------------------------------------------- 1 | import warning from '../utils/warning' 2 | 3 | function verify(selector: unknown, methodName: string): void { 4 | if (!selector) { 5 | throw new Error(`Unexpected value for ${methodName} in connect.`) 6 | } else if ( 7 | methodName === 'mapStateToProps' || 8 | methodName === 'mapDispatchToProps' 9 | ) { 10 | if (!Object.prototype.hasOwnProperty.call(selector, 'dependsOnOwnProps')) { 11 | warning( 12 | `The selector for ${methodName} of connect did not specify a value for dependsOnOwnProps.`, 13 | ) 14 | } 15 | } 16 | } 17 | 18 | export default function verifySubselectors( 19 | mapStateToProps: unknown, 20 | mapDispatchToProps: unknown, 21 | mergeProps: unknown, 22 | ): void { 23 | verify(mapStateToProps, 'mapStateToProps') 24 | verify(mapDispatchToProps, 'mapDispatchToProps') 25 | verify(mergeProps, 'mergeProps') 26 | } 27 | -------------------------------------------------------------------------------- /src/connect/wrapMapToProps.ts: -------------------------------------------------------------------------------- 1 | import type { ActionCreatorsMapObject, Dispatch, ActionCreator } from 'redux' 2 | 3 | import type { FixTypeLater } from '../types' 4 | import verifyPlainObject from '../utils/verifyPlainObject' 5 | 6 | type AnyState = { [key: string]: any } 7 | type StateOrDispatch = S | Dispatch 8 | 9 | type AnyProps = { [key: string]: any } 10 | 11 | export type MapToProps

= { 12 | // eslint-disable-next-line no-unused-vars 13 | (stateOrDispatch: StateOrDispatch, ownProps?: P): FixTypeLater 14 | dependsOnOwnProps?: boolean 15 | } 16 | 17 | export function wrapMapToPropsConstant( 18 | // * Note: 19 | // It seems that the dispatch argument 20 | // could be a dispatch function in some cases (ex: whenMapDispatchToPropsIsMissing) 21 | // and a state object in some others (ex: whenMapStateToPropsIsMissing) 22 | // eslint-disable-next-line no-unused-vars 23 | getConstant: (dispatch: Dispatch) => 24 | | { 25 | dispatch?: Dispatch 26 | dependsOnOwnProps?: boolean 27 | } 28 | | ActionCreatorsMapObject 29 | | ActionCreator, 30 | ) { 31 | return function initConstantSelector(dispatch: Dispatch) { 32 | const constant = getConstant(dispatch) 33 | 34 | function constantSelector() { 35 | return constant 36 | } 37 | constantSelector.dependsOnOwnProps = false 38 | return constantSelector 39 | } 40 | } 41 | 42 | // dependsOnOwnProps is used by createMapToPropsProxy to determine whether to pass props as args 43 | // to the mapToProps function being wrapped. It is also used by makePurePropsSelector to determine 44 | // whether mapToProps needs to be invoked when props have changed. 45 | // 46 | // A length of one signals that mapToProps does not depend on props from the parent component. 47 | // A length of zero is assumed to mean mapToProps is getting args via arguments or ...args and 48 | // therefore not reporting its length accurately.. 49 | // TODO Can this get pulled out so that we can subscribe directly to the store if we don't need ownProps? 50 | function getDependsOnOwnProps(mapToProps: MapToProps) { 51 | return mapToProps.dependsOnOwnProps 52 | ? Boolean(mapToProps.dependsOnOwnProps) 53 | : mapToProps.length !== 1 54 | } 55 | 56 | // Used by whenMapStateToPropsIsFunction and whenMapDispatchToPropsIsFunction, 57 | // this function wraps mapToProps in a proxy function which does several things: 58 | // 59 | // * Detects whether the mapToProps function being called depends on props, which 60 | // is used by selectorFactory to decide if it should reinvoke on props changes. 61 | // 62 | // * On first call, handles mapToProps if returns another function, and treats that 63 | // new function as the true mapToProps for subsequent calls. 64 | // 65 | // * On first call, verifies the first result is a plain object, in order to warn 66 | // the developer that their mapToProps function is not returning a valid result. 67 | // 68 | export function wrapMapToPropsFunc

( 69 | mapToProps: MapToProps, 70 | methodName: string, 71 | ) { 72 | return function initProxySelector( 73 | dispatch: Dispatch, 74 | { displayName }: { displayName: string }, 75 | ) { 76 | const proxy = function mapToPropsProxy( 77 | stateOrDispatch: StateOrDispatch, 78 | ownProps?: P, 79 | ): MapToProps { 80 | return proxy.dependsOnOwnProps 81 | ? proxy.mapToProps(stateOrDispatch, ownProps) 82 | : proxy.mapToProps(stateOrDispatch, undefined) 83 | } 84 | 85 | // allow detectFactoryAndVerify to get ownProps 86 | proxy.dependsOnOwnProps = true 87 | 88 | proxy.mapToProps = function detectFactoryAndVerify( 89 | stateOrDispatch: StateOrDispatch, 90 | ownProps?: P, 91 | ): MapToProps { 92 | proxy.mapToProps = mapToProps 93 | proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps) 94 | let props = proxy(stateOrDispatch, ownProps) 95 | 96 | if (typeof props === 'function') { 97 | proxy.mapToProps = props 98 | proxy.dependsOnOwnProps = getDependsOnOwnProps(props) 99 | props = proxy(stateOrDispatch, ownProps) 100 | } 101 | 102 | if (process.env.NODE_ENV !== 'production') 103 | verifyPlainObject(props, displayName, methodName) 104 | 105 | return props 106 | } 107 | 108 | return proxy 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/exports.ts: -------------------------------------------------------------------------------- 1 | import connect from './components/connect' 2 | export type { 3 | Connect, 4 | ConnectProps, 5 | ConnectedProps, 6 | } from './components/connect' 7 | 8 | import shallowEqual from './utils/shallowEqual' 9 | 10 | import Provider from './components/Provider' 11 | import { defaultNoopBatch } from './utils/batch' 12 | 13 | export { ReactReduxContext } from './components/Context' 14 | export type { ReactReduxContextValue } from './components/Context' 15 | 16 | export type { ProviderProps } from './components/Provider' 17 | 18 | export type { 19 | MapDispatchToProps, 20 | MapDispatchToPropsFactory, 21 | MapDispatchToPropsFunction, 22 | MapDispatchToPropsNonObject, 23 | MapDispatchToPropsParam, 24 | MapStateToProps, 25 | MapStateToPropsFactory, 26 | MapStateToPropsParam, 27 | MergeProps, 28 | Selector, 29 | SelectorFactory, 30 | } from './connect/selectorFactory' 31 | 32 | export { createDispatchHook, useDispatch } from './hooks/useDispatch' 33 | export type { UseDispatch } from './hooks/useDispatch' 34 | 35 | export { createSelectorHook, useSelector } from './hooks/useSelector' 36 | export type { UseSelector } from './hooks/useSelector' 37 | 38 | export { createStoreHook, useStore } from './hooks/useStore' 39 | export type { UseStore } from './hooks/useStore' 40 | 41 | export type { Subscription } from './utils/Subscription' 42 | 43 | export * from './types' 44 | 45 | /** 46 | * @deprecated As of React 18, batching is enabled by default for ReactDOM and React Native. 47 | * This is now a no-op that immediately runs the callback. 48 | */ 49 | const batch = defaultNoopBatch 50 | 51 | export { Provider, batch, connect, shallowEqual } 52 | -------------------------------------------------------------------------------- /src/hooks/useDispatch.ts: -------------------------------------------------------------------------------- 1 | import type { Context } from 'react' 2 | import type { Action, Dispatch, UnknownAction } from 'redux' 3 | 4 | import type { ReactReduxContextValue } from '../components/Context' 5 | import { ReactReduxContext } from '../components/Context' 6 | import { createStoreHook, useStore as useDefaultStore } from './useStore' 7 | 8 | /** 9 | * Represents a custom hook that provides a dispatch function 10 | * from the Redux store. 11 | * 12 | * @template DispatchType - The specific type of the dispatch function. 13 | * 14 | * @since 9.1.0 15 | * @public 16 | */ 17 | export interface UseDispatch< 18 | DispatchType extends Dispatch = Dispatch, 19 | > { 20 | /** 21 | * Returns the dispatch function from the Redux store. 22 | * 23 | * @returns The dispatch function from the Redux store. 24 | * 25 | * @template AppDispatch - The specific type of the dispatch function. 26 | */ 27 | (): AppDispatch 28 | 29 | /** 30 | * Creates a "pre-typed" version of {@linkcode useDispatch useDispatch} 31 | * where the type of the `dispatch` function is predefined. 32 | * 33 | * This allows you to set the `dispatch` type once, eliminating the need to 34 | * specify it with every {@linkcode useDispatch useDispatch} call. 35 | * 36 | * @returns A pre-typed `useDispatch` with the dispatch type already defined. 37 | * 38 | * @example 39 | * ```ts 40 | * export const useAppDispatch = useDispatch.withTypes() 41 | * ``` 42 | * 43 | * @template OverrideDispatchType - The specific type of the dispatch function. 44 | * 45 | * @since 9.1.0 46 | */ 47 | withTypes: < 48 | OverrideDispatchType extends DispatchType, 49 | >() => UseDispatch 50 | } 51 | 52 | /** 53 | * Hook factory, which creates a `useDispatch` hook bound to a given context. 54 | * 55 | * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. 56 | * @returns {Function} A `useDispatch` hook bound to the specified context. 57 | */ 58 | export function createDispatchHook< 59 | StateType = unknown, 60 | ActionType extends Action = UnknownAction, 61 | >( 62 | // @ts-ignore 63 | context?: Context | null> = ReactReduxContext, 67 | ) { 68 | const useStore = 69 | context === ReactReduxContext ? useDefaultStore : createStoreHook(context) 70 | 71 | const useDispatch = () => { 72 | const store = useStore() 73 | return store.dispatch 74 | } 75 | 76 | Object.assign(useDispatch, { 77 | withTypes: () => useDispatch, 78 | }) 79 | 80 | return useDispatch as UseDispatch> 81 | } 82 | 83 | /** 84 | * A hook to access the redux `dispatch` function. 85 | * 86 | * @returns {any|function} redux store's `dispatch` function 87 | * 88 | * @example 89 | * 90 | * import React, { useCallback } from 'react' 91 | * import { useDispatch } from 'react-redux' 92 | * 93 | * export const CounterComponent = ({ value }) => { 94 | * const dispatch = useDispatch() 95 | * const increaseCounter = useCallback(() => dispatch({ type: 'increase-counter' }), []) 96 | * return ( 97 | *

98 | * {value} 99 | * 100 | *
101 | * ) 102 | * } 103 | */ 104 | export const useDispatch = /*#__PURE__*/ createDispatchHook() 105 | -------------------------------------------------------------------------------- /src/hooks/useReduxContext.ts: -------------------------------------------------------------------------------- 1 | import { React } from '../utils/react' 2 | import { ReactReduxContext } from '../components/Context' 3 | import type { ReactReduxContextValue } from '../components/Context' 4 | 5 | /** 6 | * Hook factory, which creates a `useReduxContext` hook bound to a given context. This is a low-level 7 | * hook that you should usually not need to call directly. 8 | * 9 | * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. 10 | * @returns {Function} A `useReduxContext` hook bound to the specified context. 11 | */ 12 | export function createReduxContextHook(context = ReactReduxContext) { 13 | return function useReduxContext(): ReactReduxContextValue { 14 | const contextValue = React.useContext(context) 15 | 16 | if (process.env.NODE_ENV !== 'production' && !contextValue) { 17 | throw new Error( 18 | 'could not find react-redux context value; please ensure the component is wrapped in a ', 19 | ) 20 | } 21 | 22 | return contextValue! 23 | } 24 | } 25 | 26 | /** 27 | * A hook to access the value of the `ReactReduxContext`. This is a low-level 28 | * hook that you should usually not need to call directly. 29 | * 30 | * @returns {any} the value of the `ReactReduxContext` 31 | * 32 | * @example 33 | * 34 | * import React from 'react' 35 | * import { useReduxContext } from 'react-redux' 36 | * 37 | * export const CounterComponent = () => { 38 | * const { store } = useReduxContext() 39 | * return
{store.getState()}
40 | * } 41 | */ 42 | export const useReduxContext = /*#__PURE__*/ createReduxContextHook() 43 | -------------------------------------------------------------------------------- /src/hooks/useStore.ts: -------------------------------------------------------------------------------- 1 | import type { Context } from 'react' 2 | import type { Action, Store } from 'redux' 3 | import type { ReactReduxContextValue } from '../components/Context' 4 | import { ReactReduxContext } from '../components/Context' 5 | import { 6 | createReduxContextHook, 7 | useReduxContext as useDefaultReduxContext, 8 | } from './useReduxContext' 9 | 10 | /** 11 | * Represents a type that extracts the action type from a given Redux store. 12 | * 13 | * @template StoreType - The specific type of the Redux store. 14 | * 15 | * @since 9.1.0 16 | * @internal 17 | */ 18 | export type ExtractStoreActionType = 19 | StoreType extends Store ? ActionType : never 20 | 21 | /** 22 | * Represents a custom hook that provides access to the Redux store. 23 | * 24 | * @template StoreType - The specific type of the Redux store that gets returned. 25 | * 26 | * @since 9.1.0 27 | * @public 28 | */ 29 | export interface UseStore { 30 | /** 31 | * Returns the Redux store instance. 32 | * 33 | * @returns The Redux store instance. 34 | */ 35 | (): StoreType 36 | 37 | /** 38 | * Returns the Redux store instance with specific state and action types. 39 | * 40 | * @returns The Redux store with the specified state and action types. 41 | * 42 | * @template StateType - The specific type of the state used in the store. 43 | * @template ActionType - The specific type of the actions used in the store. 44 | */ 45 | < 46 | StateType extends ReturnType = ReturnType< 47 | StoreType['getState'] 48 | >, 49 | ActionType extends Action = ExtractStoreActionType, 50 | >(): Store 51 | 52 | /** 53 | * Creates a "pre-typed" version of {@linkcode useStore useStore} 54 | * where the type of the Redux `store` is predefined. 55 | * 56 | * This allows you to set the `store` type once, eliminating the need to 57 | * specify it with every {@linkcode useStore useStore} call. 58 | * 59 | * @returns A pre-typed `useStore` with the store type already defined. 60 | * 61 | * @example 62 | * ```ts 63 | * export const useAppStore = useStore.withTypes() 64 | * ``` 65 | * 66 | * @template OverrideStoreType - The specific type of the Redux store that gets returned. 67 | * 68 | * @since 9.1.0 69 | */ 70 | withTypes: < 71 | OverrideStoreType extends StoreType, 72 | >() => UseStore 73 | } 74 | 75 | /** 76 | * Hook factory, which creates a `useStore` hook bound to a given context. 77 | * 78 | * @param {React.Context} [context=ReactReduxContext] Context passed to your ``. 79 | * @returns {Function} A `useStore` hook bound to the specified context. 80 | */ 81 | export function createStoreHook< 82 | StateType = unknown, 83 | ActionType extends Action = Action, 84 | >( 85 | // @ts-ignore 86 | context?: Context | null> = ReactReduxContext, 90 | ) { 91 | const useReduxContext = 92 | context === ReactReduxContext 93 | ? useDefaultReduxContext 94 | : // @ts-ignore 95 | createReduxContextHook(context) 96 | const useStore = () => { 97 | const { store } = useReduxContext() 98 | return store 99 | } 100 | 101 | Object.assign(useStore, { 102 | withTypes: () => useStore, 103 | }) 104 | 105 | return useStore as UseStore> 106 | } 107 | 108 | /** 109 | * A hook to access the redux store. 110 | * 111 | * @returns {any} the redux store 112 | * 113 | * @example 114 | * 115 | * import React from 'react' 116 | * import { useStore } from 'react-redux' 117 | * 118 | * export const ExampleComponent = () => { 119 | * const store = useStore() 120 | * return
{store.getState()}
121 | * } 122 | */ 123 | export const useStore = /*#__PURE__*/ createStoreHook() 124 | -------------------------------------------------------------------------------- /src/index-rsc.ts: -------------------------------------------------------------------------------- 1 | import type * as normal from './index' 2 | import type * as rsc from './index-rsc' 3 | 4 | // checks to make sure we didn't forgot to replicate any exports 5 | 6 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 7 | const _check: typeof normal = {} as typeof rsc 8 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 9 | const _check2: typeof rsc = {} as typeof normal 10 | 11 | // ------------------------------------------------------------------------------------- 12 | 13 | const throwNotSupportedError = (( 14 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 15 | ...args: any[] 16 | ): any => { 17 | throw new Error( 18 | 'This function is not supported in React Server Components. Please only use this export in a Client Component.', 19 | ) 20 | }) as any 21 | 22 | export { 23 | throwNotSupportedError as Provider, 24 | throwNotSupportedError as batch, 25 | throwNotSupportedError as connect, 26 | throwNotSupportedError as createDispatchHook, 27 | throwNotSupportedError as createSelectorHook, 28 | throwNotSupportedError as createStoreHook, 29 | throwNotSupportedError as useDispatch, 30 | throwNotSupportedError as useSelector, 31 | throwNotSupportedError as useStore, 32 | } 33 | export const ReactReduxContext = {} as any 34 | export { default as shallowEqual } from './utils/shallowEqual' 35 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './exports' 2 | -------------------------------------------------------------------------------- /src/utils/batch.ts: -------------------------------------------------------------------------------- 1 | // Default to a dummy "batch" implementation that just runs the callback 2 | export function defaultNoopBatch(callback: () => void) { 3 | callback() 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/bindActionCreators.ts: -------------------------------------------------------------------------------- 1 | import type { ActionCreatorsMapObject, Dispatch } from 'redux' 2 | 3 | export default function bindActionCreators( 4 | actionCreators: ActionCreatorsMapObject, 5 | dispatch: Dispatch, 6 | ): ActionCreatorsMapObject { 7 | const boundActionCreators: ActionCreatorsMapObject = {} 8 | 9 | for (const key in actionCreators) { 10 | const actionCreator = actionCreators[key] 11 | if (typeof actionCreator === 'function') { 12 | boundActionCreators[key] = (...args) => dispatch(actionCreator(...args)) 13 | } 14 | } 15 | return boundActionCreators 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/hoistStatics.ts: -------------------------------------------------------------------------------- 1 | // Copied directly from: 2 | // https://github.com/mridgway/hoist-non-react-statics/blob/main/src/index.js 3 | // https://unpkg.com/browse/@types/hoist-non-react-statics@3.3.6/index.d.ts 4 | 5 | /** 6 | * Copyright 2015, Yahoo! Inc. 7 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. 8 | */ 9 | import type { ForwardRefExoticComponent, MemoExoticComponent } from 'react' 10 | import { ForwardRef, Memo, isMemo } from '../utils/react-is' 11 | 12 | const REACT_STATICS = { 13 | childContextTypes: true, 14 | contextType: true, 15 | contextTypes: true, 16 | defaultProps: true, 17 | displayName: true, 18 | getDefaultProps: true, 19 | getDerivedStateFromError: true, 20 | getDerivedStateFromProps: true, 21 | mixins: true, 22 | propTypes: true, 23 | type: true, 24 | } as const 25 | 26 | const KNOWN_STATICS = { 27 | name: true, 28 | length: true, 29 | prototype: true, 30 | caller: true, 31 | callee: true, 32 | arguments: true, 33 | arity: true, 34 | } as const 35 | 36 | const FORWARD_REF_STATICS = { 37 | $$typeof: true, 38 | render: true, 39 | defaultProps: true, 40 | displayName: true, 41 | propTypes: true, 42 | } as const 43 | 44 | const MEMO_STATICS = { 45 | $$typeof: true, 46 | compare: true, 47 | defaultProps: true, 48 | displayName: true, 49 | propTypes: true, 50 | type: true, 51 | } as const 52 | 53 | const TYPE_STATICS = { 54 | [ForwardRef]: FORWARD_REF_STATICS, 55 | [Memo]: MEMO_STATICS, 56 | } as const 57 | 58 | function getStatics(component: any) { 59 | // React v16.11 and below 60 | if (isMemo(component)) { 61 | return MEMO_STATICS 62 | } 63 | 64 | // React v16.12 and above 65 | return TYPE_STATICS[component['$$typeof']] || REACT_STATICS 66 | } 67 | 68 | export type NonReactStatics< 69 | Source, 70 | C extends { 71 | [key: string]: true 72 | } = {}, 73 | > = { 74 | [key in Exclude< 75 | keyof Source, 76 | Source extends MemoExoticComponent 77 | ? keyof typeof MEMO_STATICS | keyof C 78 | : Source extends ForwardRefExoticComponent 79 | ? keyof typeof FORWARD_REF_STATICS | keyof C 80 | : keyof typeof REACT_STATICS | keyof typeof KNOWN_STATICS | keyof C 81 | >]: Source[key] 82 | } 83 | 84 | const defineProperty = Object.defineProperty 85 | const getOwnPropertyNames = Object.getOwnPropertyNames 86 | const getOwnPropertySymbols = Object.getOwnPropertySymbols 87 | const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor 88 | const getPrototypeOf = Object.getPrototypeOf 89 | const objectPrototype = Object.prototype 90 | 91 | export default function hoistNonReactStatics< 92 | Target, 93 | Source, 94 | CustomStatic extends { 95 | [key: string]: true 96 | } = {}, 97 | >( 98 | targetComponent: Target, 99 | sourceComponent: Source, 100 | ): Target & NonReactStatics { 101 | if (typeof sourceComponent !== 'string') { 102 | // don't hoist over string (html) components 103 | 104 | if (objectPrototype) { 105 | const inheritedComponent = getPrototypeOf(sourceComponent) 106 | if (inheritedComponent && inheritedComponent !== objectPrototype) { 107 | hoistNonReactStatics(targetComponent, inheritedComponent) 108 | } 109 | } 110 | 111 | let keys: (string | symbol)[] = getOwnPropertyNames(sourceComponent) 112 | 113 | if (getOwnPropertySymbols) { 114 | keys = keys.concat(getOwnPropertySymbols(sourceComponent)) 115 | } 116 | 117 | const targetStatics = getStatics(targetComponent) 118 | const sourceStatics = getStatics(sourceComponent) 119 | 120 | for (let i = 0; i < keys.length; ++i) { 121 | const key = keys[i] 122 | if ( 123 | !KNOWN_STATICS[key as keyof typeof KNOWN_STATICS] && 124 | !(sourceStatics && sourceStatics[key as keyof typeof sourceStatics]) && 125 | !(targetStatics && targetStatics[key as keyof typeof targetStatics]) 126 | ) { 127 | const descriptor = getOwnPropertyDescriptor(sourceComponent, key) 128 | try { 129 | // Avoid failures from read-only properties 130 | defineProperty(targetComponent, key, descriptor!) 131 | } catch (e) { 132 | // ignore 133 | } 134 | } 135 | } 136 | } 137 | 138 | return targetComponent as any 139 | } 140 | -------------------------------------------------------------------------------- /src/utils/isPlainObject.ts: -------------------------------------------------------------------------------- 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: unknown) { 6 | if (typeof obj !== 'object' || obj === null) return false 7 | 8 | const 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/react-is.ts: -------------------------------------------------------------------------------- 1 | import type { ElementType, MemoExoticComponent, ReactElement } from 'react' 2 | import { React } from './react' 3 | 4 | // Directly ported from: 5 | // https://unpkg.com/browse/react-is@19.0.0/cjs/react-is.production.js 6 | // It's very possible this could change in the future, but given that 7 | // we only use these in `connect`, this is a low priority. 8 | 9 | export const IS_REACT_19 = /* @__PURE__ */ React.version.startsWith('19') 10 | 11 | const REACT_ELEMENT_TYPE = /* @__PURE__ */ Symbol.for( 12 | IS_REACT_19 ? 'react.transitional.element' : 'react.element', 13 | ) 14 | const REACT_PORTAL_TYPE = /* @__PURE__ */ Symbol.for('react.portal') 15 | const REACT_FRAGMENT_TYPE = /* @__PURE__ */ Symbol.for('react.fragment') 16 | const REACT_STRICT_MODE_TYPE = /* @__PURE__ */ Symbol.for('react.strict_mode') 17 | const REACT_PROFILER_TYPE = /* @__PURE__ */ Symbol.for('react.profiler') 18 | const REACT_CONSUMER_TYPE = /* @__PURE__ */ Symbol.for('react.consumer') 19 | const REACT_CONTEXT_TYPE = /* @__PURE__ */ Symbol.for('react.context') 20 | const REACT_FORWARD_REF_TYPE = /* @__PURE__ */ Symbol.for('react.forward_ref') 21 | const REACT_SUSPENSE_TYPE = /* @__PURE__ */ Symbol.for('react.suspense') 22 | const REACT_SUSPENSE_LIST_TYPE = /* @__PURE__ */ Symbol.for( 23 | 'react.suspense_list', 24 | ) 25 | const REACT_MEMO_TYPE = /* @__PURE__ */ Symbol.for('react.memo') 26 | const REACT_LAZY_TYPE = /* @__PURE__ */ Symbol.for('react.lazy') 27 | const REACT_OFFSCREEN_TYPE = /* @__PURE__ */ Symbol.for('react.offscreen') 28 | const REACT_CLIENT_REFERENCE = /* @__PURE__ */ Symbol.for( 29 | 'react.client.reference', 30 | ) 31 | 32 | export const ForwardRef = REACT_FORWARD_REF_TYPE 33 | export const Memo = REACT_MEMO_TYPE 34 | 35 | export function isValidElementType(type: any): type is ElementType { 36 | return typeof type === 'string' || 37 | typeof type === 'function' || 38 | type === REACT_FRAGMENT_TYPE || 39 | type === REACT_PROFILER_TYPE || 40 | type === REACT_STRICT_MODE_TYPE || 41 | type === REACT_SUSPENSE_TYPE || 42 | type === REACT_SUSPENSE_LIST_TYPE || 43 | type === REACT_OFFSCREEN_TYPE || 44 | (typeof type === 'object' && 45 | type !== null && 46 | (type.$$typeof === REACT_LAZY_TYPE || 47 | type.$$typeof === REACT_MEMO_TYPE || 48 | type.$$typeof === REACT_CONTEXT_TYPE || 49 | type.$$typeof === REACT_CONSUMER_TYPE || 50 | type.$$typeof === REACT_FORWARD_REF_TYPE || 51 | type.$$typeof === REACT_CLIENT_REFERENCE || 52 | type.getModuleId !== undefined)) 53 | ? !0 54 | : !1 55 | } 56 | 57 | function typeOf(object: any): symbol | undefined { 58 | if (typeof object === 'object' && object !== null) { 59 | const { $$typeof } = object 60 | 61 | switch ($$typeof) { 62 | case REACT_ELEMENT_TYPE: 63 | switch (((object = object.type), object)) { 64 | case REACT_FRAGMENT_TYPE: 65 | case REACT_PROFILER_TYPE: 66 | case REACT_STRICT_MODE_TYPE: 67 | case REACT_SUSPENSE_TYPE: 68 | case REACT_SUSPENSE_LIST_TYPE: 69 | return object 70 | default: 71 | switch (((object = object && object.$$typeof), object)) { 72 | case REACT_CONTEXT_TYPE: 73 | case REACT_FORWARD_REF_TYPE: 74 | case REACT_LAZY_TYPE: 75 | case REACT_MEMO_TYPE: 76 | return object 77 | case REACT_CONSUMER_TYPE: 78 | return object 79 | default: 80 | return $$typeof 81 | } 82 | } 83 | case REACT_PORTAL_TYPE: 84 | return $$typeof 85 | } 86 | } 87 | } 88 | 89 | export function isContextConsumer(object: any): object is ReactElement { 90 | return IS_REACT_19 91 | ? typeOf(object) === REACT_CONSUMER_TYPE 92 | : typeOf(object) === REACT_CONTEXT_TYPE 93 | } 94 | 95 | export function isMemo(object: any): object is MemoExoticComponent { 96 | return typeOf(object) === REACT_MEMO_TYPE 97 | } 98 | -------------------------------------------------------------------------------- /src/utils/react.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export { React } 4 | -------------------------------------------------------------------------------- /src/utils/shallowEqual.ts: -------------------------------------------------------------------------------- 1 | function is(x: unknown, y: unknown) { 2 | if (x === y) { 3 | return x !== 0 || y !== 0 || 1 / x === 1 / y 4 | } else { 5 | return x !== x && y !== y 6 | } 7 | } 8 | 9 | export default function shallowEqual(objA: any, objB: any) { 10 | if (is(objA, objB)) return true 11 | 12 | if ( 13 | typeof objA !== 'object' || 14 | objA === null || 15 | typeof objB !== 'object' || 16 | objB === null 17 | ) { 18 | return false 19 | } 20 | 21 | const keysA = Object.keys(objA) 22 | const keysB = Object.keys(objB) 23 | 24 | if (keysA.length !== keysB.length) return false 25 | 26 | for (let i = 0; i < keysA.length; i++) { 27 | if ( 28 | !Object.prototype.hasOwnProperty.call(objB, keysA[i]) || 29 | !is(objA[keysA[i]], objB[keysA[i]]) 30 | ) { 31 | return false 32 | } 33 | } 34 | 35 | return true 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/useIsomorphicLayoutEffect.ts: -------------------------------------------------------------------------------- 1 | import { React } from '../utils/react' 2 | 3 | // React currently throws a warning when using useLayoutEffect on the server. 4 | // To get around it, we can conditionally useEffect on the server (no-op) and 5 | // useLayoutEffect in the browser. We need useLayoutEffect to ensure the store 6 | // subscription callback always has the selector from the latest render commit 7 | // available, otherwise a store update may happen between render and the effect, 8 | // which may cause missed updates; we also must ensure the store subscription 9 | // is created synchronously, otherwise a store update may occur before the 10 | // subscription is created and an inconsistent state may be observed 11 | 12 | // Matches logic in React's `shared/ExecutionEnvironment` file 13 | const canUseDOM = () => 14 | !!( 15 | typeof window !== 'undefined' && 16 | typeof window.document !== 'undefined' && 17 | typeof window.document.createElement !== 'undefined' 18 | ) 19 | 20 | const isDOM = /* @__PURE__ */ canUseDOM() 21 | 22 | // Under React Native, we know that we always want to use useLayoutEffect 23 | 24 | /** 25 | * Checks if the code is running in a React Native environment. 26 | * 27 | * @returns Whether the code is running in a React Native environment. 28 | * 29 | * @see {@link https://github.com/facebook/react-native/issues/1331 Reference} 30 | */ 31 | const isRunningInReactNative = () => 32 | typeof navigator !== 'undefined' && navigator.product === 'ReactNative' 33 | 34 | const isReactNative = /* @__PURE__ */ isRunningInReactNative() 35 | 36 | const getUseIsomorphicLayoutEffect = () => 37 | isDOM || isReactNative ? React.useLayoutEffect : React.useEffect 38 | 39 | export const useIsomorphicLayoutEffect = 40 | /* @__PURE__ */ getUseIsomorphicLayoutEffect() 41 | -------------------------------------------------------------------------------- /src/utils/useSyncExternalStore.ts: -------------------------------------------------------------------------------- 1 | import type { useSyncExternalStore } from 'use-sync-external-store' 2 | import type { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector' 3 | 4 | export const notInitialized = () => { 5 | throw new Error('uSES not initialized!') 6 | } 7 | 8 | export type uSES = typeof useSyncExternalStore 9 | export type uSESWS = typeof useSyncExternalStoreWithSelector 10 | -------------------------------------------------------------------------------- /src/utils/verifyPlainObject.ts: -------------------------------------------------------------------------------- 1 | import isPlainObject from './isPlainObject' 2 | import warning from './warning' 3 | 4 | export default function verifyPlainObject( 5 | value: unknown, 6 | displayName: string, 7 | methodName: string, 8 | ) { 9 | if (!isPlainObject(value)) { 10 | warning( 11 | `${methodName}() in ${displayName} must return a plain object. Instead received ${value}.`, 12 | ) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/warning.ts: -------------------------------------------------------------------------------- 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: string) { 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 | -------------------------------------------------------------------------------- /test/components/hooks.spec.tsx: -------------------------------------------------------------------------------- 1 | /*eslint-disable react/prop-types*/ 2 | 3 | import * as rtl from '@testing-library/react' 4 | import React from 'react' 5 | import { Provider as ProviderMock, connect } from 'react-redux' 6 | import type { AnyAction } from 'redux' 7 | import { createStore } from 'redux' 8 | 9 | const IS_REACT_18 = React.version.startsWith('18') 10 | 11 | describe('React', () => { 12 | describe('connect', () => { 13 | it('should render on useEffect hook state update', () => { 14 | interface RootStateType { 15 | byId: { 16 | [x: string]: string 17 | } 18 | list: Array 19 | } 20 | const store = createStore( 21 | (state, action) => { 22 | let newState = 23 | state !== undefined 24 | ? state 25 | : { 26 | byId: {}, 27 | list: [], 28 | } 29 | switch (action.type) { 30 | case 'FOO': 31 | newState = { 32 | ...newState, 33 | list: [1], 34 | byId: { 1: 'foo' }, 35 | } 36 | break 37 | } 38 | return newState 39 | }, 40 | ) 41 | 42 | const mapStateSpy1 = vi.fn() 43 | const renderSpy1 = vi.fn() 44 | 45 | let component1StateList: number[] 46 | 47 | const component1Decorator = connect< 48 | Omit, 49 | unknown, 50 | unknown, 51 | RootStateType 52 | >((state) => { 53 | mapStateSpy1() 54 | 55 | return { 56 | list: state.list, 57 | } 58 | }) 59 | 60 | const component1 = (props: Omit) => { 61 | const [state, setState] = React.useState({ list: props.list }) 62 | 63 | component1StateList = state.list 64 | 65 | React.useEffect(() => { 66 | setState((prevState) => ({ ...prevState, list: props.list })) 67 | }, [props.list]) 68 | 69 | renderSpy1() 70 | 71 | return 72 | } 73 | 74 | const Component1 = component1Decorator(component1) 75 | 76 | const mapStateSpy2 = vi.fn() 77 | const renderSpy2 = vi.fn() 78 | 79 | interface Component2Tstate { 80 | mappedProp: string[] 81 | } 82 | const component2Decorator = connect< 83 | Component2Tstate, 84 | unknown, 85 | Omit, 86 | RootStateType 87 | >( 88 | ( 89 | state: RootStateType, 90 | ownProps: Omit, 91 | ): Component2Tstate => { 92 | mapStateSpy2() 93 | 94 | return { 95 | mappedProp: ownProps.list.map((id) => state.byId[id]), 96 | } 97 | }, 98 | ) 99 | interface Component2PropsType { 100 | list: number[] 101 | } 102 | 103 | const component2 = (props: Component2PropsType) => { 104 | renderSpy2() 105 | 106 | expect(props.list).toBe(component1StateList) 107 | 108 | return
Hello
109 | } 110 | 111 | const Component2 = component2Decorator(component2) 112 | 113 | rtl.render( 114 | 115 | 116 | , 117 | ) 118 | 119 | // 1. Initial render 120 | expect(mapStateSpy1).toHaveBeenCalledOnce() 121 | 122 | // 1.Initial render 123 | // 2. C1 useEffect 124 | expect(renderSpy1).toHaveBeenCalledTimes(2) 125 | 126 | // 1. Initial render 127 | expect(mapStateSpy2).toHaveBeenCalledOnce() 128 | 129 | // 1. Initial render 130 | expect(renderSpy2).toHaveBeenCalledOnce() 131 | 132 | rtl.act(() => { 133 | store.dispatch({ type: 'FOO' }) 134 | }) 135 | 136 | // 2. Store dispatch 137 | expect(mapStateSpy1).toHaveBeenCalledTimes(2) 138 | 139 | // 3. Store dispatch 140 | // 4. C1 useEffect 141 | expect(renderSpy1).toHaveBeenCalledTimes(4) 142 | 143 | // 2. Connect(C2) subscriber 144 | // 3. Ignored prev child props in re-render and re-runs mapState 145 | expect(mapStateSpy2).toHaveBeenCalledTimes(3) 146 | 147 | // 2. Batched update from nested subscriber / C1 re-render 148 | // Not sure why the differences across versions here 149 | // TODO: Figure out why this is 3 in React 18 but 2 in React 19 150 | const numFinalRenders = IS_REACT_18 ? 3 : 2 151 | expect(renderSpy2).toHaveBeenCalledTimes(numFinalRenders) 152 | }) 153 | }) 154 | }) 155 | -------------------------------------------------------------------------------- /test/hooks/hooks.withTypes.test.tsx: -------------------------------------------------------------------------------- 1 | import type { Action, ThunkAction } from '@reduxjs/toolkit' 2 | import { configureStore, createAsyncThunk, createSlice } from '@reduxjs/toolkit' 3 | import { useDispatch, useSelector, useStore } from 'react-redux' 4 | 5 | export interface CounterState { 6 | counter: number 7 | } 8 | 9 | const initialState: CounterState = { 10 | counter: 0, 11 | } 12 | 13 | export const counterSlice = createSlice({ 14 | name: 'counter', 15 | initialState, 16 | reducers: { 17 | increment(state) { 18 | state.counter++ 19 | }, 20 | }, 21 | }) 22 | 23 | export function fetchCount(amount = 1) { 24 | return new Promise<{ data: number }>((resolve) => 25 | setTimeout(() => resolve({ data: amount }), 500), 26 | ) 27 | } 28 | 29 | export const incrementAsync = createAsyncThunk( 30 | 'counter/fetchCount', 31 | async (amount: number) => { 32 | const response = await fetchCount(amount) 33 | // The value we return becomes the `fulfilled` action payload 34 | return response.data 35 | }, 36 | ) 37 | 38 | const { increment } = counterSlice.actions 39 | 40 | const counterStore = configureStore({ 41 | reducer: counterSlice.reducer, 42 | }) 43 | 44 | type AppStore = typeof counterStore 45 | type AppDispatch = typeof counterStore.dispatch 46 | type RootState = ReturnType 47 | type AppThunk = ThunkAction< 48 | ThunkReturnType, 49 | RootState, 50 | unknown, 51 | Action 52 | > 53 | 54 | describe('useSelector.withTypes()', () => { 55 | test('should return useSelector', () => { 56 | const useAppSelector = useSelector.withTypes() 57 | 58 | expect(useAppSelector).toBe(useSelector) 59 | }) 60 | }) 61 | 62 | describe('useDispatch.withTypes()', () => { 63 | test('should return useDispatch', () => { 64 | const useAppDispatch = useDispatch.withTypes() 65 | 66 | expect(useAppDispatch).toBe(useDispatch) 67 | }) 68 | }) 69 | 70 | describe('useStore.withTypes()', () => { 71 | test('should return useStore', () => { 72 | const useAppStore = useStore.withTypes() 73 | 74 | expect(useAppStore).toBe(useStore) 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /test/hooks/useDispatch.spec.tsx: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react' 2 | import React from 'react' 3 | import type { ProviderProps, ReactReduxContextValue } from 'react-redux' 4 | import { 5 | createDispatchHook, 6 | Provider as ProviderMock, 7 | useDispatch, 8 | } from 'react-redux' 9 | import { createStore } from 'redux' 10 | 11 | const store = createStore((c: number = 1): number => c + 1) 12 | const store2 = createStore((c: number = 1): number => c + 2) 13 | 14 | describe('React', () => { 15 | describe('hooks', () => { 16 | describe('useDispatch', () => { 17 | it("returns the store's dispatch function", () => { 18 | type PropsType = Omit 19 | const { result } = renderHook(() => useDispatch(), { 20 | wrapper: (props: PropsType) => ( 21 | 22 | ), 23 | }) 24 | 25 | expect(result.current).toBe(store.dispatch) 26 | }) 27 | }) 28 | describe('createDispatchHook', () => { 29 | it("returns the correct store's dispatch function", () => { 30 | const nestedContext = 31 | React.createContext(null) 32 | const useCustomDispatch = createDispatchHook(nestedContext) 33 | const { result } = renderHook(() => useDispatch(), { 34 | // eslint-disable-next-line react/prop-types 35 | wrapper: ({ children, ...props }: Omit) => ( 36 | 37 | 38 | {children} 39 | 40 | 41 | ), 42 | }) 43 | 44 | expect(result.current).toBe(store.dispatch) 45 | 46 | const { result: result2 } = renderHook(() => useCustomDispatch(), { 47 | // eslint-disable-next-line react/prop-types 48 | wrapper: ({ children, ...props }: Omit) => ( 49 | 50 | 51 | {children} 52 | 53 | 54 | ), 55 | }) 56 | 57 | expect(result2.current).toBe(store2.dispatch) 58 | }) 59 | }) 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /test/hooks/useReduxContext.spec.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createReduxContextHook, 3 | useReduxContext, 4 | } from '@internal/hooks/useReduxContext' 5 | import { renderHook } from '@testing-library/react' 6 | import { createContext } from 'react' 7 | import type { ReactReduxContextValue } from 'react-redux' 8 | 9 | describe('React', () => { 10 | describe('hooks', () => { 11 | describe('useReduxContext', () => { 12 | it('throws if component is not wrapped in provider', () => { 13 | const spy = vi.spyOn(console, 'error').mockImplementation(() => {}) 14 | 15 | expect(() => renderHook(() => useReduxContext())).toThrow( 16 | /could not find react-redux context value/, 17 | ) 18 | spy.mockRestore() 19 | }) 20 | }) 21 | describe('createReduxContextHook', () => { 22 | it('throws if component is not wrapped in provider', () => { 23 | const customContext = createContext(null) 24 | const useCustomReduxContext = createReduxContextHook(customContext) 25 | const spy = vi.spyOn(console, 'error').mockImplementation(() => {}) 26 | 27 | expect(() => renderHook(() => useCustomReduxContext())).toThrow( 28 | /could not find react-redux context value/, 29 | ) 30 | 31 | spy.mockRestore() 32 | }) 33 | }) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /test/setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/vitest' 2 | -------------------------------------------------------------------------------- /test/typeTestHelpers.ts: -------------------------------------------------------------------------------- 1 | export type IsEqual = 2 | (() => G extends A ? 1 : 2) extends () => G extends B ? 1 : 2 3 | ? true 4 | : false 5 | 6 | export type IfEquals = 7 | IsEqual extends true ? TypeIfEquals : TypeIfNotEquals 8 | -------------------------------------------------------------------------------- /test/typetests/counterApp.ts: -------------------------------------------------------------------------------- 1 | import type { Action, ThunkAction } from '@reduxjs/toolkit' 2 | import { configureStore, createAsyncThunk, createSlice } from '@reduxjs/toolkit' 3 | 4 | export interface CounterState { 5 | counter: number 6 | } 7 | 8 | const initialState: CounterState = { 9 | counter: 0, 10 | } 11 | 12 | export const counterSlice = createSlice({ 13 | name: 'counter', 14 | initialState, 15 | reducers: { 16 | increment(state) { 17 | state.counter++ 18 | }, 19 | }, 20 | }) 21 | 22 | export function fetchCount(amount = 1) { 23 | return new Promise<{ data: number }>((resolve) => 24 | setTimeout(() => resolve({ data: amount }), 500), 25 | ) 26 | } 27 | 28 | export const incrementAsync = createAsyncThunk( 29 | 'counter/fetchCount', 30 | async (amount: number) => { 31 | const response = await fetchCount(amount) 32 | // The value we return becomes the `fulfilled` action payload 33 | return response.data 34 | }, 35 | ) 36 | 37 | export const { increment } = counterSlice.actions 38 | 39 | const counterStore = configureStore({ 40 | reducer: counterSlice.reducer, 41 | middleware: (gdm) => gdm(), 42 | }) 43 | 44 | export type AppStore = typeof counterStore 45 | export type AppDispatch = typeof counterStore.dispatch 46 | export type RootState = ReturnType 47 | export type AppThunk = ThunkAction< 48 | ReturnType, 49 | RootState, 50 | unknown, 51 | Action 52 | > 53 | -------------------------------------------------------------------------------- /test/typetests/hooks.withTypes.test-d.tsx: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector, useStore } from 'react-redux' 2 | import type { AppDispatch, AppStore, RootState } from './counterApp' 3 | import { incrementAsync } from './counterApp' 4 | 5 | describe('type tests', () => { 6 | test('pre-typed hooks setup using `.withTypes()`', () => { 7 | const useAppSelector = useSelector.withTypes() 8 | 9 | const useAppDispatch = useDispatch.withTypes() 10 | 11 | const useAppStore = useStore.withTypes() 12 | 13 | const CounterComponent = () => { 14 | expectTypeOf(useAppSelector).toBeCallableWith((state) => state.counter) 15 | 16 | const dispatch = useAppDispatch() 17 | 18 | expectTypeOf(dispatch).toEqualTypeOf() 19 | 20 | const store = useAppStore() 21 | 22 | expectTypeOf(store).toEqualTypeOf() 23 | 24 | expectTypeOf(store.dispatch).toEqualTypeOf() 25 | 26 | const state = store.getState() 27 | 28 | expectTypeOf(state).toEqualTypeOf() 29 | 30 | expectTypeOf(state.counter).toBeNumber() 31 | 32 | // NOTE: We can't do `expectTypeOf(store.dispatch).toBeCallableWith(incrementAsync(1))` 33 | // because `.toBeCallableWith()` does not work well with function overloads. 34 | store.dispatch(incrementAsync(1)) 35 | 36 | expectTypeOf(store.dispatch).toEqualTypeOf(dispatch) 37 | 38 | return ( 39 |