React Context-API Pro | Build state management using useContext + useReducer | Typescript

React Context-API Pro | Build state management using useContext + useReducer | Typescript

The Context API in React is a powerful feature that allows you to manage state globally across your application without the need to pass props down through multiple levels of your component tree. When combined with TypeScript, it becomes even more robust, providing type safety and better code maintainability. In this article, I’ll walk you through setting up a Context API for authentication in a React app using modern conventions and TypeScript.

Project Structure

First, let’s outline the project structure. We’ll split our Context setup into four separate files:

context.tsx: This file will create and export the context.
provider.tsx: This file will provide the context to the component tree.
reducer.ts: This file will define the initial state and reducer function.
actions.ts: This file will contain action creators.
state.ts: This file will contain the initial state of the context
useAuthContext.ts: This file contain a custom hook which will help to call context

01. Creating the Context

We’ll start by creating the context.tsx file. This file will initialize the context and provide a custom hook for easy access.

import { createContext } from react;
import { AuthContextProps } from ../types;

export const AuthContext = createContext<AuthContextProps | undefined>(undefined);

02. Defining the Initial State of the Context

After that we will define the initial state for the context in the state.ts. This initial state will be use for storing all the datas.

import { AuthState } from ../types;

export const initialState: AuthState = {
isAuthenticated: false,
user: null,
token: null,
};

03. Setting Up the Provider

Next, we’ll set up the provider in the provider.tsx file. This component will use the useReducer hook to manage the state and pass it down via the context provider.

import React, { useReducer } from react;
import { AuthProviderProps } from ../types;
import { AuthContext } from ./context;
import { AuthReducer } from ./reducer;
import { initialState } from ./state;

export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, initialState);

return <AuthContext.Provider value={{ state, dispatch }}>{children}</AuthContext.Provider>;
};

04. Defining the Reducer

The reducer is the heart of our state management. In the reducer.ts file, we’ll define our initial state and the reducer function to handle actions.

import { AuthAction, AuthState } from ../types;

export const AuthReducer = (state: AuthState, action: AuthAction): AuthState => {
switch (action.type) {
case LOGIN:
return {
state,
isAuthenticated: true,
user: action.payload.user,
token: action.payload.token,
};
case LOGOUT:
return {
state,
isAuthenticated: false,
user: null,
token: null,
};
default:
return state;
}
};

05. Creating Actions

Action creators make it easier to dispatch actions in a type-safe way. We’ll define these in the actions.ts file.

import { AuthAction, IUser } from ../types;

export const login = (user: IUser, token: string): AuthAction => {
return {
type: LOGIN,
payload: { user, token },
};
};

export const logout = (): AuthAction => {
return { type: LOGOUT };
};

06. Configure the custom-hook.

This custom will help use set and call the context in any components without involving multiple parameters into it. Create a file and name it useAuthContext.ts.

import { useContext } from react;
import { AuthContext } from ./context;

export const useAuthContext = () => {
const context = useContext(AuthContext);
if (!context) throw new Error(Error: useAuth must be within in AuthProvider);
return context;
};

We are set with all the initial configuration for the state management; now we will see how we can utilize this in our application.

Using the Context

To utilize our new AuthContext, we need to wrap our application (or part of it) in the AuthProvider. We’ll do this in our main entry point, typically App.tsx.

import React from react;
import { AuthProvider } from ./context/provider;
import { BrowserRouter as Router, Routes, Route } from react-router-dom;
import Home from ./Home;

const App: React.FC = () => {
return (
<AuthProvider>
<Router>
<Routes>
<Route path=/ element={<Home />} />
</Routes>
</Router>
</AuthProvider>
);
};

export default App;

Within any component, we can now use the useAuth hook to access the auth state and dispatch actions. Here’s an example component that uses our AuthContext:

import React from react;
import { useAuthContext } from ./context/useAuthContext;
import { IUser } from ./types;
import { login, logout } from ./context/actions;

const Home: React.FC = () => {
const { state, dispatch } = useAuthContext();

const handleLoginClick = () => {
const user: IUser = { firstName: John, lastName: Doe, role: SUPER-ADMIN };
const token = dfs56ds56f5.65sdf564dsf.645sdfsd4f56;
dispatch(login(user, token));
};

const handleLogoutClick = () => dispatch(logout());

return (
<div>
<h1>Home Page</h1>
{state.isAuthenticated ? (
<div>
<h3>Welcome {state.user?.firstName}</h3>
<button onClick={handleLogoutClick}>Logout</button>
</div>
) : (
<button onClick={handleLoginClick}>Login</button>
)}
</div>
);
};

export default Home;

Types & Interfaces

import { Dispatch, ReactNode } from react;

export interface IUser {
firstName: string;
lastName: string;
role: SUPER-ADMIN | ADMIN | USER;
}

export interface AuthState {
isAuthenticated: boolean;
user: null | IUser;
token: null | string;
}

export interface AuthContextProps {
state: AuthState;
dispatch: Dispatch<AuthAction>;
}

export interface AuthProviderProps {
children: ReactNode;
}

export type AuthAction =
| { type: LOGIN; payload: { user: IUser; token: string } }
| { type: LOGOUT };

Conclusion

By following this structured approach, you can manage global state in your React applications more effectively. The Context API, when used with TypeScript, provides a powerful and type-safe solution for state management. This setup is not only limited to authentication but can be adapted for other use cases like theme management, language settings, and more.

With this knowledge, you can now use Context-API like a pro! Feel free to modify and extend this setup to fit the needs of your own projects.