Ultimate Guide to setup React Context API with a custom hook [Typescript]
damilola jerugba
Posted on September 30, 2021
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
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
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;
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[];
}
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
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,
},
],
};
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;
}
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: () => {},
});
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;
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;
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.
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";
...
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>
);
};
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();
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;
}
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 };
};
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;
With the power of typescript, we have IntelliSense to help us out.
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 ππ
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 π!
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
September 30, 2021