Quantcast
Channel: GaneshMani
Viewing all articles
Browse latest Browse all 38

Modern React Redux Toolkit - Login & User Registration Tutorial and Example

$
0
0

User Authentication is one of the common workflow in web applications. In this tutorial, we will see how to build a User Login and Signup workflow with Modern react redux toolkit.

Demo

App Demo

Let's scaffold an application using the command,

npx create-react-app redux-workflow --template redux
Enter fullscreen mode Exit fullscreen mode

If you're completely new to redux-toolkit, checkout this article to learn the basic concepts of redux toolkit.

Let me give you a glimpse about the concepts of redux toolkit. Everything in toolkit is grouped as Features. it's called duck pattern.

Action and Reducers are combined in redux toolkit as Slice. To make HTTP API call, we will be using createAsyncThunk. We will discuss about it in detail in the later part of the article.

Create App.js

importReactfrom'react';import'./App.css';import{BrowserRouterasRouter,Switch,Route,Link}from'react-router-dom';importLoginfrom'./features/User/Login';importSignupfrom'./features/User/Signup';importDashboardfrom'./features/User/Dashboard';import{PrivateRoute}from'./helpers/PrivateRoute';functionApp(){return(<divclassName="App"><Router><Switch><Routeexactcomponent={Login}path="/login"/><Routeexactcomponent={Signup}path="/signup"/><PrivateRouteexactcomponent={Dashboard}path="/"/></Switch></Router></div>);}exportdefaultApp;
Enter fullscreen mode Exit fullscreen mode

Before creating components for the workflow. let's create redux slice for our User section. create UserSlice.js inside features/User directory,

