Global state management using redux and redux-thunk
Author : JaNakh Pon , January 04, 2022
Tags
Summary
In this article, we are going to integrate redux and redux-thunk for our Todo app's functionalities(fetch, search, sort, create, update, delete ) in React.js.
We will use redux for complex state management, however, since redux doesn't support impure functions out of the box, we will need to use redux-thunk as a middleware to handle async/impure functions.
Installation & Setup
Firstly, let's install the required dependencies 😉:
> npm i react-redux redux-thunk --save
Now, let's a folder named 'redux' under src/*
and create actions
, reducers
, store
and types
folders under the redux/*
folder.
Then we'll start by creating store, we will need to pass our reducers to the Redux createStore function, which returns a store object.
import rootReducer from "../reducers";
import {createStore,applyMiddleware} from "redux";
import thunk from "redux-thunk";
const store = createStore(rootReducer,applyMiddleware(thunk));
export default store;
Note that we can also use other middlewares alongside thunk
if we need. For example:
const store = createStore(rootReducer,applyMiddleware(thunk),applyMiddleware(othermiddleware));
Now, in our root index.js, let's import store object
and then pass this object
to the react-redux Provider
component, which is rendered at the top of our component tree.
This ensures that any time we connect to Redux in our app via react-redux connect, the store is available to our components.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import store from './redux/store';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
types, actions and reducers
Types
Let's start by defining types
values:
export const TASK_COUNT = 'TASK_COUNT'
export const TASK_ERROR = 'TASK_ERROR'
export const ADD_TASK = 'ADD_TASK'
export const TASK_UPDATE = 'TASK_UPDATE'
export const TASK_REMOVE = 'TASK_REMOVE'
export const TASK_LIST = 'TASK_LIST'
export const TASK_SEARCH = 'TASK_SEARCH'
export const URL = `http://localhost:3001/api/v1/todos`
Actions
Actions are a plain JavaScript object that contains information. Actions are the only source of information for the store. Actions have a type field that tells what kind of action to perform and all other fields contain information or data. And there is one other term called Action Creators, these are the function that creates actions. So actions are the information (Objects) and action creator are functions that return these actions.
Let's start by creating action creators for create/read/update/delete/sort/filter/search
functionalities:
import axios from "axios";
import {
URL,
TASK_COUNT,
TASK_LIST,
TASK_ERROR,
TASK_REMOVE,
TASK_UPDATE,
TASK_SEARCH,
} from "../../types/task";
export const getTasksCount = () => async (dispatch) => {
let tkCount = await axios({
method: "get",
url: `${URL}/count`,
});
if (tkCount.data.err !== "") {
dispatch({
type: TASK_ERROR,
payload: tkCount.data.err,
});
}
if (tkCount.data.count) {
dispatch({
type: TASK_COUNT,
payload: tkCount.data.count,
});
}
};
export const listTasks = (page, sort, order) => async (dispatch) => {
let tkList = await axios({
method: "get",
url: `${URL}?page=${page}&sort=${sort}&order=${order}`,
});
if (tkList.data.err !== "") {
dispatch({
type: TASK_ERROR,
payload: tkList.data.err,
});
}
if (tkList.data) {
dispatch({
type: TASK_LIST,
payload: tkList.data,
});
}
};
export const removeTask = (id) => async (dispatch) => {
await axios({
method: "delete",
url: `${URL}/${id}`,
});
dispatch({
type: TASK_REMOVE,
payload: id,
});
};
export const updateTask = (completed, id) => async (dispatch) => {
let tkResp = await axios({
method: "put",
url: `${URL}/${id}`,
data: { completed: completed }
});
dispatch({
type: TASK_UPDATE,
payload: tkResp.data,
});
};
export const searchTasks = (text) => async (dispatch) => {
let tkResp = await axios({
method: "get",
url: `${URL}/search?text=${text}`
});
dispatch({
type: TASK_SEARCH,
payload: tkResp.data,
});
};
Reducers
Actions only tell what to do, but they don't tell how to do, so reducers are the pure functions that take the current state and action and return the new state and tell the store how to do.
Basically, reducer follows the "message" from action.type
and define what to do with data from action.payload
.
import * as actionTypes from "../../types/task";
const initialState = {
taskList: [],
searchList: [],
task: {},
count: 0,
searchCount: 0,
error: null,
msg: null,
loading: false,
};
export const task = (state = initialState, action) => {
switch (action.type) {
case actionTypes.TASK_COUNT:
return {
...state,
count: action.payload,
};
case actionTypes.TASK_LIST:
return {
...state,
taskList: action.payload,
};
case actionTypes.TASK_REMOVE:
return {
...state,
count: parseInt(state.count - 1),
taskList: state.taskList.filter(tk => tk.id !== action.payload),
};
case actionTypes.TASK_UPDATE:
return {
...state,
taskList: state.taskList.map(x => (x.id === action.payload.id ? { ...x, completed: action.payload.completed } : x))
};
case actionTypes.TASK_SEARCH:
return {
...state,
searchCount: action.payload.count,
searchList: action.payload.data,
};
default:
return state;
}
};
Implementation
In this step, we will use useSelector
to get state data and useDispatch
to change the state in our components:
import { useState, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import * as actions from "./redux/actions";
const App = () => {
const { task } = useSelector((obj) => obj);
const dispatch = useDispatch();
.
.
.
useEffect(() => {
dispatch(actions.getTasksCount());
}, [newtask, dispatch]);
useEffect(() => {
dispatch(actions.listTasks(page, sort, order));
}, [newtask, page, sort, order, dispatch]);
.
.
return (
<div className="container mx-auto lg:my-32 md:my-30 sm:my-15 ">
.
.
</div>
);
};
export default App;
If you are using class components instead of functional components, check this old repos with HOC: redux and redux with redux-thunk.
Ref => Rules of Reducers
Ref => Pure vs Impure Functions in Functional Programming
Ref => Pure vs Impure Functions