Why do we need middleware for async flow in Redux?

Cover Image for Why do we need middleware for async flow in Redux?
Matheus Mello
Matheus Mello
published a few days ago. updated a few hours ago

Why do we need middleware for async flow in Redux? ๐Ÿ”„

Have you ever wondered why Redux requires middleware to support asynchronous data flow? ๐Ÿค” The answer lies in the fundamental nature of Redux and how it handles actions and reducers.

According to the Redux documentation, "Without middleware, Redux store only supports synchronous data flow." But why is this the case? ๐Ÿคทโ€โ™€๏ธ Why can't we simply call the async API directly from the container component and dispatch the actions? ๐Ÿ“ฆ๐Ÿ’ฅ

Let's look at a simple example to illustrate the problem. Imagine a user interface with a field and a button. When the user clicks the button, the field should be populated with data fetched from a remote server. ๐Ÿ“ฒ๐Ÿš€

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

// Action Types
const ActionTypes = {
    STARTED_UPDATING: 'STARTED_UPDATING',
    UPDATED: 'UPDATED'
};

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

// App Component
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 Component PropTypes
App.propTypes = {
    dispatch: React.PropTypes.func,
    field: React.PropTypes.any,
    isWaiting: React.PropTypes.bool
};

// Reducer
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;
    }
};

// Store
const store = Redux.createStore(reducer);

// Connected App Component
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);

// Exported Component
export default class extends React.Component {
    render() {
        return <Provider store={store}><ConnectedApp/></Provider>;
    }
}

In this example, we have the App component which contains the field, button, and a loading indicator. When the button is clicked, it triggers the update function provided by the connect function from react-redux. This function dispatches an action to inform the App component that it is updating, and then makes an asynchronous API call to fetch the field value. Once the call is completed, another action is dispatched with the fetched value as the payload. ๐Ÿ˜ฎ

So, what's wrong with this approach? Why do we need middleware such as Redux Thunk or Redux Promise, as the documentation suggests? ๐Ÿค”

The answer lies in the requirement for action creators to be pure functions. In the past, only pure functions were allowed as action creators. However, this limitation was relaxed, as discussed in several GitHub issues (source). While action creators can now be impure functions, pure functions are still preferred for better predictability and testability. ๐Ÿงช

By using middleware, such as Redux Thunk or Redux Promise, we can separate the impure logic from the pure logic within an action creator. The impure logic, such as making API calls or handling promises, can be encapsulated in the middleware, while the action creator itself remains a pure function. ๐Ÿงฉโœจ

For example, with Redux Thunk, we can rewrite the update function as follows:

const update = () => {
    return (dispatch) => {
        dispatch({
            type: ActionTypes.STARTED_UPDATING
        });
        AsyncApi.getFieldValue()
            .then(result => dispatch({
                type: ActionTypes.UPDATED,
                payload: result
            }));
    };
};

The update function now returns a thunk, which is a function that takes the dispatch function as an argument. This allows us to dispatch multiple actions or handle asynchronous operations within a single action creator. The thunk middleware handles the execution of the thunk, invoking the function with the dispatch argument. ๐ŸŽฎ๐Ÿ”‚

So, even though Redux no longer strictly requires action creators to be pure functions, using middleware for handling async flow offers several benefits. It helps to separate concerns, improves code maintainability, and allows for better testing of pure action creators. ๐Ÿ› ๏ธ๐Ÿงช

In conclusion, middleware is needed for async flow in Redux because it helps handle asynchronous operations within action creators, separates concerns, maintains code purity, and enhances testing capabilities. ๐Ÿ”„๐Ÿ“ฆโœจ

Do you have any questions or suggestions related to this topic? Feel free to leave a comment below and let's spark a conversation! ๐Ÿ—ฃ๏ธ๐Ÿ’ฌ


More Stories

Cover Image for How can I echo a newline in a batch file?

How can I echo a newline in a batch file?

updated a few hours ago
batch-filenewlinewindows

๐Ÿ”ฅ ๐Ÿ’ป ๐Ÿ†’ Title: "Getting a Fresh Start: How to Echo a Newline in a Batch File" Introduction: Hey there, tech enthusiasts! Have you ever found yourself in a sticky situation with your batch file output? We've got your back! In this exciting blog post, we

Matheus Mello
Matheus Mello
Cover Image for How do I run Redis on Windows?

How do I run Redis on Windows?

updated a few hours ago
rediswindows

# Running Redis on Windows: Easy Solutions for Redis Enthusiasts! ๐Ÿš€ Redis is a powerful and popular in-memory data structure store that offers blazing-fast performance and versatility. However, if you're a Windows user, you might have stumbled upon the c

Matheus Mello
Matheus Mello
Cover Image for Best way to strip punctuation from a string

Best way to strip punctuation from a string

updated a few hours ago
punctuationpythonstring

# The Art of Stripping Punctuation: Simplifying Your Strings ๐Ÿ’ฅโœ‚๏ธ Are you tired of dealing with pesky punctuation marks that cause chaos in your strings? Have no fear, for we have a solution that will strip those buggers away and leave your texts clean an

Matheus Mello
Matheus Mello
Cover Image for Purge or recreate a Ruby on Rails database

Purge or recreate a Ruby on Rails database

updated a few hours ago
rakeruby-on-railsruby-on-rails-3

# Purge or Recreate a Ruby on Rails Database: A Simple Guide ๐Ÿš€ So, you have a Ruby on Rails database that's full of data, and you're now considering deleting everything and starting from scratch. Should you purge the database or recreate it? ๐Ÿค” Well, my

Matheus Mello
Matheus Mello