Always Use a Custom Hook for Context API, Not useContext (React Context API, TypeScript)

Step 1: Create the Theme Context

First, we need to create the context for our theme.

import React, { createContext, useState } from 'react';

type Theme = 'dark' | 'light';
type ThemeContextProps = {
  theme: Theme;
  setTheme: React.Dispatch<React.SetStateAction<Theme>>;
};

const ThemeContext = createContext<ThemeContextProps | null>(null);

Here, we're defining the types for our theme and setting the initial value as null.

Step 2: Implementing the Theme Context Provider

Now, we will implement the provider for the theme context.

type ThemeContextProviderProps = { children: React.ReactNode; };

function ThemeProvider(props: ThemeContextProviderProps) {
  const [theme, setTheme] = useState<Theme>('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {props.children}
    </ThemeContext.Provider>
  );
};

The ThemeProvider wraps the root component of your application and passes down the theme and a function to set the theme.

Step 3: Creating a Custom Hook

Using useContext directly can be cumbersome as you'll have to import the context and check for null every time. Instead, create a custom hook to handle this.

export const useThemeContext = () => {
  const context = useContext(ThemeContext);

  if (!context) {
    throw new Error('useThemeContext must be used within a ThemeContextProvider');
  }

  return context;
};

Now, whenever you want to access the theme or set it in any component, you can simply call this custom hook.
Step 4: Consuming the Context

Here's an example of how to use the custom hook within a component.

const Logo = () => {
  const { theme } = useThemeContext();

  return theme === 'dark' ? <DarkModeLogo /> : <LightModeLogo />;
};