import{createSlice,createAsyncThunk}from'@reduxjs/toolkit';exportconstuserSlice=createSlice({name:'user',initialState:{username:'',email:'',isFetching:false,isSuccess:false,isError:false,errorMessage:'',},reducers:{// Reducer comes here},extraReducers:{// Extra reducer comes here},});exportconstuserSelector=(state)=>state.user;
Enter fullscreen mode Exit fullscreen mode

Here, we use createSlice which handles the action and reducer in a single function. After that, add the reducer in redux store

app/store.js

import{configureStore}from'@reduxjs/toolkit';import{userSlice}from'../features/User/UserSlice';exportdefaultconfigureStore({reducer:{user:userSlice.reducer,},});
Enter fullscreen mode Exit fullscreen mode

Signup Functionality

Once we create a basic structure for redux and store. it's time to create components for the application. Create Signup.js inside features/User directory,

importReact,{Fragment,useEffect}from'react';import{Link}from'react-router-dom';import{useForm}from'react-hook-form';import{useSelector,useDispatch}from'react-redux';import{signupUser,userSelector,clearState}from'./UserSlice';import{useHistory}from'react-router-dom';importtoastfrom'react-hot-toast';constSignup=()=>{constdispatch=useDispatch();const{register,errors,handleSubmit}=useForm();consthistory=useHistory();const{isFetching,isSuccess,isError,errorMessage}=useSelector(userSelector);constonSubmit=(data)=>{dispatch(signupUser(data));};useEffect(()=>{return()=>{dispatch(clearState());};},[]);useEffect(()=>{if(isSuccess){dispatch(clearState());history.push('/');}if(isError){toast.error(errorMessage);dispatch(clearState());}},[isSuccess,isError]);return(<Fragment><divclassName="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8"><divclass="sm:mx-auto sm:w-full sm:max-w-md"><h2class="mt-6 text-center text-3xl font-extrabold text-gray-900">
            Sign Up to your account
          </h2></div><divclassName="mt-8 sm:mx-auto sm:w-full sm:max-w-md"><divclassName="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"><formclassName="space-y-6"onSubmit={handleSubmit(onSubmit)}method="POST">{*/ Form Comes Here  */}</form><divclass="mt-6"><divclass="relative"><divclass="relative flex justify-center text-sm"><spanclass="px-2 bg-white text-gray-500">
                    Or <Linkto="login"> Login</Link></span></div></div></div></div></div></div></Fragment>);};exportdefaultSignup;
Enter fullscreen mode Exit fullscreen mode

Here, we use React Hook Form to handle Form validation. Whenever we want to dispatch an action in redux, we use useDispatch provided by react-redux.

constdispatch=useDispatch();
Enter fullscreen mode Exit fullscreen mode

We can access redux state in component using hooks, useSelector

const{isFetching,isSuccess,isError,errorMessage}=useSelector(userSelector);
Enter fullscreen mode Exit fullscreen mode

Now, when an user submits a signup form, we need to dispatch an action by passing required data.

constonSubmit=(data)=>{dispatch(signupUser(data));};
Enter fullscreen mode Exit fullscreen mode

Let's create that action in UserSlice.js

exportconstsignupUser=createAsyncThunk('users/signupUser',async({name,email,password},thunkAPI)=>{try{constresponse=awaitfetch('https://mock-user-auth-server.herokuapp.com/api/v1/users',{method:'POST',headers:{Accept:'application/json','Content-Type':'application/json',},body:JSON.stringify({name,email,password,}),});letdata=awaitresponse.json();console.log('data',data);if(response.status===200){localStorage.setItem('token',data.token);return{...data,username:name,email:email};}else{returnthunkAPI.rejectWithValue(data);}}catch(e){console.log('Error',e.response.data);returnthunkAPI.rejectWithValue(e.response.data);}});
Enter fullscreen mode Exit fullscreen mode

Main purpose of using createAsyncThunk is it provides the API state out of the box. In traditional redux way, we need to handle the api state such as loading, success and failed.

createAsyncThunk provides us those states out of the box. To implement it, we just need to use the action name and the state of it.

Redux AsyncDataFlow

Image Source

createAsyncThunk takes two argument,

  • Name that helps to identify action types.
  • A callback function that should return a promise

Further, callback function take two arguments. first, is the value that we pass from dispatched action and second argument is Thunk API config.

Once it returns a promise, either it will resolve or reject the promise. By default it provides us three state which are pending, fulfilled and rejected.

extraReducers:{[signupUser.fulfilled]:(state,{payload})=>{state.isFetching=false;state.isSuccess=true;state.email=payload.user.email;state.username=payload.user.name;},[signupUser.pending]:(state)=>{state.isFetching=true;},[signupUser.rejected]:(state,{payload})=>{state.isFetching=false;state.isError=true;state.errorMessage=payload.message;}}
Enter fullscreen mode Exit fullscreen mode

It updates the redux state which will update our component using hook useSelector. Once the signup successfully, it redirects to dashboard component.

useEffect(()=>{if(isSuccess){dispatch(clearState());history.push('/');}if(isError){toast.error(errorMessage);dispatch(clearState());}},[isSuccess,isError]);
Enter fullscreen mode Exit fullscreen mode

Login Functionality

Most of the logic will be similar to login workflow. create Login.js inside features/User directory and add the following code,

importReact,{Fragment,useEffect}from'react';import{Link}from'react-router-dom';import{useForm}from'react-hook-form';import{useSelector,useDispatch}from'react-redux';import{loginUser,userSelector,clearState}from'./UserSlice';importtoastfrom'react-hot-toast';import{useHistory}from'react-router-dom';constLogin=({})=>{constdispatch=useDispatch();consthistory=useHistory();const{register,errors,handleSubmit}=useForm();const{isFetching,isSuccess,isError,errorMessage}=useSelector(userSelector);constonSubmit=(data)=>{dispatch(loginUser(data));};useEffect(()=>{return()=>{dispatch(clearState());};},[]);useEffect(()=>{if(isError){toast.error(errorMessage);dispatch(clearState());}if(isSuccess){dispatch(clearState());history.push('/');}},[isError,isSuccess]);return(<Fragment><divclassName="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8"><divclass="sm:mx-auto sm:w-full sm:max-w-md"><h2class="mt-6 text-center text-3xl font-extrabold text-gray-900">
            Sign in to your account
          </h2></div><divclassName="mt-8 sm:mx-auto sm:w-full sm:max-w-md"><divclassName="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"><formclassName="space-y-6"onSubmit={handleSubmit(onSubmit)}method="POST">{*/ Login Form Comes Here */}</form><divclass="mt-6"><divclass="relative"><divclass="relative flex justify-center text-sm"><spanclass="px-2 bg-white text-gray-500">
                    Or <Linkto="signup"> Signup</Link></span></div></div></div></div></div></div></Fragment>);};exportdefaultLogin;
Enter fullscreen mode Exit fullscreen mode

Here, we dispatch loginUser action which makes HTTP call in the redux slice.

constonSubmit=(data)=>{dispatch(loginUser(data));};
Enter fullscreen mode Exit fullscreen mode

create an AsyncThunk function inside UserSlice.js and add the following code,

exportconstloginUser=createAsyncThunk('users/login',async({email,password},thunkAPI)=>{try{constresponse=awaitfetch('https://mock-user-auth-server.herokuapp.com/api/v1/auth',{method:'POST',headers:{Accept:'application/json','Content-Type':'application/json',},body:JSON.stringify({email,password,}),});letdata=awaitresponse.json();console.log('response',data);if(response.status===200){localStorage.setItem('token',data.token);returndata;}else{returnthunkAPI.rejectWithValue(data);}}catch(e){console.log('Error',e.response.data);thunkAPI.rejectWithValue(e.response.data);}});
Enter fullscreen mode Exit fullscreen mode

Promise will either be resolved or rejected based on HTTP call, let's handle it inside our reducer with the states,

[loginUser.fulfilled]:(state,{payload})=>{state.email=payload.email;state.username=payload.name;state.isFetching=false;state.isSuccess=true;returnstate;},[loginUser.rejected]:(state,{payload})=>{console.log('payload',payload);state.isFetching=false;state.isError=true;state.errorMessage=payload.message;},[loginUser.pending]:(state)=>{state.isFetching=true;},
Enter fullscreen mode Exit fullscreen mode

Once it updates our redux state, we will use it inside our component to render the result.

const{isFetching,isSuccess,isError,errorMessage}=useSelector(userSelector);// Update UI based on the redux state(Success or Error)useEffect(()=>{if(isError){toast.error(errorMessage);dispatch(clearState());}if(isSuccess){dispatch(clearState());history.push('/');}},[isError,isSuccess]);
Enter fullscreen mode Exit fullscreen mode

Finally our Dashboard.js will be rendered with update user state from redux,

importReact,{Fragment,useEffect}from'react';import{useSelector,useDispatch}from'react-redux';import{userSelector,fetchUserBytoken,clearState}from'./UserSlice';importLoaderfrom'react-loader-spinner';import{useHistory}from'react-router-dom';constDashboard=()=>{consthistory=useHistory();constdispatch=useDispatch();const{isFetching,isError}=useSelector(userSelector);useEffect(()=>{dispatch(fetchUserBytoken({token:localStorage.getItem('token')}));},[]);const{username,email}=useSelector(userSelector);useEffect(()=>{if(isError){dispatch(clearState());history.push('/login');}},[isError]);constonLogOut=()=>{localStorage.removeItem('token');history.push('/login');};return(<divclassName="container mx-auto">{isFetching?(<Loadertype="Puff"color="#00BFFF"height={100}width={100}/>):(<Fragment><divclassName="container mx-auto">
            Welcome back <h3>{username}</h3></div><buttononClick={onLogOut}className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">
            Log Out
          </button></Fragment>)}</div>);};exportdefaultDashboard;
Enter fullscreen mode Exit fullscreen mode

Complete source code is available here


Viewing all articles
Browse latest Browse all 38

Trending Articles