Exploring the Differences Between Redux Toolkit and Redux Saga

Comparing Two Essential Redux Libraries: Redux Toolkit and Redux Saga

Benny
Bina Nusantara IT Division
6 min readJun 27, 2023

--

Redux logo from redux.js.org

Redux is a popular state management library for JavaScript applications, widely used in conjunction with React. When working with Redux, developers often come across two prominent middleware solutions: Redux Toolkit and Redux Saga. Both serve different purposes and offer unique features that cater to specific needs in managing state and handling asynchronous actions.

In this article, we will delve into the key differences between Redux Toolkit and Redux Saga, their use cases, and when to choose one over the other.

What is Redux Toolkit?

Redux Toolkit is an official package provided by the Redux team to simplify and streamline the process of working with Redux in JavaScript applications. It aims to address common pain points and boilerplate code associated with setting up and managing a Redux store.

You can install Redux Toolkit with the following command :

npm install @reduxjs/toolkit

Redux Toolkit features

Redux Toolkit provides a set of utilities and abstractions that make it easier to write Redux code with fewer lines of code and improved developer experience. It includes several features and concepts that enhance productivity and enforces best practices:

  1. configureStore: This function combines multiple Redux configurations, such as setting up the Redux store, applying middleware, and enabling Redux DevTools Extension, into a single function call. It simplifies the store setup process and eliminates the need to manually configure each aspect.
  2. createSlice: With createSlice, developers can define Redux slices, which encapsulate related reducers, actions, and selectors. It automatically generates the corresponding reducer functions and action creators based on the defined slice, reducing the boilerplate code traditionally associated with Redux.
  3. createAsyncThunk: This utility simplifies handling asynchronous operations, such as API requests, by generating thunk actions with standardized lifecycle states. It integrates with Redux Toolkit’s built-in loading, success, and error state management, making it easier to handle async logic in a predictable manner.
  4. createEntityAdapter: createEntityAdapter provides a standardized way to manage normalized data structures in the Redux store. It simplifies common CRUD (Create, Read, Update, Delete) operations and comes with built-in methods for manipulating entities.

Example

Here’s an example of using the Redux Toolkit in a ReactJS application:

  • Create a Redux Slice “counterSlice.ts”.
import { createSlice } from "@reduxjs/toolkit";

const counterSlice = createSlice({
name: "counter",
initialState: 0,
reducers: {
increment: (state) => state + 1,
decrement: (state) => state - 1,
},
});

export const { increment, decrement } = counterSlice.actions;

export default counterSlice.reducer;
  • Create the Redux Store “store.ts”.
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

const store = configureStore({
reducer: {
counter: counterReducer,
},
});

export default store;
  • Connect Redux Store to the React App.
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';

function App() {
const counter = useSelector((state) => state.counter);
const dispatch = useDispatch();

return (
<div>
<h1>Counter: {counter}</h1>
<button onClick={() => dispatch(increment())}>Increment</button>
<button onClick={() => dispatch(decrement())}>Decrement</button>
</div>
);
}

export default App;
  • Wrap the App with Redux Provider.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import store from './store';

const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<Provider store={store}>
<App />
</Provider>
);
  • Run the command “npm start” to start your application and see the result.

What is Redux Saga?

Redux Saga is a middleware library for Redux that allows you to handle side effects in your Redux application, such as asynchronous operations, network requests, and handling complex flows of actions. It follows the concept of generators and provides an alternative approach to managing side effects compared to traditional Redux Thunk.

Redux Saga features

