Table of contents
This article covers Thunk middleware in detail and Extra Reducers and we will continue with the same application. You can read out Redux Toolkit part1
Requirement
When the user login into the application, the data should be saved inside the redux store.
Approach: The login functionality code will be asynchronous so will handle inside redux-thunk.
Redux-Thunk: Middleware that handles the asynchronous task.
When you install the toolkit library, thunk gets already installed, if you want to check then open the nodes modules and search for the redux-thunk
Redux Toolkit provides the createAsyncThunk method to use thunk middleware.
Currently, in our application the logic of login is written in the login.js file, now we will move the logic inside createAsyncThunk, and for that, we will create a new Slice AuthSlice.js, now you already know the process of how to create a slice and how to configure it with store, I will not waste your time in that
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
userInfo: {},
userError: {},
loading:true
};
const AuthSlice = createSlice({
name: "auth",
initialState,
extraReducers:{}
});
I hope you already noticed that I have written extraReducers instead of reducers, you will get it in a few minutes, let's write our thunk logic for login. we need to import and call the createAsyncThunk method and that will be provided by Redux Toolkit
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
const userAuth = createAsyncThunk('login', async(value) => {
});
CreateAsyncThunk
The first parameter is the unique string(you can pass anything) and the second is an async function that can receive two-parameter(the first one is value and the second is a thunk object, will discuss it later)
moving our login functionality inside an async function
export const userAuth = createAsyncThunk("user/login", async (value) => {
const { email, password } = value;
const response = await signInWithEmailAndPassword(auth, email, password);
let userInfo = {};
userInfo = {
displayName: response.user.displayName,
email: response.user.email,
uid: response.user.uid,
};
return userInfo;
});
We have to return only those value that we want to save inside Redux and do not handle errors here we will handle everything in extra reducers
##ExtraReducers
This will handle the async task response, the async function we are passing inside the createAsyncThunk will return a promise, and the promise will be handled by extraReducers. let's check the code you will understand it
const AuthSlice = createSlice({
name: "auth",
initialState,
extraReducers: (builder) => {
builder.addCase(userAuth.pending, (state) => {
state.loading = true;
});
builder.addCase(userAuth.fulfilled, (state, action) => {
state.loading = false;
state.userInfo = action.payload;
state.error = "";
state.isAuthenticated = true;
});
builder.addCase(userAuth.rejected, (state, action) => {
state.loading = false;
state.userInfo = {};
state.error = action.error;
});
},
});
A promise can have only three states (pending, fulfilled, and rejected), so with the builder.addCase we handled all of the conditions. userAuth is the name of our thunk.
now let's extract our reducer from authSlice and check our final code
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from "../firebase";
const initialState = {
userInfo: {},
userError: {},
loading: true,
isAuthenticated: false,
};
export const userAuth = createAsyncThunk("user/login", async (value) => {
const { email, password } = value;
const response = await signInWithEmailAndPassword(auth, email, password);
let userInfo = {};
userInfo = {
displayName: response.user.displayName,
email: response.user.email,
uid: response.user.uid,
};
return userInfo;
});
const AuthSlice = createSlice({
name: "auth",
initialState,
extraReducers: (builder) => {
builder.addCase(userAuth.pending, (state) => {
state.loading = true;
});
builder.addCase(userAuth.fulfilled, (state, action) => {
state.loading = false;
state.userInfo = action.payload;
state.error = "";
state.isAuthenticated = true;
});
builder.addCase(userAuth.rejected, (state, action) => {
state.loading = false;
state.userInfo = {};
state.error = action.error;
});
},
});
export const AuthReducer = AuthSlice.reducer;
Now we have to dispatch our thunk action on the login page, open login.js, and write code for the handleSubmit method
const handleSubmit = (event) => {
event.preventDefault();
dispatch(userAuth(userInfo));
};
and make sure you have added the authReducer inside the store.js
import { configureStore } from "@reduxjs/toolkit";
import { AuthReducer } from "./AuthSlice";
import { PostReducer } from "./PostSlice";
export const store = configureStore({
reducer: {
post: PostReducer,
auth: AuthReducer,
},
});
After doing all this user will be able to login into the application.
Second Scenario
Now let's check a condition where I have multiple thunks, we will move our google sign-in functionality into thunk. That will create a new thunk and will move our logic inside that
export const googleSignIn = createAsyncThunk("user/googleSignIn", async () => {
const response = await signInWithPopup(auth, provider);
await updateProfile(auth.currentUser, {
displayName: response.user.displayName,
phoneNumber: response.user.phoneNumber,
});
const userInfo = {
displayName: response.user.displayName,
email: response.user.email,
phoneNumber: response.user.phoneNumber,
uid: response.user.uid
};
await setDoc(doc(db, "Users", response.user.uid), userInfo);
return userInfo;
});
Now let's write extra reducers for it
extraReducers: (builder) => {
builder.addCase(userAuth.pending, (state) => {
state.loading = true;
});
builder.addCase(userAuth.fulfilled, (state, action) => {
console.log("action.payload", action.payload);
state.loading = false;
state.userInfo = action.payload;
state.error = "";
state.isAuthenticated = true;
});
builder.addCase(userAuth.rejected, (state, action) => {
state.loading = false;
state.userInfo = {};
state.error = action.error;
});
builder.addCase(googleSignIn.pending, (state) => {
state.loading = true;
});
builder.addCase(googleSignIn.fulfilled, (state, action) => {
console.log("action.payload", action.payload);
state.loading = false;
state.userInfo = action.payload;
state.error = "";
state.isAuthenticated = true;
});
builder.addCase(googleSignIn.rejected, (state, action) => {
state.loading = false;
state.userInfo = {};
state.error = action.error;
});
},
Did you notice that all userAuth and googleSignIn thunk is returning the same values, but we are writing 6 cases for that?
Note: If the cases are the same for two thunks then we can reduce it but if cases are different then we have to write as many cases as required
As we have the same cases for userAuth and GoogleSignIn, so we will reduce it to three, Instead of using the builder.addCase() we will call the builder.addMatcher().
extraReducers: (builder) => {
builder.addMatcher(
isAnyOf(userAuth.pending, googleSignIn.pending),
(state) => {
state.loading = true;
}
);
builder.addMatcher(
isAnyOf(userAuth.fulfilled, googleSignIn.fulfilled),
(state, action) => {
state.loading = false;
state.userInfo = action.payload;
state.error = "";
state.isAuthenticated = true;
}
);
builder.addMatcher(
isAnyOf(userAuth.rejected, googleSignIn.rejected),
(state, action) => {
state.loading = false;
state.userInfo = {};
state.error = action.error;
}
);
IsAnyOf: This function is provided by Redux Toolkit which makes sure either of the condition should match. you can pass any number of cases inside isAnyOf, but make sure the cases should be the same.
now in login.js we will dispatch googleSignIn
const handleGoogleButton = () => {
dispatch(googleSignIn());
};
Our Second scenario has been completed and you will be able to sign up with google.
Now we have covered most of the things, let's note some key points:
- In createAsyncMethod, the second parameter which is a function can accept two parameters first one is a value (which we send when we dispatch an action) and the second parameter is a think API
thunkApi:- gives us access to store state, dispatch etc(etc:- you can check in docs if you want more info mostly these two are helpful)
export const userAuth = createAsyncThunk("user/login", async (value,{getState, dispatch}) => {
const state = getState(); // store state
dispatch() // you can dispatch other actions from here
});
We have covered most of the things now, If you liked the content please follow me on LinkedIn and subscribe to my youtube channel. If you have any questions then please drop a comment.
Did you find this article valuable?
Support Aman Singh Tomar by becoming a sponsor. Any amount is appreciated!