|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- Redux Thunk
- =============
-
- Thunk [middleware](https://redux.js.org/advanced/middleware) for Redux.
-
- [](https://travis-ci.org/reduxjs/redux-thunk)
- [](https://www.npmjs.com/package/redux-thunk)
- [](https://www.npmjs.com/package/redux-thunk)
-
- ```js
- npm install --save redux-thunk
- ```
-
- ## Note on 2.x Update
-
- Most tutorials today assume Redux Thunk 1.x so you might run into an issue when running their code with 2.x.
- **If you use Redux Thunk 2.x in CommonJS environment, [don’t forget to add `.default` to your import](https://github.com/reduxjs/redux-thunk/releases/tag/v2.0.0):**
-
- ```diff
- - var ReduxThunk = require('redux-thunk')
- + var ReduxThunk = require('redux-thunk').default
- ```
-
- If you used ES modules, you’re already all good:
-
- ```js
- import ReduxThunk from 'redux-thunk' // no changes here 😀
- ```
-
- Additionally, since 2.x, we also support a [UMD build](https://unpkg.com/redux-thunk/dist/redux-thunk.min.js):
-
- ```js
- var ReduxThunk = window.ReduxThunk.default
- ```
-
- As you can see, it also requires `.default` at the end.
-
- ## Why Do I Need This?
-
- If you’re not sure whether you need it, you probably don’t.
-
- **[Read this for an in-depth introduction to thunks in Redux.](http://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout/35415559#35415559)**
-
- ## Motivation
-
- Redux Thunk [middleware](https://github.com/reactjs/redux/blob/master/docs/advanced/Middleware.md) allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods `dispatch` and `getState` as parameters.
-
- An action creator that returns a function to perform asynchronous dispatch:
-
- ```js
- const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
-
- function increment() {
- return {
- type: INCREMENT_COUNTER
- };
- }
-
- function incrementAsync() {
- return dispatch => {
- setTimeout(() => {
- // Yay! Can invoke sync or async actions with `dispatch`
- dispatch(increment());
- }, 1000);
- };
- }
- ```
-
- An action creator that returns a function to perform conditional dispatch:
-
- ```js
- function incrementIfOdd() {
- return (dispatch, getState) => {
- const { counter } = getState();
-
- if (counter % 2 === 0) {
- return;
- }
-
- dispatch(increment());
- };
- }
- ```
-
- ## What’s a thunk?!
-
- A [thunk](https://en.wikipedia.org/wiki/Thunk) is a function that wraps an expression to delay its evaluation.
-
- ```js
- // calculation of 1 + 2 is immediate
- // x === 3
- let x = 1 + 2;
-
- // calculation of 1 + 2 is delayed
- // foo can be called later to perform the calculation
- // foo is a thunk!
- let foo = () => 1 + 2;
- ```
-
- The term [originated](https://en.wikipedia.org/wiki/Thunk#cite_note-1) as a humorous past-tense version of "think".
-
- ## Installation
-
- ```
- npm install --save redux-thunk
- ```
-
- Then, to enable Redux Thunk, use [`applyMiddleware()`](https://redux.js.org/api-reference/applymiddleware):
-
- ```js
- import { createStore, applyMiddleware } from 'redux';
- import thunk from 'redux-thunk';
- import rootReducer from './reducers/index';
-
- // Note: this API requires redux@>=3.1.0
- const store = createStore(
- rootReducer,
- applyMiddleware(thunk)
- );
- ```
-
- ## Composition
-
- Any return value from the inner function will be available as the return value of `dispatch` itself. This is convenient for orchestrating an asynchronous control flow with thunk action creators dispatching each other and returning Promises to wait for each other’s completion:
-
- ```js
- import { createStore, applyMiddleware } from 'redux';
- import thunk from 'redux-thunk';
- import rootReducer from './reducers';
-
- // Note: this API requires redux@>=3.1.0
- const store = createStore(
- rootReducer,
- applyMiddleware(thunk)
- );
-
- function fetchSecretSauce() {
- return fetch('https://www.google.com/search?q=secret+sauce');
- }
-
- // These are the normal action creators you have seen so far.
- // The actions they return can be dispatched without any middleware.
- // However, they only express “facts” and not the “async flow”.
-
- function makeASandwich(forPerson, secretSauce) {
- return {
- type: 'MAKE_SANDWICH',
- forPerson,
- secretSauce
- };
- }
-
- function apologize(fromPerson, toPerson, error) {
- return {
- type: 'APOLOGIZE',
- fromPerson,
- toPerson,
- error
- };
- }
-
- function withdrawMoney(amount) {
- return {
- type: 'WITHDRAW',
- amount
- };
- }
-
- // Even without middleware, you can dispatch an action:
- store.dispatch(withdrawMoney(100));
-
- // But what do you do when you need to start an asynchronous action,
- // such as an API call, or a router transition?
-
- // Meet thunks.
- // A thunk is a function that returns a function.
- // This is a thunk.
-
- function makeASandwichWithSecretSauce(forPerson) {
-
- // Invert control!
- // Return a function that accepts `dispatch` so we can dispatch later.
- // Thunk middleware knows how to turn thunk async actions into actions.
-
- return function (dispatch) {
- return fetchSecretSauce().then(
- sauce => dispatch(makeASandwich(forPerson, sauce)),
- error => dispatch(apologize('The Sandwich Shop', forPerson, error))
- );
- };
- }
-
- // Thunk middleware lets me dispatch thunk async actions
- // as if they were actions!
-
- store.dispatch(
- makeASandwichWithSecretSauce('Me')
- );
-
- // It even takes care to return the thunk’s return value
- // from the dispatch, so I can chain Promises as long as I return them.
-
- store.dispatch(
- makeASandwichWithSecretSauce('My wife')
- ).then(() => {
- console.log('Done!');
- });
-
- // In fact I can write action creators that dispatch
- // actions and async actions from other action creators,
- // and I can build my control flow with Promises.
-
- function makeSandwichesForEverybody() {
- return function (dispatch, getState) {
- if (!getState().sandwiches.isShopOpen) {
-
- // You don’t have to return Promises, but it’s a handy convention
- // so the caller can always call .then() on async dispatch result.
-
- return Promise.resolve();
- }
-
- // We can dispatch both plain object actions and other thunks,
- // which lets us compose the asynchronous actions in a single flow.
-
- return dispatch(
- makeASandwichWithSecretSauce('My Grandma')
- ).then(() =>
- Promise.all([
- dispatch(makeASandwichWithSecretSauce('Me')),
- dispatch(makeASandwichWithSecretSauce('My wife'))
- ])
- ).then(() =>
- dispatch(makeASandwichWithSecretSauce('Our kids'))
- ).then(() =>
- dispatch(getState().myMoney > 42 ?
- withdrawMoney(42) :
- apologize('Me', 'The Sandwich Shop')
- )
- );
- };
- }
-
- // This is very useful for server side rendering, because I can wait
- // until data is available, then synchronously render the app.
-
- store.dispatch(
- makeSandwichesForEverybody()
- ).then(() =>
- response.send(ReactDOMServer.renderToString(<MyApp store={store} />))
- );
-
- // I can also dispatch a thunk async action from a component
- // any time its props change to load the missing data.
-
- import { connect } from 'react-redux';
- import { Component } from 'react';
-
- class SandwichShop extends Component {
- componentDidMount() {
- this.props.dispatch(
- makeASandwichWithSecretSauce(this.props.forPerson)
- );
- }
-
- componentDidUpdate(prevProps) {
- if (prevProps.forPerson !== this.props.forPerson) {
- this.props.dispatch(
- makeASandwichWithSecretSauce(this.props.forPerson)
- );
- }
- }
-
- render() {
- return <p>{this.props.sandwiches.join('mustard')}</p>
- }
- }
-
- export default connect(
- state => ({
- sandwiches: state.sandwiches
- })
- )(SandwichShop);
- ```
-
- ## Injecting a Custom Argument
-
- Since 2.1.0, Redux Thunk supports injecting a custom argument using the `withExtraArgument` function:
-
- ```js
- const store = createStore(
- reducer,
- applyMiddleware(thunk.withExtraArgument(api))
- )
-
- // later
- function fetchUser(id) {
- return (dispatch, getState, api) => {
- // you can use api here
- }
- }
- ```
-
- To pass multiple things, just wrap them in a single object and use destructuring:
-
- ```js
- const store = createStore(
- reducer,
- applyMiddleware(thunk.withExtraArgument({ api, whatever }))
- )
-
- // later
- function fetchUser(id) {
- return (dispatch, getState, { api, whatever }) => {
- // you can use api and something else here
- }
- }
- ```
-
-
- ## License
-
- MIT
|