Here are some key features and concepts of Redux Saga:

  1. Side Effect Management: Redux Saga focuses on managing side effects, which are operations that occur asynchronously, such as API calls or interacting with browser APIs. It provides a structured way to handle these side effects by using generator functions.
  2. Generator Functions: Redux Saga leverages JavaScript generator functions (‘function*’) to write asynchronous code in a synchronous-looking manner. Generator functions allow you to pause and resume the execution of a function, which is useful for managing asynchronous flows.
  3. Sagas: Sagas are generator functions that encapsulate the logic for handling specific side effects. They listen for dispatched actions and perform corresponding tasks, such as making API calls or dispatching other actions.
  4. Effects: Redux Saga provides a set of predefined effect functions that represent different types of side effects. These effects include making API requests (call), delaying execution (delay), handling concurrency (fork, race), and more. Effects are used within sagas to define the behavior of specific side effects.
  5. Middleware Integration: Redux Saga is integrated into the Redux middleware pipeline. It intercepts dispatched actions before they reach the reducers, allowing you to handle side effects transparently in a separate layer.
  6. Testability: Redux Saga promotes testability by providing a declarative and deterministic way to write tests for your sagas. Since sagas are generator functions, you can test them by stepping through the yielded values and making assertions on the expected behavior.

Example

Here’s an example of using Redux Saga in a ReactJS application:

  • Create the Redux Action “actions.ts”.
export const GET_USER_FETCH = 'GET_USER_FETCH';
export const GET_USER_SUCCESS = 'GET_USER_SUCCESS';

export const getUserFetch = () => ({
type: GET_USER_FETCH
});
  • Create the Reducer “reducer.ts”.
import { GET_USER_SUCCESS } from "./actions";

const initState = { user: [] };

const myReducer = (state = initState, action: any) => {
switch(action.type) {
case GET_USER_SUCCESS:
return { ...state, user: action.user };
default:
return state;
}
}

export default myReducer;
  • Create the Redux Saga “sagas.ts”. We use free fake API for testing from JSON Placeholder (Typicode).
import { call, put, takeEvery } from 'redux-saga/effects';
import { GET_USER_FETCH, GET_USER_SUCCESS } from './actions';

function userFetch() {
return fetch('https://jsonplaceholder.typicode.com/users').then(response => response.json());
}

function* workerGetUserFetch() {
const user: Promise<any> = yield call(userFetch);
yield put({ type: GET_USER_SUCCESS, user });
}

function* mySaga() {
yield takeEvery(GET_USER_FETCH, workerGetUserFetch);
}

export default mySaga;
  • Create the Redux Store “store.ts”.
import { configureStore } from '@reduxjs/toolkit';
import createSagaMiddleware from 'redux-saga';
import mySaga from './sagas';
import userReducer from './reducer';

const sagaMiddleware = createSagaMiddleware();

const store = configureStore({
reducer: {
user: userReducer
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(sagaMiddleware),
});

sagaMiddleware.run(mySaga);

export default store;
  • Connect Redux Store to the React App.
import React from 'react';
import './App.css';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';
import { getUserFetch } from './actions';

function App() {
const user = useSelector((state: any) => state.user.user);
const dispatch = useDispatch();

return (
<div>
<div>
<button onClick={() => dispatch(getUserFetch())}>Get Users</button>
<div>
Users: {user.map((user: any) => (<div>{user.name}</div>))}
</div>
</div>
</div>
);
}

export default App;
  • Wrap the App with Redux Provider just like we do before.
  • Finally start your application and see the result.

Choosing the Right Solution

  • Simplicity vs. Advanced Control: Redux Toolkit prioritizes simplicity, reducing the learning curve and providing a smoother developer experience. On the other hand, Redux Saga offers more flexibility and advanced features but requires a deeper understanding of generators and Redux middleware.
  • Project Complexity: Consider the complexity of your project and the specific requirements for managing state and handling async actions. Redux Toolkit is suitable for simpler applications, while Redux Saga shines in more complex scenarios.
  • Combination: Redux Toolkit and Redux Saga can be used together in the same application, leveraging the strengths of each solution for different parts of the state management and async action handling.

Conclusion

Redux Toolkit and Redux Saga are two powerful libraries that serve different purposes in Redux-based applications. Redux Toolkit simplifies Redux development, reduces boilerplate, and provides an opinionated structure, making it suitable for smaller projects.

On the other hand, Redux Saga offers advanced control over asynchronous actions and complex side effects, making it ideal for larger applications with complex async workflows.

Choosing between Redux Toolkit and Redux Saga depends on your project’s requirements, complexity, and the level of control needed over state management and async action handling.

Remember, both libraries have their strengths, and it’s important to evaluate your specific project needs before deciding which one to use.

--

--