How to use useContext with Typescript

Introduction

When it comes to building robust and scalable application as it grows in size and complexity, managing state becomes more and more challenging and that's when useContext hook might come in handy.

So what is useContext?

It's one of many built-in React Hooks. It uses React's Context API which allows components to consume context values without the need to pass props down the component tree (prop drilling).

Component Tree Image source: https://openclassrooms.com/en/courses/7315991-intermediate-react/7572062-share-your-data-with-context-and-usecontext

That's exactly what we are trying to avoid while implementing useContext hook. When our component tree grows we don't want to rely on passing data only via props.

A basic example of using useContext

import { createContext, useContext } from "react";
 
const weather = {
  sunny: "☀️",
  rainy: "🌧️",
};
 
const WeatherContext = createContext(weather);
export const App = () => {
  return (
    <WeatherContext.Provider>
      <Weather />
    </WeatherContext.Provider>
  );
};
 
export const Weather = () => {
  const emoji = useContext(weather);
  return <div>{emoji}</div>;
};

All components which are wrapped around our provider will have access to our weather object. Context can also store functions and state which is handy.

Examples of when to use useContext

  • Theme: You can store your theme in context and access it from any component.
  • Authentication: You can store user data in context and access it from any component.
  • Language: You can store language in context and access it from any component.

A rule of thumb is to use context when we need to access our data globally across all of our components and the data is not changing frequently, then state management libraries might be better.

Template for useContext with Typescript

I remember when I taught myself useContext and started using it, it made my App component huge as with application complexity had been growing, I had been adding more and more states, functions and providers. Later I developed a way of creating a separate file for each context and exporting it as a component.

import { createContext, ReactNode, useMemo, useState } from "react";
 
export type Theme = {
  switchTheme: () => void;
  theme: string;
};
 
type Theme = {
  children: ReactNode;
}
 
export const ThemeContext = createContext<Theme | null>(null);

First, we import stuff and write types for our context and props to our component with a context provider.

export const ThemeContextProvider = ({ children }: IProps) => {
  const [theme, setTheme] = useState<string>("light");
 
  const switchTheme = () => {
    setTheme(prev => prev === "light" ? "dark" : "light");
  }
 
  const contextValues = useMemo(
    () => ({ theme, switchTheme }),
    [theme]
  );
 
  return (
    <ThemeContext.Provider value={contextValues}>
      {children}
    </ThemeContext.Provider>
  );
};

That's what our provider component looks like. It is useful when our context grows in size and complexity and we have multiple states with numerous functions (however at some point you might consider switching to state management library eg. redux). For example, if we create our modals from scratch, we can also use here useLocation hook to manipulate our state.

Accessing values from our context:

import { useContext } from "react";
import { ThemeContext } from "../context/ThemeContext";
 
export const App = () => {
  const { switchTheme } = useContext(ThemeContext) as Theme;
 
  return (
    <button onClick={switchTheme}>Switch Theme</button>
  );
};

It's not a bad idea to have AppProviders component wrapping all of our providers from context, redux store etc:

import { ThemeContextProvider } from './ThemeContext'
import { App } from './App'
 
const AppProviders = () => {
  return (
    <ThemeContextProvider>
      <App />
    </ThemeContextProvider>
  )
}
 
export default AppProviders

When to not use context (and transition to state management libraries)

Context API therefore useContext hook is useful when our data and application are not complex and should be used only when we know that data is not changing frequently so it won't affect performance. State management libraries such as redux are designed for large and complex apps providing state management across your entire application, although developers should find a way to balance between those two as redux might not be the best to manage state of some modal or color theme as React's Context API is great for it and does its job.

In summary, useContext is a powerful tool in the React developer's toolkit, but it's important to know your specific needs and choose the right state management solution for your application as it may not provide the scalability and robustness needed for large and complex applications. With the right approach, you can build high-quality, maintainable React applications that provide a great user experience.

Newsletter

If you enjoyed the read, you might consider checking out our FREE daily newsletter. We send short, 3 minute daily emails with 1 tip, 1 challenge and optionally news/tools from web development. Link ->

On this page