React Context-API Pro | Build state management using useContext + useReducer | Typescript
Tags: javascript, react, reactnative, nextjs
Published: 2024-06-16 05:45:30

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gpl26xoeovblllabrype.png)

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

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

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

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

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

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

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

```javascript
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:

```javascript

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

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