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:
ThemeContext
: We create aThemeContext
usingcreateContext
. This will store the current theme and a function to toggle it.ThemeProvider
: This component manages the state for the theme (light
ordark
). We also useuseEffect
to persist the theme in localStorage, so the user’s preference is remembered across sessions.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:
- We use the
useContext
hook to access thetheme
andtoggleTheme
function fromThemeContext
. - The button toggles the theme by calling the
toggleTheme
function when clicked. It also dynamically updates the text to indicate the current theme. - 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:
- In the
App
component, we useuseEffect
to listen for changes in thetheme
. When the theme changes, we add the corresponding class (light
ordark
) to thebody
element. - The styles defined in
App.css
will apply based on the class of thebody
.
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!