React Redux with TypeScript … practical example ⚛️

In general before using Redux, you need to be sure that the value which you intend to store in Redux store is being used in multiple components such as userInfo (after login), shopping cart Items ….

First I would like to mention the main concepts of Redux:

Redux Store:
Which holds the states of your App (It is a single source of truth of state for your application). However, app can’t directly update Redux store state but this can be done by dispatching a specific action to handle a specific task (Add new user, Update user, delete user …)

Redux Action:
Is a plain JavaScript object which contains information to descript what we need to happen. However, it doesn’t describe how it happens. Which is the responsibility of Reducer (which consumes the action).
Action contains:
I. type (mandatory): which tells us what type of what we need to happen by dispatching this action (type should be a string).
II. payload (optional): which is a data field in action, which hold the data from our app to be uses inside the reducer to eventually update Redux store
We can use an action creator which is a function return a blain JS object (an action)

Redux Reducer:
It is used to change the state (to be more accurate reducer return a new update instance of the state with all required updates) by passing an action to.
Reducer is a pure function that takes current application state and an action and return the new updated state.
Every time an action is dispatched, all reducers will be triggered. Each reducer compares its switch cases with action type and if there is a match this the corresponding reducer will return a new instance of the state based of action payload if existing and reducer logic.

Redux middleware:
It uses to intercept every action before hitting the reducer so this middleware can change the action or even cancel it. Also, it can be used in logging, making asynchronous requests …

useSelector(): get current Redux state from Redux Store

In case we want to fetch user information data form external API,
we could have three options to use:

First option:
We can use Redux Thunk which is a middleware to handle the asynchronous request. Redux Thunk will return a function which receives the dispatch which will be used to dispatch a regular synchronous action and after receiving the result from the external API.
The steps will be as the following:
1- When the user visit a page ThunkAction will be dispatched inside useEffect.
2- Inside the ThunkAction the external API (asynchronous request) will be called.
3- After getting the result, a regular synchronous action will be dispatched based on the API response status.
4- This Action will be consumed by a reducer which will return a new instance of the state.
5- To get the desired state we can use the useSelector hook and choose the desired state slice.
6- We will use this state from useSelector to update React component.
The full example can be found in the following repository:
https://github.com/alaa-m1/ecommerce-webapp-react-redux/blob/master/src/store/products/productsActions.ts

Second option:
We can use a combination of Redux and ReactQuery:
In this scenario we can use Redux to manage client state and ReactQuery to manage fetching caching and updating server state
So we will use ReactQuery to fetch the data and after receiving the result we can save the received data using Redux.
Some of the benefits when applying this approach:
• Simplify asynchronous requests handling: for example by using simple useQuery(..) we can get {loading, refetch, isLoading,isFetching, isError … } without the need to write any extra code.
• Request caching.
• Larger community support
The full example can be found in the following repository:
https://github.com/alaa-m1/ecommerce-webapp-react-redux/blob/master/src/pages/Home/HomePage.tsx

Third option:
We can use RTK Query data fetching and caching API.

The Practical Example: 💻💻

userActions.ts

import { UserInfo } from firebase/auth;
import { createAction } from utils/redux/reduxUtils;
import { userActionTypes } from ./userActionTypes;

export const setCurrentUser = (user: null | UserInfo) =>
createAction(userActionTypes.SET_CURRENT_USER, user);
export const setCurrentThemeMode = (mode: light | dark) =>
createAction(userActionTypes.SET_CURRENT_THEME_MODE, mode);

userActionTypes.ts

export const userActionTypes={
SET_CURRENT_USER:SET_CURRENT_USER,
SET_CURRENT_THEME_MODE:SET_CURRENT_THEME_MODE
}

userReducer.ts

import { UserInfo } from firebase/auth
import { userActionTypes } from ./userActionTypes

type UserState={
currentUser: null | UserInfo;
themeMode: light | dark;
}

interface UserAction {
type: any,
payload: null | UserInfo | light | dark
}

const initailState: UserState = {
currentUser: null,
themeMode: light
}
export const userReducer = (state = initailState, action: UserAction) => {
const { type, payload } = action;
switch (type) {
case userActionTypes.SET_CURRENT_USER:
return {
state,
currentUser: payload as null | UserInfo
}
case userActionTypes.SET_CURRENT_THEME_MODE:
return {
state,
themeMode: payload as light | dark
}
default:
return state
}
}

rootReducer.ts

import {combineReducers} from redux;
import { userReducer } from ./user/userReducer;

export const rootReducer = combineReducers({
user: userReducer,
….
})

🎉🎉[Project on GitHub]: E-commerce Web Application: React, Redux, React Query, MUI, Firebase … (https://github.com/alaa-m1/ecommerce-webapp-react-redux)

Leave a Reply

Your email address will not be published. Required fields are marked *