2

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>
    )
}
James Gaunt
  • 119
  • 1
  • 14

1 Answers1

4

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]}>
Phil
  • 157,677
  • 23
  • 242
  • 245
  • I have one question, where do I use const { darkTheme, setDarkTheme } = useContext(ThemeContext); Currently, I am using it in the render() method, but it gives me this error: React Hook "useContext" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks – James Gaunt Jul 26 '22 at 03:26
  • @JimmyGrant see the docs for using context in a class-based component ~ https://reactjs.org/docs/context.html#classcontexttype. I'd highly recommend moving those to function components though – Phil Jul 26 '22 at 03:34
  • () => {} causes eslint to complain "unexpected empty method" – Daniel Mar 29 '23 at 05:16
  • @Daniel do you mean [no-empty-function](https://eslint.org/docs/latest/rules/no-empty-function)? Did you add the comment like in my answer? – Phil Mar 29 '23 at 05:39
  • @Phil yes that is what I mean. Didn't add the comment though. I didn't realize it was an eslint thing. I just did () => {""} which was accepted (kind of ugly though). – Daniel Mar 29 '23 at 16:24
  • @Daniel you could always use something like `() => { console.warn("using context without provider"); }` – Phil Mar 29 '23 at 20:28