Asynchronous actions are a common requirement in modern web applications. For instance, when a user makes an API request, the application should not block the user interface while waiting for the response. Redux provides two popular middleware solutions to handle asynchronous actions: Redux Thunk and Redux Saga.
In this article, we will discuss both Redux Thunk and Redux Saga and how they can be used to handle asynchronous actions in a React functional component. We will start with an overview of Redux and then discuss the differences between Thunk and Saga.
Redux Overview
Redux is a predictable state container for JavaScript applications. It helps manage the application's state in a predictable manner by providing a single source of truth. The state is stored in a central store, which can be modified by dispatching actions. The actions are plain JavaScript objects that describe what happened in the application. The state changes are handled by pure functions called reducers.
Redux has three main principles:
Single source of truth: The state of the application is stored in a single store.
State is read-only: The state can only be changed by dispatching actions.
Changes are made with pure functions: The reducers are pure functions that take the current state and an action and return the next state.
Redux Thunk and Redux Saga are middleware solutions that extend Redux to handle asynchronous actions.
Redux Thunk
Redux Thunk is a middleware that allows you to write action creators that return a function instead of an action object. The returned function can then be used to dispatch multiple actions asynchronously.
Here's an example of how Redux Thunk can be used to fetch data asynchronously:
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import axios from "axios";
const initialState = {
isLoading: false,
data: null,
error: null,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "FETCH_DATA_REQUEST":
return {
...state,
isLoading: true,
};
case "FETCH_DATA_SUCCESS":
return {
...state,
isLoading: false,
data: action.payload,
};
case "FETCH_DATA_FAILURE":
return {
...state,
isLoading: false,
error: action.payload,
};
default:
return state;
}
};
const fetchDataRequest = () => {
return {
type: "FETCH_DATA_REQUEST",
};
};
const fetchDataSuccess = (data) => {
return {
type: "FETCH_DATA_SUCCESS",
payload: data,
};
};
const fetchDataFailure = (error) => {
return {
type: "FETCH_DATA_FAILURE",
payload: error,
};
};
const fetchData = () => {
return (dispatch) => {
dispatch(fetchDataRequest());
axios
.get("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => {
const data = response.data;
dispatch(fetchDataSuccess(data));
})
.catch((error) => {
const errorMessage = error.message;
dispatch(fetchDataFailure(errorMessage));
});
};
};
const store = createStore(reducer, applyMiddleware(thunk));
store.dispatch(fetchData());
In this example, we have defined three action creators fetchDataRequest, fetchDataSuccess, and fetchDataFailure. The fetchData action creator returns a function that dispatches the fetchDataRequest action before making an API call using axios. When the API call is successful, it dispatches the fetchDataSuccess action with the received data. If the API call fails, it dispatches the fetchDataFailure action with an error message.
The applyMiddleware function is used to apply the thunk middleware to the Redux store. When the fetchData action is dispatched, Redux Thunk intercepts it and executes the function instead of passing it directly to the reducer.
Redux Saga
Redux Saga is a middleware that allows you to write complex, non-blocking code for handling asynchronous actions. It uses generators to make asynchronous code look and behave like synchronous code.
Here's an example of how Redux Saga can be used to fetch data asynchronously:
import { createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";
import axios from "axios";
import { call, put, takeLatest } from "redux-saga/effects";
const initialState = {
isLoading: false,
data: null,
error: null,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "FETCH_DATA_REQUEST":
return {
...state,
isLoading: true,
};
case "FETCH_DATA_SUCCESS":
return {
...state,
isLoading: false,
data: action.payload,
};
case "FETCH_DATA_FAILURE":
return {
...state,
isLoading: false,
error: action.payload,
};
default:
return state;
}
};
function* fetchData() {
try {
yield put({ type: "FETCH_DATA_REQUEST" });
const response = yield call(
axios.get,
"https://jsonplaceholder.typicode.com/todos/1"
);
const data = response.data;
yield put({ type: "FETCH_DATA_SUCCESS", payload: data });
} catch (error) {
const errorMessage = error.message;
yield put({ type: "FETCH_DATA_FAILURE", payload: errorMessage });
}
}
function* watchFetchData() {
yield takeLatest("FETCH_DATA", fetchData);
}
const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(watchFetchData);
store.dispatch({ type: "FETCH_DATA" });
In this example, we have defined a generator function fetchData that handles the asynchronous data fetching. The call function is used to call the axios.get function, and the put function is used to dispatch the actions.
The watchFetchData generator function uses the takeLatest function to listen for the FETCH_DATA action and call the fetchData function.
The createSagaMiddleware function is used to create the middleware, and the sagaMiddleware.run function is used to run the watchFetchData generator function.
Differences between Redux Thunk and Redux Saga
Redux Thunk and Redux Saga are both popular middleware solutions for handling asynchronous actions in Redux. They have some differences that make them suitable for different use cases.
Redux Thunk is a simple and lightweight middleware that is suitable for most use cases. It allows you to write asynchronous code in a more synchronous way, making it easier to read and debug. It is easy to learn and use and requires less boilerplate code.
Redux Saga is a more advanced middleware that allows you to write complex, non-blocking code for handling asynchronous actions. It is suitable for complex use cases that require more control over the flow of the application. It uses generators to make asynchronous code look and behave like synchronous code, making it easier to read and understand.
Conclusion
Asynchronous actions are a common requirement in modern web applications, and Redux provides two popular middleware solutions to handle them: Redux Thunk and Redux Saga. Redux Thunk is a simple and lightweight middleware that allows you to write asynchronous code in a more synchronous way, while Redux Saga is a more advanced middleware that allows you to write complex, non-blocking code for handling asynchronous actions.
When choosing between Redux Thunk and Redux Saga, consider the complexity of your use case and the level of control you need over the flow of your application. If you have a simple use case and want to write asynchronous code in a more synchronous way, Redux Thunk is a good choice. If you have a more complex use case and want to write non-blocking code using generators, Redux Saga may be a better fit.
Regardless of which middleware you choose, make sure to thoroughly test your code and handle errors appropriately to ensure that your application is robust and reliable. With the right tools and best practices, you can easily handle asynchronous actions in your Redux applications and build high-quality, scalable web applications.