Creating a Dark Mode Toggle with useContext

Published on 21.7.2019

As web applications grow more dynamic, user preferences like dark mode are becoming increasingly popular. Dark mode not only provides an aesthetically pleasing experience but can also reduce eye strain in low-light environments. React’s hooks provide an elegant way to manage such features without the need for external state management libraries like Redux.

In this post, we’ll build a dark mode toggle using React’s useContext and useState hooks. We’ll also store the user’s theme preference in local storage, so it persists across page reloads.


What is useContext and Why Use It?

The useContext hook is part of React’s API for sharing state between components without having to pass props down through each level of your component tree. It allows us to manage global state in a simple way, making it ideal for managing global settings like theme preferences across an application.

For this example, we’ll use useContext to provide a global dark mode state to our components and allow the user to toggle between dark and light modes.


Setting Up the Project

Before we begin coding, let’s install a fresh React app (if you don’t have one already). If you’re starting from scratch:

npx create-react-app dark-mode-toggle
cd dark-mode-toggle
npm start

Once the app is set up, let’s proceed with building the dark mode feature.


Creating a Theme Context

The first step is to create a context to store our theme (dark or light mode). We’ll create a ThemeContext.js file to hold our context and provide a way to toggle between modes.

// src/ThemeContext.js
import React, { createContext, useState, useEffect } from 'react';

// Create a context for the theme
const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
  // Retrieve the theme from localStorage or default to 'light'
  const [theme, setTheme] = useState(() => {
    const savedTheme = localStorage.getItem('theme');
    return savedTheme ? savedTheme : 'light';
  });

  // Update localStorage whenever the theme changes
  useEffect(() => {
    localStorage.setItem('theme', theme);
  }, [theme]);

  const toggleTheme = () => {
    setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
  };

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

export default ThemeContext;

Explanation:

  1. ThemeContext: We create a ThemeContext using createContext. This will store the current theme and a function to toggle it.
  2. ThemeProvider: This component manages the state for the theme (light or dark). We also use useEffect to persist the theme in localStorage, so the user’s preference is remembered across sessions.
  3. toggleTheme: This function toggles between light and dark themes.

Using the Theme Context in Components

Now that we have our theme context set up, let’s use it in our components. We’ll create a simple button that allows users to toggle between dark and light modes.

App Component

// src/App.js
import React, { useContext } from 'react';
import { ThemeProvider } from './ThemeContext';
import ThemeToggle from './ThemeToggle';

function App() {
  return (
    <ThemeProvider>
      <div className="App">
        <ThemeToggle />
      </div>
    </ThemeProvider>
  );
}

export default App;

Here, we wrap the App component in the ThemeProvider to ensure that all components inside it have access to the theme context.

ThemeToggle Component

// src/ThemeToggle.js
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';

function ThemeToggle() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <div>
      <button onClick={toggleTheme}>
        Switch to {theme === 'light' ? 'Dark' : 'Light'} Mode
      </button>
      <p>The current theme is {theme} mode</p>
    </div>
  );
}

export default ThemeToggle;

Explanation:

  1. We use the useContext hook to access the theme and toggleTheme function from ThemeContext.
  2. The button toggles the theme by calling the toggleTheme function when clicked. It also dynamically updates the text to indicate the current theme.
  3. We display the current theme for the user in a paragraph tag.

Styling the Application for Dark Mode

Now that we have the theme state and toggle button working, let’s style the application. We’ll use basic CSS to apply the correct styles for both dark and light modes.

App.css

/* src/App.css */

body {
  transition: background-color 0.3s, color 0.3s;
  font-family: Arial, sans-serif;
  margin: 0;
  padding: 0;
}

.light {
  background-color: white;
  color: black;
}

.dark {
  background-color: #121212;
  color: white;
}

button {
  padding: 10px 20px;
  font-size: 16px;
  cursor: pointer;
  background-color: #4caf50;
  color: white;
  border: none;
  border-radius: 5px;
}

button:hover {
  background-color: #45a049;
}

Modifying the App Component to Apply Styles

Now, let’s update our App component to apply the correct class to the body tag based on the current theme.

// src/App.js
import React, { useContext, useEffect } from 'react';
import { ThemeProvider } from './ThemeContext';
import ThemeToggle from './ThemeToggle';
import './App.css';

function App() {
  const { theme } = useContext(ThemeContext);

  useEffect(() => {
    document.body.classList.remove('light', 'dark');
    document.body.classList.add(theme);
  }, [theme]);

  return (
    <div className="App">
      <ThemeToggle />
    </div>
  );
}

export default App;

Explanation:

  1. In the App component, we use useEffect to listen for changes in the theme. When the theme changes, we add the corresponding class (light or dark) to the body element.
  2. The styles defined in App.css will apply based on the class of the body.

Conclusion

In this post, we built a dark mode toggle using React’s useContext hook. We created a global context to manage the theme and allow components to access and toggle the theme. By storing the user’s preference in localStorage, we ensured that the theme persists across page reloads.

This approach is simple and efficient, without the need for complex state management solutions like Redux. It also allows for easy customization and extension if you decide to add more global settings in the future.

Now, you can easily implement dark mode in any React app and provide a more personalized experience for your users.

Happy coding!

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *