This is a guide to help you set up React Context API with Typescript.
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!
To set up the project we need to first create a
application with the typescript template, To do this open up a terminal window and run the commandcreate-react-app
npx create-react-app context-typescript --template typescript
# or
yarn create react-app context-typescript --template typescript
Open the
directory in your favorite text editor like VS code and delete the following files within the context-typescript
directory.src
App.css
App.test.tsx
or simply run the commands
cd context-typescript/src
rm App.css App.test.tsx
Then open up the
file, clear everything within it and copy the following lines of code inside it.App.tsx
// src/App.tsx
import logo from "./logo.svg";
function App() {
return <div></div>;
}
export default App;
Within the
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
// react-app-env.d.ts
interface Todo {
id: number;
title: string;
isCompleted: Boolean;
createdAt: Date;
}
interface State {
isDark: boolean;
todos: Todo[];
}
Create a folder in the
directory called src
within it create two files called context
and index.tsx
.reducer.ts
or run the commands
mkdir src/context
cd src/context
touch index.tsx reducer.ts
Within the
we'll create our context, global context provider, and our custom hook. In the index.tsx
we'll create our reducer function.
Open up the reducer.ts
type the followingindex.tsx
// 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
interface.
Before we create our Context let's first declare the State
and Interface
we'll be using for our context.
Within the type
file add the following lines of code.react-app-env.d.ts
// 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
interface and its respective types (Action
)
Now we can create our context, Add the following lines of code underneath the initial state we just declared in the ActionTypes
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
function and createContext
interface, we also implemented our Dispatch
interface, and set the initial state to our Action
initialState
Before we create the reducer function lets the
for our reducer function within the Type
filereact-app-env.d.ts
// react-app-env.d.ts
...
type ReducerType = (state: State, action: Action) => State;
This is simply a function that takes in the
and State
and returns the Action
.
Within the State
file, copy the function below.reducer.ts
// 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
type we previously initialized, we are using for the ActionTypes
statement's switch
.
Because we are using Typescript our text editor or IDE helps us with IntelliSense for the action types.
action.type
Within the
file we'll import the reducer function we just created.index.tsx
// 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
and ReactNode
.
The useReducer
property is gotten from our previously created Provider
, We also added in the parameters globalContext
and reducer
inside the initialState
hook, (psst! picture useReducer
as useReduer
on steroids πͺ). The useState
prop is simply the direct child component of children
(our entire app).
Now we simply just wrap the GlobalContextProvider
around our root component within the GlobalContextProvider
filesrc/index.tsx
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();
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
, this is useful because it lets us use the power of Typescript.
We'll declare this within the Type
file like we always have.react-app-env.d.ts
// 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
filesrc/context/index.tsx
// src/context/index.tsx
...
export const useGlobalContext: ContextHook = () => {
const { state, dispatch } = useContext(globalContext);
return { state, dispatch };
};
We previously imported the
hook, which takes in our useContext
.globalContext
Within the
file we'll import the App.tsx
hook we just created.useGlobalContext
// 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 π .