DEV Community

Cover image for Toggling Light/Dark Theme in React with useContext
Nick Scialli (he/him)
Nick Scialli (he/him)

Posted on

Toggling Light/Dark Theme in React with useContext

This post shows one method to use React's useContext and useState hooks to implement dark/light mode toggling. The relevant files are src/ThemeProvider.tsx, src/index.tsx, and src/App.tsx.

This project uses Typescript, but the same functionality can be achieved in javascript by removing the types.

Demo and Full Code

You can see a working version of this simple app on Netlify here:

Demo site

The full code can be found on github here:

Full code

Diving into the Code

ThemeProvider.tsx

In our ThemeProvider component, we define our Theme as being either light or dark and we define our ThemeContext as being an object with two properties: theme and toggleTheme (the theme and ability to toggle the theme will be made available to other components via the useContext hook).

We have to make sure to export the ThemeContext object we create using React.createContext.

Within the ThemeProvider component, we maintain our theme state using the useState hook and create a toggleTheme function that will toggle the state between light and dark.

For simplicity, we simple set the document body's color and backgroundColor styles based on whether the theme state is currently light or dark. Finally, we export our ThemeContext Provider with value set to and object with theme and toggleTheme properties. We then render children within our ThemeContext.Provider component.

import React, { useState } from "react";
typescript;
type Theme = "light" | "dark";
type ThemeContext = { theme: Theme; toggleTheme: () => void };

export const ThemeContext = React.createContext<ThemeContext>(
  {} as ThemeContext
);

export const ThemeProvider: React.FC = ({ children }) => {
  const [theme, setTheme] = useState<Theme>("light");
  const toggleTheme = () => {
    setTheme(theme === "light" ? "dark" : "light");
  };

  const color = theme === "light" ? "#333" : "#FFF";
  const backgroundColor = theme === "light" ? "#FFF" : "#333";

  document.body.style.color = color;
  document.body.style.backgroundColor = backgroundColor;

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

index.tsx

In our index file, we simply wrap the entire app in our new ThemeProvider component. Of course we don't need to do this at the app level in real projects, we just need to make sure that any components that need theme or toggleTheme are within the child tree of our provider.

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { ThemeProvider } from './ThemeProvider';

ReactDOM.render(
  <ThemeProvider>
    <App />
  </ThemeProvider>,
  document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

App.tsx

In the App component, we use the useContext hook to gain access to our theme string and toggleTheme function. We create a simple button that can toggle the theme and only use theme to determine what we show the user: "Switch to dark mode" or "Switch to light mode"

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

const App: React.FC = () => {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <div>
      <div>Hi friend!</div>
      <button onClick={toggleTheme}>
        Switch to {theme === 'light' ? 'dark' : 'light'} mode
      </button>
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

And that's it!

Top comments (3)

Collapse
 
freenrg profile image
freenrg • Edited

Nice! It helped.
I would warn readers that modifying document like this is probably not the ideal approach in React:
document.body.style.color = color;
document.body.style.backgroundColor = backgroundColor;

Collapse
 
mahbod profile image
Mahbod Ahmadi

useContext returns undefined..

Collapse
 
pavanmehta profile image
Pavan Maheshwari

What if we create a simple hook which just toggles the mode for us. Ideally inside of an app we'd only be having the toggle button at one place. We might not need context provider for this.