28 Feb 2022
React app authentication with hooks, redux-toolkit & typescript
4 mins read
Share:

Idea for this article was formulated as a part of a sizeable application development with React and Redux. As application progressed it was very difficult to maintain the repo and code, as a solution redux team introduced redux-toolkit https://redux-toolkit.js.org/ . Here is how we can use it to reduce the effort of writing lot of many lines of codes for action and constants in redux.

So what is Redux-toolkit?

Redux-toolkit is a new way to implement Redux, a more functional way. Its cleaner, you write less lines of code and we get the same Redux state management, we have come to love and trust. The best part is it comes with redux-thunk & already built into it. Plus they use immerJs  to handle all the immutability, so all we need to think about is what needs to get done.

The application will have a basic authentication from a mock api and listing of data from https://jsonplaceholder.typicode.com/. The article will cover how to implement React in Typescript , how to wire redux-toolkit and hooks in a React application.

Initially create a login form and on submit , call the function/actions in the slice.

import React from 'react';
import { Card, Form, Input, Button, } from 'antd';
import { authenticateUser } from './loginSlice';
import { useAppDispatch } from '../../hooks';
import { isAuthenticated } from '../../services/authenticationService';
import { useHistory } from 'react-router-dom';


export function LoginPage() {

    const dispatch = useAppDispatch();
    let history = useHistory();
    if (isAuthenticated()) {
      history.push('/v1');
    }

    const onFinish = (values: any) => {
        dispatch(authenticateUser(values));
    };

    return (
            <Card hoverable={true} title="Authentication" className="login-card">
                <Form
                    name="basic"
                    labelCol={{ span: 8 }}
                    wrapperCol={{ span: 16 }}
                    initialValues={{ remember: true }}
                    onFinish={onFinish}
                    autoComplete="off"
                >
                    <Form.Item
                        label="Email"
                        name="username"
                        rules={[{ required: true, message: 'Please input your email!' , type: 'email'} ]}
                    >
                        <Input />
                    </Form.Item>

                    <Form.Item
                        label="Password"
                        name="password"
                        rules={[{ required: true, message: 'Please input your password!' }]}
                    >
                        <Input.Password />
                    </Form.Item>


                    <Form.Item wrapperCol={{ offset: 8, span: 16 }}>
                        <Button type="primary" htmlType="submit">
                            Submit
                        </Button>
                    </Form.Item>
                </Form>
            </Card>
    )
}

 

Here is a sample slice for login. The beauty and simplicity of redux-toolkit is we don’t need any separate files for action , enums and reducers. Everything can come from a single authentication slice.

import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { authenticate } from '../../services/authenticationService';
import { setTokens } from '../../services/localStorage';
import { RootState } from '../../store';
import { history } from '../../../helpers/history';


export interface IAuthentication {
  isProcessingRequest: boolean;
  accessToken?: string;
}
const initialState: IAuthentication = { isProcessingRequest: false };
export const authenticationSlice = createSlice({
  name: 'authentication',
  initialState,
  reducers: {
    start: (state) => {
      return {
        ...state,
        isProcessingRequest: true,
      };
    },
    success: (state, action: PayloadAction<any>) => {
      return {
        ...state,
        isProcessingRequest: false,
      };
    },
    error: (state, action: PayloadAction<string>) => {
      return {
        ...state,
        isProcessingRequest: false,
      };
    },
  },
});
export const authenticateUser = (userData: any) => async (dispatch: any) => {
  try {
    const authData = await authenticate(
     userData
    );
    setTokens(authData.data);
    dispatch(success(authData.data));
    history.push('/v1');
  } catch (err) {
    dispatch(error(err));
  }
};
export const { start, success, error } = authenticationSlice.actions;
export const selectAuthentication = (state: RootState) => state.authentication;
export const authenticationReducer = authenticationSlice.reducer;

User listing from external API — https://jsonplaceholder.typicode.com/users.Intially create a user listing template using ANTD and its interface.

