Modern React Redux Toolkit - Login & User Registration Tutorial and Example
Tags: react, redux, webdev, tutorial
Published: 2021-02-10 12:37:30


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](https://dev-to-uploads.s3.amazonaws.com/i/y5vgcyyr8j3cvwybdzzp.gif)

Let's  scaffold an application using the command, 

```bash
npx create-react-app redux-workflow --template redux
```

If you're completely new to redux-toolkit, checkout [this](https://cloudnweb.dev/2020/04/modern-react-redux-tutotials-redux-toolkit) 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](https://redux.js.org/style-guide/style-guide) 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`

```jsx
import React from 'react';
import './App.css';
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
import Login from './features/User/Login';
import Signup from './features/User/Signup';
import Dashboard from './features/User/Dashboard';
import { PrivateRoute } from './helpers/PrivateRoute';

function App() {
  return (
    <div className="App">
      <Router>
        <Switch>
          <Route exact component={Login} path="/login" />
          <Route exact component={Signup} path="/signup" />
          <PrivateRoute exact component={Dashboard} path="/" />
        </Switch>
      </Router>
    </div>
  );
}

export default App;
```

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

```jsx
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const userSlice = createSlice({
  name: 'user',
  initialState: {
    username: '',
    email: '',
    isFetching: false,
    isSuccess: false,
    isError: false,
    errorMessage: '',
  },
  reducers: {
	  // Reducer comes here
  },
  extraReducers: {
	   // Extra reducer comes here
  },
});

export const userSelector = (state) => state.user;
```

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`

```jsx
import { configureStore } from '@reduxjs/toolkit';
import { userSlice } from '../features/User/UserSlice';
export default configureStore({
  reducer: {
    user: userSlice.reducer,
  },
});
```

## 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,

 

```jsx
import React, { 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';
import toast from 'react-hot-toast';

const Signup = () => {
  const dispatch = useDispatch();
  const { register, errors, handleSubmit } = useForm();
  const history = useHistory();

  const { isFetching, isSuccess, isError, errorMessage } = useSelector(
    userSelector
  );
  const onSubmit = (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>
      <div className="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
        <div class="sm:mx-auto sm:w-full sm:max-w-md">
          <h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
            Sign Up to your account
          </h2>
        </div>
        <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
          <div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
            <form
              className="space-y-6"
              onSubmit={handleSubmit(onSubmit)}
              method="POST"
            >
             {*/ Form Comes Here  */}
            </form>
            <div class="mt-6">
              <div class="relative">
                <div class="relative flex justify-center text-sm">
                  <span class="px-2 bg-white text-gray-500">
                    Or <Link to="login"> Login</Link>
                  </span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </Fragment>
  );
};

export default Signup;
```

Here, we use [React Hook Form](https://react-hook-form.com/) to handle Form validation. Whenever we want to dispatch an action in redux, we use `useDispatch` provided by `react-redux`.

```jsx
 const dispatch = useDispatch();
```

We can access redux state in component using hooks, `useSelector`

```jsx
const { isFetching, isSuccess, isError, errorMessage } = useSelector(
    userSelector
  );
```

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

```jsx
const onSubmit = (data) => {
    dispatch(signupUser(data));
  };
```

Let's create that action in `UserSlice.js` 

```jsx
export const signupUser = createAsyncThunk(
  'users/signupUser',
  async ({ name, email, password }, thunkAPI) => {
    try {
      const response = await fetch(
        '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,
          }),
        }
      );
      let data = await response.json();
      console.log('data', data);

      if (response.status === 200) {
        localStorage.setItem('token', data.token);
        return { ...data, username: name, email: email };
      } else {
        return thunkAPI.rejectWithValue(data);
      }
    } catch (e) {
      console.log('Error', e.response.data);
      return thunkAPI.rejectWithValue(e.response.data);
    }
  }
);
```

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](https://redux.js.org/assets/images/ReduxAsyncDataFlowDiagram-d97ff38a0f4da0f327163170ccc13e80.gif)

[Image Source](https://redux.js.org/tutorials/fundamentals/part-6-async-logic)

`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`. 

```jsx
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;
    }
}
```

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

```jsx
useEffect(() => {
    if (isSuccess) {
      dispatch(clearState());
      history.push('/');
    }

    if (isError) {
      toast.error(errorMessage);
      dispatch(clearState());
    }
  }, [isSuccess, isError]);
```

## Login Functionality

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

```jsx
import React, { 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';
import toast from 'react-hot-toast';
import { useHistory } from 'react-router-dom';

const Login = ({}) => {
  const dispatch = useDispatch();
  const history = useHistory();
  const { register, errors, handleSubmit } = useForm();
  const { isFetching, isSuccess, isError, errorMessage } = useSelector(
    userSelector
  );
  const onSubmit = (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>
      <div className="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
        <div class="sm:mx-auto sm:w-full sm:max-w-md">
          <h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
            Sign in to your account
          </h2>
        </div>
        <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
          <div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
            <form
              className="space-y-6"
              onSubmit={handleSubmit(onSubmit)}
              method="POST"
            >
             {*/ Login Form Comes Here */}
            </form>
            <div class="mt-6">
              <div class="relative">
                <div class="relative flex justify-center text-sm">
                  <span class="px-2 bg-white text-gray-500">
                    Or <Link to="signup"> Signup</Link>
                  </span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </Fragment>
  );
};

export default Login;
```

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

```jsx
const onSubmit = (data) => {
    dispatch(loginUser(data));
  };
```

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

```jsx
export const loginUser = createAsyncThunk(
  'users/login',
  async ({ email, password }, thunkAPI) => {
    try {
      const response = await fetch(
        '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,
          }),
        }
      );
      let data = await response.json();
      console.log('response', data);
      if (response.status === 200) {
        localStorage.setItem('token', data.token);
        return data;
      } else {
        return thunkAPI.rejectWithValue(data);
      }
    } catch (e) {
      console.log('Error', e.response.data);
      thunkAPI.rejectWithValue(e.response.data);
    }
  }
);
```

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

```jsx
[loginUser.fulfilled]: (state, { payload }) => {
      state.email = payload.email;
      state.username = payload.name;
      state.isFetching = false;
      state.isSuccess = true;
      return state;
    },
    [loginUser.rejected]: (state, { payload }) => {
      console.log('payload', payload);
      state.isFetching = false;
      state.isError = true;
      state.errorMessage = payload.message;
    },
    [loginUser.pending]: (state) => {
      state.isFetching = true;
    },
```

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

```jsx
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]);
```

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

 

```jsx
import React, { Fragment, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { userSelector, fetchUserBytoken, clearState } from './UserSlice';
import Loader from 'react-loader-spinner';
import { useHistory } from 'react-router-dom';

const Dashboard = () => {
  const history = useHistory();

  const dispatch = 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]);

  const onLogOut = () => {
    localStorage.removeItem('token');

    history.push('/login');
  };

  return (
    <div className="container mx-auto">
      {isFetching ? (
        <Loader type="Puff" color="#00BFFF" height={100} width={100} />
      ) : (
        <Fragment>
          <div className="container mx-auto">
            Welcome back <h3>{username}</h3>
          </div>

          <button
            onClick={onLogOut}
            className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
          >
            Log Out
          </button>
        </Fragment>
      )}
    </div>
  );
};

export default Dashboard;
```

Complete source code is available [here](https://github.com/ganeshmani/redux-toolkit-user-flow)