Why do we need middleware for async flow in Redux?
Asked 07 September, 2021
Viewed 821 times
  • 68
Votes

According to the docs, "Without middleware, Redux store only supports synchronous data flow". I don't understand why this is the case. Why can't the container component call the async API, and then dispatch the actions?

For example, imagine a simple UI: a field and a button. When user pushes the button, the field gets populated with data from a remote server.

A field and a button

import * as React from 'react';
import * as Redux from 'redux';
import { Provider, connect } from 'react-redux';

const ActionTypes = {
    STARTED_UPDATING: 'STARTED_UPDATING',
    UPDATED: 'UPDATED'
};

class AsyncApi {
    static getFieldValue() {
        const promise = new Promise((resolve) => {
            setTimeout(() => {
                resolve(Math.floor(Math.random() * 100));
            }, 1000);
        });
        return promise;
    }
}

class App extends React.Component {
    render() {
        return (
            <div>
                <input value={this.props.field}/>
                <button disabled={this.props.isWaiting} onClick={this.props.update}>Fetch</button>
                {this.props.isWaiting && <div>Waiting...</div>}
            </div>
        );
    }
}
App.propTypes = {
    dispatch: React.PropTypes.func,
    field: React.PropTypes.any,
    isWaiting: React.PropTypes.bool
};

const reducer = (state = { field: 'No data', isWaiting: false }, action) => {
    switch (action.type) {
        case ActionTypes.STARTED_UPDATING:
            return { ...state, isWaiting: true };
        case ActionTypes.UPDATED:
            return { ...state, isWaiting: false, field: action.payload };
        default:
            return state;
    }
};
const store = Redux.createStore(reducer);
const ConnectedApp = connect(
    (state) => {
        return { ...state };
    },
    (dispatch) => {
        return {
            update: () => {
                dispatch({
                    type: ActionTypes.STARTED_UPDATING
                });
                AsyncApi.getFieldValue()
                    .then(result => dispatch({
                        type: ActionTypes.UPDATED,
                        payload: result
                    }));
            }
        };
    })(App);
export default class extends React.Component {
    render() {
        return <Provider store={store}><ConnectedApp/></Provider>;
    }
}

When the exported component is rendered, I can click the button and the input is updated correctly.

Note the update function in the connect call. It dispatches an action that tells the App that it is updating, and then performs an async call. After the call finishes, the provided value is dispatched as a payload of another action.

What is wrong with this approach? Why would I want to use Redux Thunk or Redux Promise, as the documentation suggests?

EDIT: I searched the Redux repo for clues, and found that Action Creators were required to be pure functions in the past. For example, here's a user trying to provide a better explanation for async data flow:


  

The action creator itself is still a pure function, but the thunk function it returns doesn't need to be, and it can do our async calls

Action creators are no longer required to be pure. So, thunk/promise middleware was definitely required in the past, but it seems that this is no longer the case?

11 Answer