React Middleware: Bridging APIs and Components
React-based UI is adopting middleware as a way to handle complex asynch functionalities during state management to manage application flow effectively.
Join the DZone community and get the full member experience.
Join For FreeMiddleware is not a new concept in web development. It is typically associated with backend frameworks like Express.js, where there is a lot of authentication, logging, etc. Because of its inherent advantages, middleware has gained significant traction in the frontend.
Frontend frameworks such as React are adopting it as a way to handle complex functionalities during state management. In this guide, we’ll walk you through the concept of middleware in React and how to use it to enhance your React app’s functionality and manage application flow effectively.
By the end of it, you’ll have a good grasp of what React middleware is and how to implement it with Redux Thunk.
React Middleware Simplified: Streamlining Application Logic
What Is Middleware?
As the name suggests, middleware is a layer that sits between different components of an application. It provides a layer of processing and functionality here when there wouldn’t be one without it. Middleware works by intercepting requests flowing from one component to another and allows you to perform specific actions. Once done it passes the modified request to the next middleware or its intended destination.
This is a concept primarily used in the backend. But, as mentioned earlier, it has been adapted in React to serve a similar purpose. Now, let’s dive into what middleware means, specifically in React.
Middleware in React
In React, middleware is implemented as a series of functions, executed one after the other, in the order you define them. Each function has access to the app’s state and actions. Hence, the functions can modify and dispatch new actions.
When state changes in React, the application dispatches actions which are then handled by the Reducer(s). The Reducer(s) will then return a new state, which is then stored and passed back to the application.
React middleware sits between the dispatched action and intercepts it before it reaches the reducer. Then, it allows you to hook into this process and execute some code on this action, for example, logging it or making an API call.
Once this is done, it will pass the modified action to the Reducer (if there isn’t another middleware in the chain).
Examples of Middleware in React
You can find React middleware mainly in Redux, the complex state management React library. Redux has two main types of middleware:
- Thunk – we’ll dive deeper into how to use this middleware to modify an action.
- Saga.
Each of them handles different types of modifications to actions, as we’ll see below when looking at the uses of middleware in React. Other React libraries, such as React Query, also use middleware (more on this later).
Before we look at how to implement this, let’s go over the different uses of middleware in React.
Uses of Middleware in React
React middleware comes in handy for the following use cases:
Debugging and Logging
React middleware can be used to log information like current state, actions, and other data during the application development. Moreover, these can be useful for identifying the potential bugs and errors for early resolution.
The Redux Logger (for Redux) can help you do this. It logs Redux actions and state changes for easy debugging. It intercepts every dispatched action and logs the previous state, action, and the next state.
Authorization and Authentication
If you want to authenticate users before you update state you can use Redux Thunk or Saga to handle the authentication workflows. Once you intercept an action this middleware enables you to store tokens, check authentication before dispatching the action, or refresh tokens.
In other words, these middleware are useful for checking whether a user is authenticated to access a particular route or not.
Event-Driven Operations
The React reducer is designed to run synchronous code. This means that if you attempt to run anything asynchronous on it, it won’t work. With React middleware such as Redux Thunk, you can catch the action, perform async tasks like making an API call, and proceed to the reducer once this is done.
Caching Data
React Query (another React middleware) can serve as an effective tool in caching application data. It caches API responses automatically and revalidates them when needed. As a result, it helps you avoid redundant API calls by checking for the required information in the cache.
Performance Enhancement
React middleware can also help enhance the application's performance by delaying unnecessary actions for later, debouncing events, and batch-processing certain tasks. By caching API responses, React Query also enhances the performance of your React apps.
How to Use Middleware in React?
Redux is a powerful tool for managing global state in React applications. It includes several middleware in its toolkit you can use to add custom functionality to your React apps. One of the most common ones is Redux Thunk.
Introduction to Redux Thunk
Before diving deep into the implementation of Thunk, let's gather some information about it so that we can use it effectively.
What Is Thunk?
In general programming, a “Thunk” is simply a function that is used to delay the evaluation and execution of a function. We can look at it as a function that postpones an action until a specific condition is met.
In Redux, a thunk is a specific function used with the Redux Thunk middleware. Redux Thunk is designed to allow asynchronous operations within your React app. Earlier on we mentioned that the Reducer is built to run synchronous code. This means when an action is dispatched, the reducer updates state immediately.
Redux Thunk as Middleware
Redux Thunk acts as middleware. It allows you to write action creators that return functions (thunks) instead of plain action objects. These functions can contain asynchronous logic. When you dispatch a thunk, the Thunk middleware intercepts it and executes the function.
Inside the thunk
, you can perform your asynchronous operation (e.g., make an API call) and then dispatch regular actions to update the Redux store when the operation is complete.
Note: Redux Thunk allows both asynchronous and synchronous operations, although its primary purpose is to facilitate asynchronous actions. It doesn't prevent you from dispatching synchronous actions; it simply provides a mechanism for handling asynchronous ones as well.
With that out of the way, let’s see React middleware in action by implementing Redux Thunk.
A Step-by-Step Guide to Implementing Redux Thunk
Implementing Redux middleware involves the following steps:
Step 1: Set Up Redux With Thunk
Create a React application and install the dependencies.
npx create-react-app newApp
Step 2: Install Redux Thunk
Run the following command to install Redux Thunk.
npm install redux-thunk
Step 3: Enable Thunk Middleware
Enable the Thunk middleware in the Redux store.
import { configureStore } from '@reduxjs/toolkit';
import thunk from 'redux-thunk';
import rootReducer from './reducers'; // Combine all reducers here
const store = configureStore({
reducer: rootReducer,
middleware: [thunk],
});
export default store;
Note: When using Redux Toolkit, there is no need to install thunk
explicitly.
Step 4: Writing Async Function
A thunk returns another function that receives the dispatch
and getstate
arguments to read the state or dispatch actions. The following is the example code for fetching data:
// Action types
const START_FETCH = 'START_FETCH';
const FETCH_SUCCESS = 'FETCH_SUCCESS';
const FETCH_ERROR = 'FETCH_ERROR';
// Action creators
const startFetch = () => ({ type: START_FETCH });
const fetchSuccess = (data) => ({ type: FETCH_SUCCESS, payload: data });
const fetchError = (error) => ({ type: FETCH_ERROR, payload: error });
// Thunk action
export const fetchData = () => {
return async (dispatch, getState) => {
dispatch(startFetch()); // Notify that the fetch has started
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!response.ok) {
throw new Error('Failed to fetch data');
}
const data = await response.json();
dispatch(fetchSuccess(data)); // Send the fetched data to the store
} catch (error) {
dispatch(fetchError(error.message)); // Handle any errors
}
};
};
Step 5: Handling Dispatched Actions in Reducer
Handle the dispatched actions by updating the reducer.
const initialState = {
data: [],
isLoading: false,
error: null,
};
export const dataReducer = (state = initialState, action) => {
switch (action.type) {
case START_FETCH:
return { ...state, isLoading: true, error: null };
case FETCH_SUCCESS:
return { ...state, isLoading: false, data: action.payload };
case FETCH_ERROR:
return { ...state, isLoading: false, error: action.payload };
default:
return state;
}
};
Step 6: Dispatching Thunk From a Component
Use the useDispatch
hook to dispatch thunks in React projects.
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchData } from './actions';
const DataComponent = () => {
const dispatch = useDispatch();
const { data, isLoading, error } = useSelector((state) => state.data);
useEffect(() => {
dispatch(fetchData()); // Trigger the thunk to fetch data
}, [dispatch]);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>
);
};
export default DataComponent;
Using createAsyncThunk
The Redux Toolkit contains a built-in API that defines high-level logic for async functions, dispatches them, and handles errors promptly. Please note that as this provides an abstraction for the specific use cases of async functions, createAsyncThunk
does not apply to all the use cases of thunks.
The following is the example implementation of the createAsyncThunk
:
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
export const dataFetch = createAsyncThunk('data/fetchData', async () => {
const response = await fetch('https://api.example.com/data');
return response.json();
});
const dataSlice = createSlice({
name: 'data',
initial_State: { data: [], loading: false, error: null },
extraReducers: (builder) => {
builder
.addCase(dataFetch.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(dataFetch.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(dataFetch.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
},
});
export default dataSlice.reducer;
Now, to dispatch the dataFetch
thunk, use the following code:
dispatch(dataFetch ());
Uses of React Middleware – Redux Thunk
Thunks can be used for a variety of purposes, including but not limited to:
- Abstracting complex logic from the components.
- Making async requests and logic.
- Write functions to dispatch multiple actions in a series.
React Middleware in Other State Management Libraries
Redux isn’t the only React library that leverages middleware. You can also find them in other state management libraries, although the implementation of this concept in these libraries is different from how it is done in Redux.
MobX
MobX doesn’t have traditional middleware like Redux. It offers similar functionality through mechanisms such as interceptors, observers, and reactions. These mechanisms allow you to observe changes in your MobX state and react to them. In this way, MobX provides ways to handle side effects, logging, and other tasks middleware typically handles in Redux.
Recoil
Recoil doesn’t support middleware in the same way as Redux because it doesn’t need to. It allows you to directly update pieces of state (atoms) using specific functions. There is no dispatching of actions to a reducer, which you can intercept with middleware. To handle async operations, it uses selectors — derived state depending on atoms or other selectors.
Zustand
It helps manage states with a simple API and supports native middleware for logging and storing states, just like Redux.
XState
XState supports middleware-like behavior using hooks such as onTransition
, actions
, and services
. These hooks help to intercept and modify state transitions.
Conclusion
Middleware acts as a bridge to connect and combine different components of a web application for better data flow and handling. They have been widely used on the backend ever since, but now have found use cases on the frontend as well.
In React, there are various ways to implement the middleware. They are usually linked to the state management libraries such as Redux, and MobX. The most commonly used React middleware, Thunk, is included in the Redux library. A thunk is a code block that performs the delayed tasks.
In this article, we have explored Middleware in React, Redux library, and Redux Thunk. We also covered steps to implement Redux Thunk middleware for fetching data and dispatching actions.
Opinions expressed by DZone contributors are their own.
Comments