├── .babelrc
├── .eslintrc
├── .gitattributes
├── .gitbook.yml
├── .github
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ └── help.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── docs
├── README.md
├── guides
│ ├── async-await.md
│ ├── chaining-actions.md
│ ├── comparison.md
│ ├── custom-delimiters.md
│ ├── custom-suffixes.md
│ ├── design-principles.md
│ ├── dispatching-promises.md
│ ├── null-values.md
│ ├── optimistic-updates.md
│ ├── passing-extra-values-to-reducer.md
│ ├── reducers.md
│ ├── redux-actions.md
│ ├── redux-promise-middleware-actions.md
│ └── rejected-promises.md
├── introduction.md
└── upgrading
│ ├── v4.md
│ ├── v5.md
│ └── v6.md
├── examples
├── catching-errors-with-middleware
│ ├── .gitignore
│ ├── README.md
│ ├── actions.js
│ ├── index.html
│ ├── index.js
│ ├── middleware.js
│ ├── package-lock.json
│ ├── package.json
│ └── store.js
├── catching-errors
│ ├── .gitignore
│ ├── README.md
│ ├── actions.js
│ ├── index.html
│ ├── index.js
│ ├── package-lock.json
│ ├── package.json
│ └── store.js
├── using-promise-actions
│ ├── .gitignore
│ ├── README.md
│ ├── actions.js
│ ├── index.html
│ ├── index.js
│ ├── package-lock.json
│ ├── package.json
│ └── store.js
├── using-promise-all
│ ├── .gitignore
│ ├── actions.js
│ ├── index.html
│ ├── index.js
│ ├── package-lock.json
│ ├── package.json
│ └── store.js
├── using-promise-middleware
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── index.js
│ ├── package-lock.json
│ ├── package.json
│ └── store.js
└── using-typescript
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── index.ts
│ ├── package-lock.json
│ ├── package.json
│ ├── store.ts
│ └── tsconfig.json
├── package-lock.json
├── package.json
├── src
├── index.d.ts
├── index.js
└── isPromise.js
├── test
├── .eslintrc
├── actions.spec.js
├── async-functions.spec.js
├── bluebird.spec.js
├── delimiter.spec.js
├── function.spec.js
├── module-versions.spec.js
├── module.spec.js
├── optimistic-updates.spec.js
├── promise-fulfilled.spec.js
├── promise-pending.spec.js
├── promise-rejected.spec.js
├── resolve-boolean.spec.js
├── resolve-null.spec.js
├── resolve-number.spec.js
└── utils
│ ├── createStore.js
│ ├── defaults.js
│ ├── modifierMiddleware.js
│ └── spyMiddleware.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [["@babel/env"], "@babel/react"],
3 | "env": {
4 | "es": {
5 | "presets": [["@babel/env", { "modules": false }]]
6 | },
7 | "test": {
8 | "presets": [
9 | [
10 | "@babel/env",
11 | {
12 | "targets": {
13 | "node": "current"
14 | }
15 | }
16 | ]
17 | ]
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-airbnb",
3 | "parser": "babel-eslint",
4 | "env": {
5 | "browser": true,
6 | "node": true,
7 | "es6": true,
8 | "mocha": true
9 | },
10 | "parserOptions": {
11 | "ecmaVersion": 2018,
12 | "sourceType": "module",
13 | "ecmaFeatures": {
14 | "jsx": true
15 | }
16 | },
17 | "rules": {
18 | "react/jsx-uses-react": 2,
19 | "react/jsx-uses-vars": 2,
20 | "react/react-in-jsx-scope": 2,
21 | "react/jsx-filename-extension": 0,
22 | "padded-blocks": 0,
23 | "block-scoped-var": 0,
24 | "comma-dangle": 0,
25 | "arrow-parens": 0,
26 | "no-extra-boolean-cast": 0,
27 | "import/no-extraneous-dependencies": 0,
28 | "import/first": 0,
29 | "import/no-unresolved": 0,
30 | "import/extensions": 0,
31 | "brace-style": 0
32 | },
33 | "plugins": [
34 | "react"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.js text=auto
2 |
--------------------------------------------------------------------------------
/.gitbook.yml:
--------------------------------------------------------------------------------
1 | structure:
2 | readme: README.md
3 | summary: docs/README.md
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
5 |
6 | ## Our Standards
7 | Examples of behavior that contributes to creating a positive environment include:
8 | * Using welcoming and inclusive language
9 | * Being respectful of differing viewpoints and experiences
10 | * Gracefully accepting constructive criticism
11 | * Focusing on what is best for the community
12 | * Showing empathy towards other community members
13 |
14 | Examples of unacceptable behavior by participants include:
15 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
16 | * Trolling, insulting/derogatory comments, and personal or political attacks
17 | * Public or private harassment
18 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
19 | * Other conduct which could reasonably be considered inappropriate in a professional setting
20 |
21 | ## Our Responsibilities
22 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
23 |
24 | 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, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
25 |
26 | ## Scope
27 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
28 |
29 | ## Enforcement
30 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting Patrick Burtchaell, the project owner, at [patrick@pburtchaell.com](mailto:patrick@pburtchaell.com). The project owner will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
31 |
32 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
33 |
34 | ## Attribution
35 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version].
36 |
37 | [homepage]: http://contributor-covenant.org
38 | [version]: http://contributor-covenant.org/version/1/4/
39 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 | Please familize yourself with the [GitHub Community Guidelines](https://help.github.com/articles/github-community-guidelines/) before contributing.
3 |
4 | ## Getting Started
5 |
6 | 1. Clone the repository
7 | 2. Install dependencies: `npm install`
8 | 3. Run tests: `npm test`
9 | 4. Run examples: `npm start`
10 |
11 | ### File Organization
12 | All code is written in JavaScript and transpiled using Babel.
13 |
14 | Source files are located in the `src` directory. Test files are located in the `test` directory.
15 |
16 | ### Git
17 | Git commit messages [should be written in the imperative](http://chris.beams.io/posts/git-commit/).
18 |
19 | A pre-commit hook will run tests and lint code when you make a commit. If needed, you can force a commit with `--no-verify`.
20 |
21 | ### Tests
22 | Tests are written in Mocha and code style is maintained with ESLint.
23 |
24 | ### Dependencies
25 | Please use Yarn instead of npm to upgrade dependencies.
26 |
27 | You can interactively upgrade dependencies:
28 |
29 | ```
30 | yarn upgrade-intractive
31 | ```
32 |
33 | Or you can upgrade dependencies one by one. For example, to upgrade Redux, you would run a command like this:
34 |
35 | ```
36 | yarn add redux@4.0.0 -D
37 | ```
38 |
39 | Install [synp](https://github.com/imsnif/synp):
40 |
41 | ```
42 | npm install -g synp
43 | ```
44 |
45 | Sync package-lock.json with yark.lock with:
46 |
47 | ```
48 | yarn generate-lockfile
49 | ```
50 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: pburtchaell
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | ---
5 | Hello! :wave:
6 |
7 | This template enables you to open an issue that is valuable for you, the project, and the GitHub community. :two_hearts:
8 |
9 | Before submitting an issue, please:
10 | 1. Search for related issues.
11 | 2. Agree the [code of conduct](/.github/CODE_OF_CONDUCT.md).
12 | 3. Read the [contributing guide](/.github/CONTRIBUTING.md).
13 |
14 | If you are new to open source, check out this 38 minute course on [how to contribute to open source on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github). It's free! :smile:
15 |
16 | ---
17 |
18 | ## Version Number
19 | The Redux version number and the Redux Promise Middleware version number.
20 |
21 | If your issue involves other libraries, please include the names and versions of those libraries
22 |
23 | ## Test Case
24 | Some code or a fork to help us reproduce the behavior.
25 |
26 | Consider creating a [JSBin](https://jsbin.com/?html,output) for the code example.
27 |
28 | ## Steps to Reproduce
29 | Steps to reproduce the behavior:
30 | 1. Go to '...'
31 | 2. Click on '....'
32 | 3. Scroll down to '....'
33 | 4. See error
34 |
35 | ## Expected Behavior
36 | A clear and concise description of what the bug is.
37 |
38 | ## Actual Behavior
39 | A clear and concise description of what you expected to happen.
40 |
41 | ## Screenshots
42 | If applicable, screenshots to help explain your problem.
43 |
44 | ## Additional Context
45 | Add any other context about the problem here.
46 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | ---
5 | Hello! :wave:
6 |
7 | This template enables you to open an issue that is valuable for you, the project, and the GitHub community. :two_hearts:
8 |
9 | Before submitting an issue, please:
10 | 1. Agree the [Code of Conduct](/.github/CODE_OF_CONDUCT.md).
11 | 2. Read the [Contributing Guide](/.github/CONTRIBUTING.md).
12 |
13 | If you are new to open source, check out this 38 minute course on [how to contribute to open source on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github). It's free! :smile:
14 |
15 | ---
16 |
17 | ## Problem
18 | A clear and concise description of what the problem is and how it relates to the project.
19 |
20 | ## Solution(s)
21 | A clear and concise description of what you want to happen.
22 |
23 | ## Alternatives
24 | A clear and concise description of any alternative solutions or features you've considered.
25 |
26 | ## Additional Context
27 | Add any other context or screenshots about the feature request here.
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/help.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Help
3 | about: Ask for help on a problem
4 | ---
5 | Issues should primarily be used for bug reports and feature requests.
6 |
7 | Before you ask for help on a problem, please answer the following questions:
8 |
9 | - Am I asking for help on a problem that is unreleated to or outside the scope of this project? (yes/no)
10 | - Can I ask for help on [StackOverflow](https://stackoverflow.com/questions/tagged/redux-promise-middleware) instead of GitHub? (yes/no)
11 |
12 | If you answered "yes" to one of those questions, please do not create an issue asking for help. I
13 |
14 | Instead, ask a question on StackOverflow with the "redux-promise-middleware" tag. We actively watch [StackOverflow for questions about the middleware](https://stackoverflow.com/questions/tagged/redux-promise-middleware) and will help you through that channel.
15 |
16 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Hello! :wave:
2 |
3 | This template enables you to open an issue that is valuable for you, the project, and the GitHub community. :two_hearts:
4 |
5 | Before submitting an issue, please:
6 | 1. Agree the [Code of Conduct](/.github/CODE_OF_CONDUCT.md).
7 | 2. Read the [Contributing Guide](/.github/CONTRIBUTING.md).
8 |
9 | If you are new to open source, check out this 38 minute course on [how to contribute to open source on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github). It's free! :smile:
10 |
11 | ---
12 |
13 | Before submitting a pull request, please make sure the following is done:
14 |
15 | 1. Add tests for new functionality or bug fixes, following the [red/green/refactor method](https://en.wikipedia.org/wiki/Test-driven_development#Development_style)
16 | 2. Ensure all tests pass: `npm test`
17 | 3. Update and/or add documentation
18 |
19 | **In order to merge your Pull Request, tests and documentation are required.**
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | example/build/
3 | node_modules/
4 | .idea/
5 | .coverage
6 | .coveralls.yml
7 | .DS_Store
8 | *.log
9 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .github/
2 | bin/
3 | docs/
4 | examples/
5 | jspm_packages/
6 | node_modules/
7 | src/
8 | test/
9 | .babelrc
10 | .eslintrc
11 | .npmignore
12 | .travis.yml
13 | webpack.config.js
14 | !src/index.d.ts
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "node" # latest stable release
4 | - "v8.10.0"
5 | cache: yarn
6 | script:
7 | - yarn test
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Patrick Burtchaell
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 | # Redux Promise Middleware
2 |
3 | [](https://www.npmjs.com/package/redux-promise-middleware)
4 |
5 | Redux Promise Middleware enables simple, yet robust handling of async action creators in [Redux](http://redux.js.org).
6 |
7 | ```js
8 | const asyncAction = () => ({
9 | type: 'PROMISE',
10 | payload: new Promise(...),
11 | })
12 | ```
13 |
14 | Given a single action with an async payload, the middleware transforms the action to a separate pending action and a separate fulfilled/rejected action, representing the states of the async action.
15 |
16 | The middleware can be combined with [Redux Thunk](https://github.com/gaearon/redux-thunk) to chain action creators.
17 |
18 | ```js
19 | const secondAction = (data) => ({
20 | type: 'SECOND',
21 | payload: {...},
22 | })
23 |
24 | const firstAction = () => {
25 | return (dispatch) => {
26 | const response = dispatch({
27 | type: 'FIRST',
28 | payload: new Promise(...),
29 | })
30 |
31 | response.then((data) => {
32 | dispatch(secondAction(data))
33 | })
34 | }
35 | }
36 | ```
37 |
38 | ## Documentation and Help
39 | - [Introduction](/docs/introduction.md)
40 | - [Guides](/docs/guides/)
41 | - [Examples](/examples)
42 |
43 | **Heads Up:** Version 6 includes some breaking changes. Check the [upgrading guide](docs/upgrading/v6.md) for help.
44 |
45 | ## Issues
46 | For bug reports and feature requests, [file an issue on GitHub](https://github.com/pburtchaell/redux-promise-middleware/issues/new).
47 |
48 | For help, [ask a question on StackOverflow](https://stackoverflow.com/questions/tagged/redux-promise-middleware).
49 |
50 | ## Releases
51 | - [Release History](https://github.com/pburtchaell/redux-promise-middleware/releases)
52 | - [Upgrade from 5.x to 6.0.0](docs/upgrading/v6.md)
53 | - [Upgrade from 4.x to 5.0.0](docs/upgrading/v5.md)
54 | - [Upgrade from 3.x to 4.0.0](docs/upgrading/v4.md)
55 |
56 | For older versions:
57 | - [5.x](https://github.com/pburtchaell/redux-promise-middleware/tree/5.1.1)
58 | - [4.x](https://github.com/pburtchaell/redux-promise-middleware/tree/4.4.0)
59 | - [3.x](https://github.com/pburtchaell/redux-promise-middleware/tree/3.3.0)
60 | - [2.x](https://github.com/pburtchaell/redux-promise-middleware/tree/2.4.0)
61 | - [1.x](https://github.com/pburtchaell/redux-promise-middleware/tree/1.0.0)
62 |
63 | ## Maintainers
64 | Please reach out to us if you have any questions or comments.
65 |
66 | Patrick Burtchaell (pburtchaell):
67 | - [GitHub](https://github.com/pburtchaell)
68 |
69 | Thomas Hudspith-Tatham (tomatau):
70 | - [GitHub](https://github.com/tomatau)
71 |
72 | ## License
73 |
74 | [Code licensed with the MIT License (MIT)](/LICENSE).
75 |
76 | [Documentation licensed with the CC BY-NC License](https://creativecommons.org/licenses/by-nc/4.0/).
77 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Table of Contents
2 |
3 | ## Getting Started
4 | - [Introduction](introduction.md)
5 | - [Design Principles](guides/design-principles.md)
6 |
7 | ## Guides
8 | - [Catching Errors Thrown by Rejected Promises](guides/rejected-promises.md)
9 | - [Chaining Actions](guides/chaining-actions.md)
10 | - [Passing extra values to reducer](guides/passing-extra-values-to-reducer.md)
11 | - [Comparison to other promise middleware](guides/comparison.md)
12 | - [Custom Type Delimiters](guides/custom-delimiters.md)
13 | - [Custom Types](guides/custom-suffixes.md)
14 | - [Optimistic Updates](guides/optimistic-updates.md)
15 | - [Use with Async/Await](guides/async-await.md)
16 | - [Use with Reducers](guides/reducers.md)
17 | - [Use with Redux Actions](guides/redux-actions.md)
18 | - [Use with Redux Promise Actions](guides/redux-promise-middleware-actions.md)
19 | - [Use with Promises Resolved with Null Values](guides/null-values.md)
20 |
21 | ## Upgrade Guides
22 | - [Upgrade from 5.x to 6.0.0](upgrading/v6.md)
23 | - [Upgrade from 4.x to 5.0.0](upgrading/v5.md)
24 | - [Upgrade from 3.x to 4.0.0](upgrading/v4.md)
25 | - [Release History](https://github.com/pburtchaell/redux-promise-middleware/releases)
26 |
--------------------------------------------------------------------------------
/docs/guides/async-await.md:
--------------------------------------------------------------------------------
1 | # Use with Async/Await
2 |
3 | Instead of chaining your async code with `.then().then().then()`, you can use async/await.
4 |
5 | Consider this example. First, request `fooData`, then request `barData` and exit the function (also resolving the promise).
6 |
7 | ```js
8 | {
9 | type: 'TYPE',
10 | async payload () {
11 | const fooData = await getFooData();
12 | const barData = await getBarData(fooData);
13 |
14 | return barData;
15 | }
16 | }
17 | ```
18 |
19 | Async/await can be combined with data for [optimistic updates](optimistic-updates.md):
20 |
21 | ```js
22 | {
23 | type: 'OPTIMISTIC_TYPE',
24 | payload: {
25 | data: {
26 | ...
27 | },
28 | async promise () {
29 | ...
30 | }
31 | }
32 | }
33 | ```
34 |
35 | Please note there is no need to `return await` in an async function. [See this ESLint rule for more details](https://eslint.org/docs/rules/no-return-await).
36 |
--------------------------------------------------------------------------------
/docs/guides/chaining-actions.md:
--------------------------------------------------------------------------------
1 | # Chaining Actions
2 |
3 | When a promise is resolved, one might want to dispatch additional actions in response. One example could be changing the route after a user is successfully signed in. Another could be showing an error message after a request fails.
4 |
5 | First, note this behavior uses thunks. You will need to include [Redux Thunk](https://github.com/gaearon/redux-thunk) in your middleware stack.
6 |
7 | *Note: Redux Thunk is a middleware that enables action creators to return a function instead of an object ([hence the name "thunk"](https://en.wikipedia.org/wiki/Thunk)). The returned function is called with a `dispatch` argument, which is what you can use to chain actions.*
8 |
9 |
10 | ```js
11 | const foo = () => {
12 | return dispatch => {
13 |
14 | return dispatch({
15 | type: 'TYPE',
16 | payload: new Promise()
17 | }).then(() => dispatch(bar()));
18 | };
19 | }
20 | ```
21 |
22 | If you need to chain several actions, using `Promise.all` is suggested.
23 |
24 | ```js
25 | const foo = () => {
26 | return dispatch => {
27 |
28 | return dispatch({
29 | type: 'TYPE',
30 | payload: Promise.all([
31 | dispatch(bar()),
32 | dispatch(baz())
33 | ])
34 | });
35 | };
36 | }
37 | ```
38 |
39 | When handling a promise with `then`, the parameter is an object with two properties: (1) the "value" (if the promise is fulfilled) or the "reason" (if the promise is rejected) and (2) the object of the dispatched action.
40 |
41 | ```js
42 | // fulfilled promise
43 | const foo = () => {
44 | return dispatch => {
45 |
46 | return dispatch({
47 | type: 'FOO',
48 | payload: new Promise(resolve => {
49 | resolve('foo'); // resolve the promise with the value 'foo'
50 | })
51 | }).then(({ value, action }) => {
52 | console.log(value); // => 'foo'
53 | console.log(action.type); // => 'FOO_FULFILLED'
54 | });
55 | };
56 | }
57 |
58 | // rejected promise
59 | const bar = () => {
60 | return dispatch => {
61 |
62 | return dispatch({
63 | type: 'BAR',
64 | payload: new Promise(() => {
65 | throw new Error('foo'); // reject the promise for the reason 'bar'
66 | })
67 | }).then(() => null, error => {
68 | console.log(error instanceof Error) // => true
69 | console.log(error.message); // => 'foo'
70 | });
71 | };
72 | }
73 | ```
74 |
75 | Rejected promises can also be handled with `.catch()`.
76 |
77 | ```js
78 | // rejected promise with throw
79 | const baz = () => {
80 | return dispatch => {
81 |
82 | return dispatch({
83 | type: 'BAZ',
84 | payload: new Promise(() => {
85 | throw new Error(); // throw an error
86 | })
87 | }).catch((error) => {
88 | console.log(error instanceof Error) // => true
89 | });
90 | };
91 | }
92 | ```
93 |
--------------------------------------------------------------------------------
/docs/guides/comparison.md:
--------------------------------------------------------------------------------
1 | # What is the difference between this and other promise middleware?
2 |
3 | For context, this question was originally asked in issue [#27](https://github.com/pburtchaell/redux-promise-middleware/issues/27).
4 |
5 | ## [acdlite/redux-promise](https://github.com/acdlite/redux-promise)
6 |
7 | Both middleware solve the same problem, but the implementation is different. Promise middleware dispatches a pending action in addition to a rejected or fulfilled action. This is a feature acdlite/redux-promise has not implemented at time of writing (November 2015). The pending action enables optimistic updates and describes an unsettled promise.
8 |
9 | Both middleware use the [Flux Standard Action](https://github.com/acdlite/flux-standard-action) specification.
10 |
11 | One could also argue the API for promise middleware is more transparent and easier to integrate. For example, you do not need to use [redux-actions](https://github.com/acdlite/redux-actions).
12 |
--------------------------------------------------------------------------------
/docs/guides/custom-delimiters.md:
--------------------------------------------------------------------------------
1 | # Type Delimiter Configuration
2 |
3 | In the case you need to use different type delimiters, you can configure this globally for all actions. By default, the middleware uses a underscore `_` delimiter.
4 |
5 | For example, given `FOO` async action, `PENDING` type will be appended with a underscore `_` delimiter.
6 |
7 | ```js
8 | {
9 | type: 'FOO_PENDING'
10 | }
11 | ```
12 |
13 | To change the default, supply an optional configuration object to the middleware with the `promiseTypeDelimiter` property. This property accepts a new string to use as the delimiter.
14 |
15 | ```js
16 | import { createPromise } from 'redux-promise-middleware';
17 |
18 | applyMiddleware(
19 | createPromise({
20 | promiseTypeDelimiter: '/'
21 | })
22 | )
23 | ```
24 |
25 | With this configuration, given `FOO` async action, the type will be appended with a forward slash `/` delimiter.
26 |
27 | ```js
28 | {
29 | type: 'FOO/PENDING'
30 | }
31 | ```
32 |
33 | Finally, if you are using a library like [type-to-reducer](https://github.com/tomatau/type-to-reducer), you'll also need to [configure it to handle the custom delimiter]](https://github.com/tomatau/type-to-reducer#custom-type-delimiter).
34 |
--------------------------------------------------------------------------------
/docs/guides/custom-suffixes.md:
--------------------------------------------------------------------------------
1 | # Type Suffix Configuration
2 |
3 | In the case you need to use different type suffixes, you can configure this globally for all actions or locally (action-by-action).
4 |
5 | To change suffixes, you can supply an optional configuration object to the middleware. This object accepts an array of suffix strings that can be used instead of the default with a key of `promiseTypeSuffixes`.
6 |
7 | ```js
8 | import { createPromise } from 'redux-promise-middleware';
9 |
10 | applyMiddleware(
11 | createPromise({
12 | promiseTypeSuffixes: ['LOADING', 'SUCCESS', 'ERROR']
13 | })
14 | )
15 | ```
16 |
--------------------------------------------------------------------------------
/docs/guides/design-principles.md:
--------------------------------------------------------------------------------
1 | # Design Principles
2 |
3 | ## Promise Objects Are State Machines
4 |
5 | A promise object is a "machine" holding one of two states:
6 |
7 | 1. Pending
8 | 2. Settled
9 |
10 | A settled state is the deffered result of the promise. This state will be either (a) rejected or (b) resolved. The rejected state throws an error and the fulfilled state returns either null or a value.
11 |
12 | See more: [ECMAScript 25.4 Spec: Promise Objects](https://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects).
13 |
14 | ## Action Objects Describe State Changes to the Store
15 |
16 | An action object describes changes to the store. Actions are the only source of information for the store.
17 |
18 | See more: [Redux Documentation](http://redux.js.org/docs/basics/Actions.html).
19 |
20 | ## Asynchronous Action Objects Describe the Promise Object State
21 |
22 | Promise middleware dispatches "asynchronous" action objects describing the state of the promise:
23 |
24 | 1. Pending action
25 | 2. Fullfilled or rejcted action (settled)
26 |
27 | This affords asynchronous updates to the store.
28 |
29 | Another way of thinking of this is promise middleware abstracts the two states of an promise object to two action objects.
30 |
31 | ## Use Flux Standard Action (FSA)
32 |
33 | Promise middleware dispatches actions in compliance with [the Flux Standard Action](https://github.com/acdlite/flux-standard-action) reccommendations.
34 |
--------------------------------------------------------------------------------
/docs/guides/dispatching-promises.md:
--------------------------------------------------------------------------------
1 | # Dispatching Promises
2 |
3 | ## Implicitly
4 |
5 | ```js
6 | const foo = () => ({
7 | type: 'FOO',
8 | payload: new Promise()
9 | });
10 | ```
11 |
12 | ## Explicitly
13 |
14 | ```js
15 | const foo = () => ({
16 | type: 'FOO',
17 | payload: {
18 | promise: new Promise()
19 | }
20 | });
21 | ```
22 |
23 | ## Async/Await
24 |
25 | For more on using async/await, [see the guide](async-await.md).
26 |
27 | ```js
28 | const foo = () => ({
29 | type: 'FOO',
30 | async payload() {
31 | const data = await getDataFromApi():
32 |
33 | return data;
34 | }
35 | });
36 | ```
37 |
--------------------------------------------------------------------------------
/docs/guides/null-values.md:
--------------------------------------------------------------------------------
1 | # Promises Resolved with Null Values
2 |
3 | If a promise is resolved with a `null` or `undefined` value, the fullfilled action will not include a payload property. This is because actions describe changes in state. Consider the following two actions:
4 |
5 | ```
6 | // A
7 | {
8 | type: 'ACTION`,
9 | meta: ...
10 | }
11 |
12 | // B
13 | {
14 | type: 'ACTION'
15 | payload: null,
16 | meta: ...
17 | }
18 | ```
19 |
20 | Both actions describe the same change in state. This is why, when you resolve with `null` or `undefined`, the payload property is not included. It would be redundant to include it.
21 |
--------------------------------------------------------------------------------
/docs/guides/optimistic-updates.md:
--------------------------------------------------------------------------------
1 | # Optimistic Updates
2 |
3 | ## What are optimistic updates?
4 |
5 | > Optimistic [updates to a UI] don't wait for an operation to finish to update to the final state. They immediately switch to the final state, showing fake data for the time while the real operation is still in-progress.
6 | > - Igor Mandrigin, UX Planet
7 |
8 | ["Optimistic UI,"](https://uxplanet.org/optimistic-1000-34d9eefe4c05#.twmtjnmaw) a short article by UX Planet, is a great summary if you are unfamiliar with the practice. In short, it's the practice of updating the UI when a request is pending. This makes the experience more fluid for users.
9 |
10 | Because promise middleware dispatches a pending action, it is easy to optimistically update the Redux store.
11 |
12 | ## Code
13 |
14 | You may pass an optional `data` object. This is dispatched from the pending action and is useful for optimistic updates.
15 |
16 | ```js
17 | const foo = data => ({
18 | type: 'FOO',
19 | payload: {
20 | promise: new Promise(),
21 | data: data
22 | }
23 | });
24 | ```
25 |
26 | Considering the action creator above, the pending action would be described as:
27 |
28 | ```js
29 | // pending action
30 | {
31 | type: 'FOO_PENDING',
32 | payload: data
33 | }
34 | ```
35 |
--------------------------------------------------------------------------------
/docs/guides/passing-extra-values-to-reducer.md:
--------------------------------------------------------------------------------
1 | # Passing extra values to reducer
2 |
3 | When returning an object from an action, only the payload and type attributes would be passed to the reducer.
4 |
5 | Consider this example:
6 |
7 | ```
8 | {
9 | type: 'ACTION`,
10 | payload: new Promise(),
11 | extraValue: 123
12 | }
13 | ```
14 |
15 | Here, reducer would not receive the `extraValue` property.
16 | In order to pass extra attributes, you need to include it as part of the `meta` attribute.
17 |
18 |
19 | ```
20 | {
21 | type: 'ACTION`,
22 | payload: new Promise(),
23 | meta: {extraValue: 123}
24 | }
25 | ```
26 |
--------------------------------------------------------------------------------
/docs/guides/reducers.md:
--------------------------------------------------------------------------------
1 | # Use with Reducers
2 |
3 | Handling actions dispatched by Redux promise middleware is simple by default.
4 |
5 | ```js
6 | const FOO_TYPE = 'FOO';
7 |
8 | // Dispatch the action
9 | const fooActionCreator = () => ({
10 | type: FOO_TYPE
11 | payload: Promise.resolve('foo')
12 | });
13 |
14 | // Handle the action
15 | const fooReducer = (state = {}, action) => {
16 | switch(action.type) {
17 | case `${FOO_TYPE}_PENDING`:
18 | return;
19 |
20 | case `${FOO_TYPE}_FULFILLED`:
21 | return {
22 | isFulfilled: true,
23 | data: action.payload
24 | };
25 |
26 | case `${FOO_TYPE}_REJECTED`:
27 | return {
28 | isRejected: true,
29 | error: action.payload
30 | };
31 |
32 | default: return state;
33 | }
34 | }
35 | ```
36 |
37 | ### Action Types
38 |
39 | Optionally, the default promise suffixes can be imported from this module.
40 |
41 | ```js
42 | import { ActionType } from 'redux-promise-middleware';
43 | ```
44 |
45 | This can be useful in your reducers to ensure types are more robust.
46 |
47 | ```js
48 | const FOO_PENDING = `FOO_${ActionType.Pending}`;
49 | const FOO_FULFILLED = `FOO_${ActionType.Fulfilled}`;
50 | const FOO_REJECTED = `FOO_${ActionType.Rejected}`;
51 | ```
52 |
53 | ## Large Applications
54 |
55 | In a large application with many async actions, having many reducers with this same structure can grow redundant.
56 |
57 | To keep your reducers [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself), you might see value in using a solution like [type-to-reducer](https://github.com/tomatau/type-to-reducer).
58 |
59 | ```js
60 | import typeToReducer from 'type-to-reducer';
61 |
62 | const BAR_TYPE = 'BAR';
63 |
64 | // Dispatch the action
65 | const barActionCreator = () => ({
66 | type: BAR_TYPE
67 | payload: Promise.resolve('bar')
68 | });
69 |
70 | // Handle the action
71 | const barReducer = typeToReducer({
72 | [BAR_TYPE]: {
73 | PENDING: () => ({
74 | // ...
75 | }),
76 | REJECTED: (state, action) => ({
77 | isRejected: true,
78 | error: action.payload
79 | }),
80 | FULFILLED: (state, action) => ({
81 | isFulfilled: true,
82 | data: action.payload
83 | })
84 | }
85 | }, {});
86 | ```
87 |
--------------------------------------------------------------------------------
/docs/guides/redux-actions.md:
--------------------------------------------------------------------------------
1 | # Use with Redux Actions
2 |
3 | To use this middleware with Redux Actions, return a promise from the payload creator. Note this example is experimental and not tested; it may not work as expected.
4 |
5 | ```js
6 | // Create an async action
7 | const fooAction = createAction('FOO', async () => {
8 | const { response } = await asyncFoo();
9 | return response;
10 | });
11 |
12 | // Use async action
13 | fooAction('123')
14 | ```
15 |
16 | This would dispatch `FOO_PENDING` and `FOO_FULFILLED` with the data as the payload.
17 |
--------------------------------------------------------------------------------
/docs/guides/redux-promise-middleware-actions.md:
--------------------------------------------------------------------------------
1 | # Use with Redux Promise Middleware Actions
2 |
3 | To use this middleware with [redux-promise-middleware-actions](https://github.com/omichelsen/redux-promise-middleware-actions), invoke `createAsyncAction` and return a promise from the payload creator.
4 |
5 | ```js
6 | import { createAsyncAction } from 'redux-promise-middleware-actions';
7 |
8 | // Create an async action
9 | const fooAction = createAsyncAction('FOO', async (url) => {
10 | const response = await fetch(url);
11 | return response.json();
12 | });
13 |
14 | // Use async action
15 | dispatch(fooAction('https://some.url'));
16 | ```
17 |
18 | This would dispatch `FOO_PENDING` and `FOO_FULFILLED` with the data as the payload. `fooAction` has a reference to these action creators on the object itself, e.g. `fooAction.pending()`. You can listen for these in the reducer like this:
19 |
20 | ```js
21 | const reducer = (state, action) => {
22 | switch (action.type) {
23 | case String(fooAction.pending):
24 | return {
25 | ...state,
26 | pending: true,
27 | };
28 | case String(fooAction.fulfilled):
29 | return {
30 | ...state,
31 | data: action.payload,
32 | error: undefined,
33 | pending: false,
34 | };
35 | case String(fooAction.rejected):
36 | return {
37 | ...state,
38 | error: action.payload,
39 | pending: false,
40 | };
41 | default:
42 | return state;
43 | }
44 | };
45 | ```
46 |
--------------------------------------------------------------------------------
/docs/guides/rejected-promises.md:
--------------------------------------------------------------------------------
1 | # Catching Errors Thrown by Rejected Promises
2 |
3 | ## The Principle
4 |
5 | Redux promise middleware dispatches an action for a rejected promise, but does not catch the error thrown. **This is an expected behavior.** Because the error is not caught, you will (in most cases) get an "uncaught" warning in the developer console. Again, this is an expected behavior.
6 |
7 | By principle, it's your application's responsibility to catch the error thrown by the rejected promise. It's not the responsibility of the middleware.
8 |
9 | ## How to Catch Promises
10 |
11 | However, you probably want to catch the error. Here's some suggested approaches/solutions to this.
12 |
13 | 1. Catch/handle the error "globally" in error handling middleware
14 | 2. Catch/handle the error "locally" at the action creator
15 |
16 | ## Catching Errors Locally
17 |
18 | Generally, it'll make sense to use local error handling to directly control the "side effect(s)" of an error.
19 |
20 | This can be done by dispatching some specific action. Here's an example of handling an error locally at the action creator.
21 |
22 | ```js
23 | export function foo() {
24 | return dispatch => ({
25 | type: 'FOO_ACTION',
26 |
27 | // Throw an error
28 | payload: new Promise(() => {
29 | throw new Error('foo');
30 | })
31 |
32 | // Catch the error locally
33 | }).catch(error => {
34 | console.log(error.message); // 'foo'
35 |
36 | // Dispatch a second action in response to the error
37 | dispatch(bar());
38 | });
39 | }
40 | ```
41 |
42 | Please note this example requires [Redux Thunk](https://github.com/gaearon/redux-thunk).
43 |
44 | ## Catching Errors Globally
45 |
46 | In some cases, it might make sense to "globally" catch all errors or all errors of a certain action type. To give an example, you might want to show a alert modal whenever an error is thrown.
47 |
48 | [There is an example of how this middleware would work](https://github.com/pburtchaell/redux-promise-middleware/tree/master/examples/catching-errors-with-middleware/middleware.js). Note that any middleware you write will see all rejected promises before they're passed up to action creators for handling.
49 |
50 | ## The unhandledrejection Event
51 |
52 | A third option is to handle all rejected promises (not just promises used with Redux promise middleware) using an [`unhandledrejection`](https://developer.mozilla.org/en-US/docs/Web/Events/unhandledrejection) event. I wouldn't reccommend this because it assumes too much and could be difficult to debug, but there might be a case where it is useful for your program.
53 |
--------------------------------------------------------------------------------
/docs/introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | ## Installation
4 |
5 | First, install the middleware.
6 |
7 | ```
8 | npm i redux-promise-middleware -s
9 | ```
10 |
11 | ## Setup
12 |
13 | Import the middleware and include it in `applyMiddleware` when creating the Redux store:
14 |
15 | ```js
16 | import promise from 'redux-promise-middleware'
17 |
18 | composeStoreWithMiddleware = applyMiddleware(
19 | promise,
20 | )(createStore)
21 | ```
22 |
23 | ## Use
24 |
25 | Dispatch a promise as the value of the `payload` property of the action.
26 |
27 | ```js
28 | const foo = () => ({
29 | type: 'FOO',
30 | payload: new Promise()
31 | });
32 | ```
33 |
34 | A pending action is immediately dispatched.
35 |
36 | ```js
37 | {
38 | type: 'FOO_PENDING'
39 | }
40 | ```
41 |
42 | Once the promise is settled, a second action will be dispatched. If the promise is resolved a fulfilled action is dispatched.
43 |
44 | ```js
45 | {
46 | type: 'FOO_FULFILLED'
47 | payload: {
48 | ...
49 | }
50 | }
51 | ```
52 |
53 | On the other hand, if the promise is rejected, a rejected action is dispatched.
54 |
55 | ```js
56 | {
57 | type: 'FOO_REJECTED'
58 | error: true,
59 | payload: {
60 | ...
61 | }
62 | }
63 | ```
64 |
65 | That's it!
66 |
67 | ## Further Reading
68 |
69 | - [Catching Errors Thrown by Rejected Promises](guides/rejected-promises.md)
70 | - [Use with Reducers](guides/reducers.md)
71 | - [Optimistic Updates](guides/optimistic-updates.md)
72 | - [Design Principles](guides/design-principles.md)
73 |
74 | ---
75 | Copyright (c) 2017 Patrick Burtchaell. [Code licensed with the MIT License (MIT)](/LICENSE). [Documentation licensed with the CC BY-NC License](LICENSE).
76 |
--------------------------------------------------------------------------------
/docs/upgrading/v4.md:
--------------------------------------------------------------------------------
1 | ## Upgrade from 3.x to 4.0.0
2 |
3 | This release introduces changes to error handling.
4 |
5 | Previously, the parameter of the rejected promise callback was both the dispatched action and an error object. The middleware also *always* constructed a new error object, which caused unexpected mutation and circular references.
6 |
7 | **Now, the parameter of the rejected promise callback is the value of `reject`.** The middleware does not construct a new error; it is your responsibility to make sure the promise is rejected with an Error object.
8 |
9 | ```js
10 | // before
11 | const bar = () => ({
12 | type: 'FOO',
13 | payload: new Promise(() => {
14 | reject('foo');
15 | })
16 | });.then(() => null, ({ reason, action }) => {
17 | console.log(action.type): // => 'FOO'
18 | console.log(reason.message); // => 'foo'
19 | });
20 |
21 | // after
22 | const bar = () => ({
23 | type: 'FOO',
24 | payload: new Promise(() => {
25 |
26 | /**
27 | * Make sure the promise is rejected with an error. You
28 | * can also use `reject(new Error('foo'));`. It's a best
29 | * practice to reject a promise with an Error object.
30 | */
31 | throw new Error('foo');
32 | })
33 | });.then(() => null, error => {
34 | console.log(error instanceof Error); // => true
35 | console.log(error.message); // => 'foo'
36 | });
37 | ```
38 |
39 | # 2.x to 3.0.0
40 |
41 | This release introduces some major changes to the functionality of the middleware:
42 |
43 | **First, the middleware returns a promise instead of the action.**
44 |
45 | ```js
46 | // before
47 | const foo = () => ({
48 | type: 'FOO',
49 | payload: {
50 | promise: Promise.resolve('foo')
51 | }
52 | });
53 |
54 | foo().action.promise.then(value => {
55 | console.log(value); // => 'foo'
56 | });
57 |
58 | // after
59 | const bar = () => ({
60 | type: 'BAR',
61 | payload: Promise.resolve('bar')
62 | });
63 |
64 | bar().then(({ value }) => {
65 | console.log(value); // => 'bar'
66 | });
67 | ```
68 |
69 | **Second, a new promise is created** so `.then()` and `.catch()` work as expected.
70 |
71 | ``` js
72 | // before
73 | const foo = () => ({
74 | type: 'FOO',
75 | payload: {
76 | promise: Promise.reject('foo')
77 | }
78 | });
79 |
80 | foo().action.promise.then(
81 | value => {
82 | console.log(value); // => 'foo'
83 | },
84 | reason => {
85 | // nothing happens
86 | }
87 | );
88 |
89 | // after
90 | const bar = () => ({
91 | type: 'BAR',
92 | payload: Promise.reject('bar')
93 | });
94 |
95 | bar().then(
96 | ({ value }) => {
97 | // ...
98 | },
99 | ({ reason }) => {
100 | console.log(reason); // => 'bar'
101 | }
102 | );
103 |
104 | const baz = () => ({
105 | type: 'BAZ',
106 | payload: new Promise((resolve, reject) => {
107 | throw 'baz'
108 | })
109 | });
110 |
111 | bar().catch(({ reason }) => {
112 | console.log(reason) // => 'baz'
113 | });
114 | ```
115 |
116 | **Third, promises can be explicitly or implicitly in the action object.**
117 |
118 | ```js
119 | // before
120 | const foo = () => ({
121 | type: 'FOO',
122 | payload: {
123 | promise: Promise.resolve()
124 | }
125 | });
126 |
127 | // after, with implicit promise as the value of the 'payload' property
128 | const bar = () => ({
129 | type: 'BAR',
130 | payload: Promise.resolve()
131 | });
132 | ```
133 |
134 | Of course, if you prefer the explicit syntax, this still works. This syntax is also required for optimistic updates.
135 |
136 | ```js
137 | // after, but with explicit 'promise' property and 'data' property
138 | const bar = () => ({
139 | type: 'BAZ',
140 | payload: {
141 | promise: Promise.resolve(),
142 | data: ...
143 | }
144 | });
145 | ```
146 |
147 | **Fourth, thunks are no longer bound to the promise.** If you are chaining actions with Redux Thunk, this is critical change.
148 |
149 | ```js
150 | // before, with Redux Thunk
151 | const foo = () => ({
152 | type: 'FOO',
153 | payload: {
154 | promise: new Promise((resolve, reject) => {
155 | ...
156 | }).then(
157 | value => (action, dispatch) => {
158 | // handle fulfilled
159 | dispatch(someSuccessHandlerActionCreator());
160 | },
161 | reason => (action, dispatch) => {
162 | // handle rejected
163 | dispatch(someErrorHandlerActionCreator());
164 | }
165 | )
166 | }
167 | });
168 |
169 | // after, with Redux Thunk
170 | const bar = () => {
171 | return (dispatch, getState) => {
172 |
173 | return dispatch({
174 | type: 'FOO',
175 | payload: Promise.resolve('foo')
176 | }).then(
177 | ({ value, action }) => {
178 | console.log(value); // => 'foo'
179 | console.log(action.type); // => 'FOO_FULFILLED'
180 | dispatch(someSuccessHandlerActionCreator());
181 | },
182 | ({ reason, action }) => {
183 | // handle rejected
184 | dispatch(someErrorHandlerActionCreator());
185 | }
186 | );
187 | };
188 | };
189 | ```
--------------------------------------------------------------------------------
/docs/upgrading/v5.md:
--------------------------------------------------------------------------------
1 | # Upgrade from 4.x to 5.0.0
2 |
3 | Previously, the `promiseTypeSeparator` config property was used to change the character used to join type strings.
4 |
5 | Now, the `promiseTypeDelimiter` config property is used. Why? Because [delimiters](https://en.wikipedia.org/wiki/Delimiter) are one or more characters used to specify the boundaries in strings. It’s s delimiter, not a separator!
6 |
7 | ```js
8 | applyMiddleware(
9 | promiseMiddleware({
10 | promiseTypeDelimiter: '/'
11 | })
12 | )
13 | ```
14 |
15 | With this configuration, given `FOO` async action, the type will be appended with a forward slash `/` delimiter.
16 |
17 | ```js
18 | {
19 | type: 'FOO/PENDING'
20 | }
21 | ```
--------------------------------------------------------------------------------
/docs/upgrading/v6.md:
--------------------------------------------------------------------------------
1 | # Upgrade from 5.x to 6.0.0
2 |
3 | This version includes changes to the public API, making it easier to import and use the middleware.
4 |
5 | ## New: Preconfigured by Default
6 |
7 | ### Before
8 |
9 | Previously, the middleware need to be instantiated with an optional configuration.
10 |
11 | ```js
12 | import promiseMiddleware from 'redux-promise-middleware'
13 |
14 | applyMiddleware(
15 | promiseMiddleware({
16 | // Optional configuration
17 | }),
18 | )(createStore)
19 | ```
20 |
21 | This implementation enabled custom configuration, but, for most implementations, it is uncessary overhead.
22 |
23 | ### After
24 |
25 | Now, the default export is preconfigured and ready to go.
26 |
27 | ```js
28 | import promise from 'redux-promise-middleware'
29 |
30 | applyMiddleware(
31 | promise,
32 | )(createStore)
33 | ```
34 |
35 | The middleware still supports custom configuration: import `createPromise` and pass in the configuration object.
36 |
37 | ```js
38 | import { createPromise } from 'redux-promise-middleware'
39 |
40 | applyMiddleware(
41 | createPromise({
42 | // Custom configuration
43 | typeDelimiter: '/',
44 | }),
45 | )(createStore)
46 | ```
47 |
48 | ## New: `ActionType` Export
49 |
50 | ### Before
51 |
52 |
53 | ```js
54 | import { PENDING, FULFILLED, REJECTED } from 'redux-promise-middleware'
55 | ```
56 |
57 | Previously, the middleware exported three string constants. One each for the pending, fulfilled and rejected action types. This is useful for reducers, for example:
58 |
59 | ```js
60 | const reducer = (state = {}, action) => {
61 | switch(action.type) {
62 | case `FOO_${PENDING}`:
63 | // ..
64 |
65 | case `FOO_${FULFILLED}`:
66 | // ...
67 |
68 | default: return state;
69 | }
70 | }
71 | ```
72 |
73 | This is a nice affordance, it could be better design if the action types were exported as an enum (E.g., an object, since this is just JavaScript), as opposed three individual strings.
74 |
75 | ### After
76 |
77 | ```js
78 | import { ActionType } from 'redux-promise-middleware'
79 | ```
80 |
81 | Now, the action types are exported as one enum.
82 |
83 | ```js
84 | const reducer = (state = {}, action) => {
85 | switch(action.type) {
86 | case `FOO_${ActionType.Pending}`:
87 | // ..
88 |
89 | case `FOO_${ActionType.Fulfilled}`:
90 | // ...
91 |
92 | case `FOO_${ActionType.Rejected}`:
93 | // ...
94 |
95 | default: return state;
96 | }
97 | }
98 | ```
99 |
100 | Using action types is entirely optional. One one hand, code benefits from more robust types and is less prone to static errors, but, on another hand, you and your team spends more time and effort writing the code.
101 |
102 | At the end of the day, you can also just use regular strings like any other action.
103 |
104 |
105 | ```js
106 | const reducer = (state = {}, action) => {
107 | switch(action.type) {
108 | case `FOO_PENDING`:
109 | // ..
110 |
111 | case `FOO_FULFILLED`:
112 | // ...
113 |
114 | case `FOO_REJECTED`:
115 | // ...
116 |
117 | default: return state;
118 | }
119 | }
120 | ```
121 |
--------------------------------------------------------------------------------
/examples/catching-errors-with-middleware/.gitignore:
--------------------------------------------------------------------------------
1 | .cache
2 | dist
3 | node_modules
--------------------------------------------------------------------------------
/examples/catching-errors-with-middleware/README.md:
--------------------------------------------------------------------------------
1 | # Catching Errors with Middleware
2 |
3 | This example demonstrates how to catch a rejected promise.
4 |
5 | ## Getting Started
6 |
7 | - Clone this repository to your computer
8 | - Open the folder for this example
9 | - Run `npm i` to install dependencies
10 | - Run `npm start` to start the example
11 | - Open `http://localhost:1234` in a web browser
12 |
--------------------------------------------------------------------------------
/examples/catching-errors-with-middleware/actions.js:
--------------------------------------------------------------------------------
1 | export const foo = () => ({
2 | type: 'FOO',
3 | // When you throw an error, always instantiate a new Error object with `new Error()`
4 | payload: Promise.reject(new Error('foo')),
5 | });
6 |
7 | export const bar = () => ({
8 | type: 'BAR',
9 | payload: Promise.reject(new Error('bar')),
10 | });
11 |
--------------------------------------------------------------------------------
/examples/catching-errors-with-middleware/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Catching Errors with Middleware
5 |
6 |
7 |
--------------------------------------------------------------------------------
/examples/catching-errors-with-middleware/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import * as actions from './actions';
4 | import store from './store';
5 |
6 | const App = () => (
7 | <>
8 |
9 |
Catching Errors with Middleware
10 |
This example demonstrates how to catch a rejected promise with an error middleware.
11 |
Open the Developer Console to see logs for the dispatched actions.
12 |
13 |
14 |
15 | <>
16 |
Example 1: Foo
17 |
The "foo" action throws an error and the error is caught at the middleware.
18 |
You'll see a normal error message in the console.
19 |
22 | >
23 | <>
24 |
Example 2: Bar
25 |
The "bar " action throws an error and is uncaught.
26 |
You'll see an "Uncaught (in promise) Error" message in the console.
27 |
30 | >
31 |
32 | >
33 | );
34 |
35 | render(, document.querySelector('#mount'));
36 |
--------------------------------------------------------------------------------
/examples/catching-errors-with-middleware/middleware.js:
--------------------------------------------------------------------------------
1 | import isPromise from 'is-promise';
2 | import _ from 'underscore';
3 |
4 | export default function errorMiddleware() {
5 | return next => action => {
6 | const types = {
7 | FOO: true,
8 | };
9 |
10 | // If not a promise, continue on
11 | if (!isPromise(action.payload)) {
12 | return next(action);
13 | }
14 |
15 | /*
16 | * Another solution would would be to include a property in `meta`
17 | * and evaulate that property.
18 | *
19 | * if (action.meta.globalError === true) {
20 | * // handle error
21 | * }
22 | *
23 | * The error middleware serves to dispatch the initial pending promise to
24 | * the promise middleware, but adds a `catch`.
25 | */
26 | if (_.has(types, action.type)) {
27 |
28 | // Dispatch initial pending promise, but catch any errors
29 | return next(action).catch(error => {
30 | console.warn(error);
31 |
32 | return error;
33 | });
34 | }
35 |
36 | return next(action);
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/examples/catching-errors-with-middleware/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "index.js",
3 | "scripts": {
4 | "start": "`npm bin`/parcel index.html",
5 | "test": ""
6 | },
7 | "dependencies": {
8 | "is-promise": "^2.1.0",
9 | "react": "^16.4.1",
10 | "react-dom": "^16.4.1",
11 | "react-redux": "^5.0.7",
12 | "redux": "^4.0.0",
13 | "redux-logger": "^3.0.6",
14 | "underscore": "^1.9.1"
15 | },
16 | "devDependencies": {
17 | "@babel/core": "^7.7.4",
18 | "parcel-bundler": "^1.12.4"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/catching-errors-with-middleware/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import promise from '../../src';
3 | import errorMiddleware from './middleware';
4 | import { createLogger } from 'redux-logger';
5 |
6 | const reducer = (state) => state;
7 |
8 | // Custom error middleware should go before the promise middleware
9 | const store = createStore(reducer, null, applyMiddleware(
10 | errorMiddleware,
11 | promise,
12 | createLogger({ collapsed: true }),
13 | ));
14 |
15 | export default store;
16 |
--------------------------------------------------------------------------------
/examples/catching-errors/.gitignore:
--------------------------------------------------------------------------------
1 | .cache
2 | dist
3 | node_modules
--------------------------------------------------------------------------------
/examples/catching-errors/README.md:
--------------------------------------------------------------------------------
1 | # Catching Errors
2 |
3 | This example demonstrates how to catch a rejected promise.
4 |
5 | ## Getting Started
6 |
7 | - Clone this repository to your computer
8 | - Open the folder for this example
9 | - Run `npm i` to install dependencies
10 | - Run `npm start` to start the example
11 | - Open `http://localhost:1234` in a web browser
12 |
--------------------------------------------------------------------------------
/examples/catching-errors/actions.js:
--------------------------------------------------------------------------------
1 | export const foo = () => dispatch => dispatch({
2 | type: 'FOO',
3 | // When you throw an error, always instantiate a new Error object with `new Error()`
4 | payload: Promise.reject(new Error('foo')),
5 | });
6 |
7 | export const bar = () => dispatch => dispatch({
8 | type: 'BAR',
9 | payload: Promise.reject(new Error('bar')),
10 | });
11 |
--------------------------------------------------------------------------------
/examples/catching-errors/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Catching Errors
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/catching-errors/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 | import * as actions from './actions';
4 | import store from './store';
5 |
6 | class App extends Component {
7 | constructor(props) {
8 | super(props);
9 |
10 | this.state = {
11 | isPending: false,
12 | error: null,
13 | };
14 |
15 | this.throwError = this.throwError.bind(this);
16 | }
17 |
18 | throwError() {
19 | const action = store.dispatch(actions.foo());
20 |
21 | this.setState({ isPending: true });
22 |
23 | action.catch(error => {
24 | this.setState({
25 | isPending: false,
26 | error: error.message,
27 | });
28 | });
29 | }
30 |
31 | render() {
32 | const { isPending, error } = this.state;
33 |
34 | return (
35 | <>
36 |
37 |
Catching Errors
38 |
This example demonstrates how to catch a rejected promise.