Search code examples
javascriptreactjstypescriptuse-stateuse-context

How to use "useContext" in typescript?


I am trying to make a dark/light theme system in my project, but I am having some problems with the code.

This line of code works fine in javascript:

const [darktheme, setDarkTheme] = useContext(ThemeContext);

But when I write it into typescript, I get 6 errors.

I know that some of these variables need to have their type declared, but I only know the type of the darkTheme variable, which is a boolean.

After I declare the types, 2 errors go away, but there is still 4 errors!

const [darktheme: boolean, setDarkTheme: any] = useContext(ThemeContext);

I used any after dark theme, which is not good practice but I didn't know the type

Now I just get these errors: enter image description here

enter image description here

I think that the main problem with my project is that I am trying to integrate javascript with typescript. I don't know if that is normal or not, but I am doing it because some components are much easier to write with typescript, and some more basic components are better written in javascript.

Here is part of my app.js:

// Context

export const ThemeContext = React.createContext();

function App() {
  const [darkTheme, setDarkTheme] = useState(false);

  return (
    <ThemeContext.Provider value={[darkTheme, setDarkTheme]}>

,and when I use the function in this component, it works just fine:

import React, { useContext } from 'react';
import { ThemeContext } from '../App';

import Button from 'react-bootstrap/Button';

export default function DarkThemeTest() {
    const [darktheme, setDarkTheme] = useContext(ThemeContext);

    return (
        <Button onClick={() => {
            setDarkTheme(!darktheme);
        }}>
            Theme: {darktheme && "Dark" || "Light"}
        </Button>
    )
}

Solution

  • First, define a type for your context value

    import { createContext, Dispatch, SetStateAction } from "react";
    
    interface ThemeContextType {
      darkTheme: boolean;
    
      // this is the type for state setters
      setDarkTheme: Dispatch<SetStateAction<boolean>>; 
    }
    

    Then, create your context with this type and initialise it with a default value. This might seem unnecessary but it will avoid checking for null or undefined context later on

    export const ThemeContext = createContext<ThemeContextType>({
      darkTheme: false,
      setDarkTheme: () => {}, // no-op default setter
    });
    

    Once you have created your state value and setter, set them into the context provider value

    <ThemeContext.Provider value={{ darkTheme, setDarkTheme }}>
    

    Now you can destructure the context value easily via useContext with full type support

    const { darkTheme, setDarkTheme } = useContext(ThemeContext);
    

    You could continue to use your array format though I wouldn't recommend it.

    type ThemeContextType = [boolean, Dispatch<SetStateAction<boolean>>];
    
    export const ThemeContext = createContext<ThemeContextType>([false, () => {}]);
    

    and

    <ThemeContext.Provider value={[darkTheme, setDarkTheme]}>