import React , { useEffect } from 'react';
import { useAppDispatch , useAppSelector} from '../../hooks';
import { fetchUsers , selectUserLists } from './userSlice';
import { ColumnsType } from 'antd/es/table';
import { IUser } from './interface';
import { Table } from 'antd';

export const UsersListPage = () => {
    const dispatch = useAppDispatch();

    const usersList = useAppSelector(selectUserLists);

    const columns: ColumnsType<IUser> = [
        {
          key: '1',
          title: 'Name',
          dataIndex: 'name',
          ellipsis: true,
          fixed: "left",
        },
        {
            key: '2',
            title: 'Username',
            dataIndex: 'username',
            ellipsis: true,
        },
        {
            key: '3',
            title: 'Email',
            dataIndex: 'email',
            ellipsis: true,
        },
        {
            key: '4',
            title: 'Website',
            dataIndex: 'website',
            ellipsis: true,
        },
        {
            key: '5',
            title: 'Address',
            dataIndex: 'address',
            ellipsis: true,
            render: (text) => <span>{text.street}, {text.city} , {text.zipcode}</span>
        },
        {
            key: '6',
            title: 'Company',
            dataIndex: 'company',
            ellipsis: true,
            render: (text) => <span>{text.name}, {text.catchpharse}</span>
        }
    ]


    useEffect(() => {
        dispatch(fetchUsers());
      }, [dispatch]);

    return(
        <Table<IUser>
          loading={usersList.isLoadingUsers}
          dataSource = { usersList.userList}
          columns = {columns}
          rowKey="id"
        />

        
    )
}

 

export interface IUser {
    id: number;
    name: string;
    username: string;
    email: string;
    address: IAddress;
    phone: string;
    website: string;
    compnay: ICompany;

}

export interface IAddress {
    street: string;
    suite: string;
    city: string;
    zipcode: string;
    geo: IGeo;

}

export interface IGeo {
    lat : string;
    lng: string
}

export interface ICompany {
    name: string;
    catchPharase: string;
    bs: string;
}

 

Now create a user slice where we have the function to call the external API’s and update the state of the application. Bind the corresponding user slice to the central store of the application.

import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { getUserList } from '../../services/userService';
import { RootState } from '../../store';
import { IUser } from './interface';

export interface IUsersList {
  isLoadingUsers: boolean;
  userList?: IUser[];
}
const initialState: IUsersList = { isLoadingUsers: false };
export const userListSlice = createSlice({
  name: 'userList',
  initialState,
  reducers: {
    start: (state) => {
      return {
        ...state,
        isLoadingUsers: true,
      };
    },
    success: (state, action: PayloadAction<any>) => {
      return {
        ...state,
        ...action.payload,
        isLoadingUsers: false,
      };
    },
    error: (state, action: PayloadAction<string>) => {
      return {
        ...state,
        isLoadingUsers: false,
      };
    },
  },
});
export const fetchUsers = () => async (dispatch: any) => {
  dispatch(start());
  try {
    const userLists = await getUserList();
    dispatch(success({userList : userLists}));
  } catch (err) {
    dispatch(error(err));
  }
};
export const { start, success, error } = userListSlice.actions;
export const selectUserLists = (state: RootState) => state.userList;
export const usersListReducer = userListSlice.reducer;

 

import { configureStore } from '@reduxjs/toolkit';
import { authenticationReducer } from './pages/Login/loginSlice';
import { usersListReducer } from './pages/Users-List/userSlice'
 

export const store = configureStore({
  reducer: {
    authentication: authenticationReducer,
    userList: usersListReducer
  },
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;

Here is the GitHub link for the reference code — https://github.com/jkrrishac/redux-toolkit-hooks-authentication

Written by
Jayakrishna C
Subscribe to Our Articles
Subscribe to Our Articles
We're committed to your privacy. Flycatch uses the information you provide to us to contact you about our relevant content, products, and services. You may unsubscribe from these communications at any time.
Related Articles