Ultimate Guide to setup React Context API with a custom hook [Typescript]

damiisdandy

damilola jerugba

Posted on September 30, 2021

Ultimate Guide to setup React Context API with a custom hook [Typescript]

This is a guide to help you set up React Context API with typescript.

🀨 What is React Context API?

Context is designed to share data that can be considered β€œglobal” for a tree of React components, This prevents Prop drilling and allows you to pass data around your react component tree efficiently.

There are external libraries like Redux that help with this, but luckily react implemented a built-in feature called React Context API that does this perfectly.

Let's dive in! 😁

Setup πŸ› 

To set up the project we need to first create a create-react-app application with the typescript template, To do this open up a terminal window and run the command

npx create-react-app context-typescript --template typescript

# or

yarn create react-app context-typescript --template typescript
Enter fullscreen mode Exit fullscreen mode

Open the context-typescript directory in your favorite text editor like VS code and delete the following files within the src directory.

  • App.css
  • App.test.tsx

or simply run the commands

cd context-typescript/src
rm App.css App.test.tsx
Enter fullscreen mode Exit fullscreen mode

Then open up the App.tsx file, clear everything within it and copy the following lines of code inside it.

// src/App.tsx

import logo from './logo.svg';

function App() {
  return (
    <div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Declaring the Interfaces and types we'll use 🧩

Within the react-app-env.d.ts file we'll declare the Interface for our global state, We will be building a To-do application in this example to illustrate the use of the context API.

// react-app-env.d.ts

interface Todo {
    id: number;
    title: string;
    isCompleted: Boolean;
    createdAt: Date;
}
interface State {
    isDark: boolean;
    todos: Todo[];
}
Enter fullscreen mode Exit fullscreen mode

Creating Our Context 🌴

Create a folder in the src directory called context within it create two files called index.tsx and reducer.ts.

or run the commands

mkdir src/context

cd src/context

touch index.tsx reducer.ts
Enter fullscreen mode Exit fullscreen mode

Within the index.tsx we'll create our context, global context provider, and our custom hook. In the reducer.ts we'll create our reducer function.

Open up the index.tsx type the following

// src/context/index.tsx

import {
  createContext,
  Dispatch,
  ReactNode,
  useContext,
  useReducer,
} from "react";

// Initial State
const initialState: State = {
  isDark: false,
  todos: [
    {
      id: 0,
      title: "Prepare dev.to article ✍",
      createdAt: new Date("2021-09-28T12:00:00-06:30"),
      isCompleted: false,
    },
    {
      id: 2,
      title: "Watch season 3 episode 2 of Attack on titans πŸ‘€",
      createdAt: new Date("2021-09-30T11:00:00-06:30"),
      isCompleted: false,
    },
  ],
};

Enter fullscreen mode Exit fullscreen mode

We simply just imported all that we'll be using in the file and initiated our initial state. Notice how we used the State interface.

Before we create our Context let's first declare the Interface and type we'll be using for our context.

Within the react-app-env.d.ts file add the following lines of code.

// react-app-env.d.ts

...
type ActionTypes = 'TOGGLE_MODE' | 'ADD_TODO' | 'REMOVE_TODO' | 'MARK_AS_DONE';

interface Action {
    type: ActionTypes;
    payload?: any;
}
Enter fullscreen mode Exit fullscreen mode

We've just declared the Action interface and its respective types (ActionTypes)

Now we can create our context, Add the following lines of code underneath the initial state we just declared in the index.tsx

// src/context/index.tsx

...
// Create Our context
const globalContext = createContext<{
  state: State;
  dispatch: Dispatch<Action>;
}>({
  state: initialState,
  dispatch: () => {},
});
Enter fullscreen mode Exit fullscreen mode

We've already imported the createContext function and Dispatch interface, we also implemented our Action interface, and set the initial state to our initialState

Creating the Reducer πŸ“¦

Before we create the reducer function lets the Type for our reducer function within the react-app-env.d.ts file

// react-app-env.d.ts
...

type ReducerType = (state: State, action: Action) => State;
Enter fullscreen mode Exit fullscreen mode

This is simply a function that takes in the State and Action and returns the State.

Within the reducer.ts file, copy the function below.

// src/context/reducer.ts

const reducer: ReducerType = (state, action) => {
  switch (action.type) {
    case "TOGGLE_MODE":
      return { ...state, isDark: !state.isDark }
    case "ADD_TODO":
      const mostRecentTodos = state.todos.sort((a, b) => b.id - a.id);
      return {
        ...state, todos: [
          ...state.todos,
          {
            // generate it's id based on the most recent todo
            id: mostRecentTodos.length > 0 ? mostRecentTodos[0].id + 1 : 0,
            title: action.payload,
            isCompleted: false,
            createdAt: new Date(),
          }
        ]
      };
    case "REMOVE_TODO":
      return { ...state, todos: state.todos.filter(el => el.id !== action.payload) }
    case "MARK_AS_DONE":
      const selectedTodo = state.todos.find(el => el.id === action.payload);
      if (selectedTodo) {
        return {
          ...state, todos: [...state.todos.filter(el => el.id !== action.payload), {
            ...selectedTodo,
            isCompleted: true,
          }]
        }
      } else {
        return state
      }
    default:
      return state;
  }
}

export default reducer;
Enter fullscreen mode Exit fullscreen mode

Based on the ActionTypes type we previously initialized, we are using for the switch statement's action.type

Because we are using Typescript our text editor or IDE helps us with IntelliSense for the action types.

typescript intelliSense

Creating the Global Provider 🌐

Within the index.tsx file we'll import the reducer function we just created.

// src/context/index.tsx
...
import reducer from "./reducer";
...
Enter fullscreen mode Exit fullscreen mode

Then we'll create the global provider that we'll wrap around our root component

// src/context/index.tsx

...
// Provider to wrap around our root react component
export const GlobalContextProvider = ({
  children,
}: {
  children: ReactNode;
}) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <globalContext.Provider
      value={{
        state,
        dispatch,
      }}
    >
      {children}
    </globalContext.Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

We've previously imported ReactNode and useReducer.
The Provider property is gotten from our previously created globalContext, We also added in the parameters reducer and initialState inside the useReducer hook, (psst! picture useReduer as useState on steroids πŸ’ͺ). The children prop is simply the direct child component of GlobalContextProvider (our entire app).

Now we simply just wrap the GlobalContextProvider around our root component within the src/index.tsx file

Your code should look like this

// src/index.tsx

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { GlobalContextProvider } from "./context";

ReactDOM.render(
  <React.StrictMode>
    <GlobalContextProvider>
      <App />
    </GlobalContextProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

Enter fullscreen mode Exit fullscreen mode

Custom Hook πŸ“Ž

We are going to create a hook that lets us access our global state and dispatch function anywhere in our component tree (react app).

Before we do that let's create its Type, this is useful because it lets us use the power of Typescript.

We'll declare this within the react-app-env.d.ts file like we always have.

// react-app-env.d.ts

...
type ContextHook = () => {
    state: State,
    dispatch: (action: Action) => void;
}
Enter fullscreen mode Exit fullscreen mode

This is a function that simply returns an object that contains our global state and dispatch function.

Now we create the hook within the src/context/index.tsx file

// src/context/index.tsx

...
// Custom context hook
export const useGlobalContext: ContextHook = () => {
  const { state, dispatch } = useContext(globalContext);
  return { state, dispatch };
};
Enter fullscreen mode Exit fullscreen mode

We previously imported the useContext hook, which takes in our globalContext.

Using our custom hook

Within the App.tsx file we'll import the useGlobalContext hook we just created.

// src/App.tsx

import logo from './logo.svg';
import { useGlobalContext } from "./context";

function App() {
  const { state, dispatch } = useGlobalContext();
  return (
    <div>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

With the power of typescript, we have IntelliSense to help us out.

typescript intelliSense

typescript intelliSense

That's all for this tutorial πŸŽ‰, This is my first article πŸ˜…, feedback will be nice, Be sure to comment down below if you have any questions, additions, or subtractions.

The full source code to with project with a functioning todo application is linked below πŸ‘‡πŸ‘‡

GitHub logo damiisdandy / context-api-typescript

An example project on the article I wrote about setting up react's context api with typescript

Thank you for reading πŸ™!

πŸ’– πŸ’ͺ πŸ™… 🚩
damiisdandy
damilola jerugba

Posted on September 30, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related