Building a Reusable Dark Mode Toggle in React: A Line-by-Line Code Walkthrough
đ Quick Review: This practical application is built upon a fundamental programming concept. Review the Theory Lesson here first.
Demystifying useDarkMode: A Practical Code Breakdown
Understanding the theory behind custom hooks is one thing; seeing them in action and dissecting their implementation is another. In this practical lesson, weâll dive deep into the provided useDarkMode custom hook, breaking down each line of code to understand its purpose, how it leverages Reactâs core features, and its interaction with the browserâs execution environment. By the end, youâll have a solid grasp of how to build and integrate such a powerful utility into your own React applications.
Prerequisites
Before we begin, a basic understanding of React, functional components, and the fundamental concepts of useState and useEffect hooks will be beneficial.
The useDarkMode Custom Hook: Full Code
Hereâs the complete code snippet weâll be analyzing:
import { useState, useEffect } from 'react';function useDarkMode() { const [theme, setTheme] = useState('light'); useEffect(() => { // Check for saved preference or system settings const savedTheme = window.localStorage.getItem('theme'); if (savedTheme) { setTheme(savedTheme); } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { setTheme('dark'); } }, []); useEffect(() => { // Update document object and save preference const root = window.document.documentElement; root.classList.remove('light', 'dark'); root.classList.add(theme); window.localStorage.setItem('theme', theme); }, [theme]); const toggleTheme = () => { setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light')); }; return [theme, toggleTheme];}export default useDarkMode;
Line-by-Line Code Breakdown
1. Importing Hooks
import { useState, useEffect } from 'react';
This line imports the necessary React Hooks: useState for managing component state and useEffect for performing side effects.
2. Defining the Custom Hook
function useDarkMode() {
This defines our custom hook, named useDarkMode. By convention, all custom hooks start with the prefix âuseâ to signal that they follow the Rules of Hooks.
3. Initializing Theme State
const [theme, setTheme] = useState('light');
Here, we use useState to declare a state variable named theme and its corresponding setter function setTheme. The initial value of theme is set to 'light'. This theme variable will hold the current active theme (âlightâ or âdarkâ).
4. First useEffect: Initial Theme Detection
useEffect(() => { // Check for saved preference or system settings const savedTheme = window.localStorage.getItem('theme'); if (savedTheme) { setTheme(savedTheme); } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { setTheme('dark'); } }, []);
This useEffect hook is crucial for initializing the theme when the component (or the hook consumer) first mounts. Letâs break down its logic:
const savedTheme = window.localStorage.getItem('theme');: It first attempts to retrieve a previously saved theme preference from the browserâslocalStorage. This ensures user preferences are remembered across sessions.if (savedTheme) { setTheme(savedTheme); }: If a saved theme is found, it updates thethemestate with that value.else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { setTheme('dark'); }: If no saved theme is found, it then checks the userâs system preference usingwindow.matchMedia('(prefers-color-scheme: dark)'). If the system prefers dark mode, the theme is set to'dark'.}, []);: The empty dependency array[]ensures thisuseEffectruns only once after the initial render, mimickingcomponentDidMountbehavior in class components.
5. Second useEffect: Applying and Persisting Theme Changes
useEffect(() => { // Update document object and save preference const root = window.document.documentElement; root.classList.remove('light', 'dark'); root.classList.add(theme); window.localStorage.setItem('theme', theme); }, [theme]);
This useEffect hook handles the actual application of the theme to the DOM and its persistence. It runs whenever the theme state changes:
const root = window.document.documentElement;: It gets a reference to the HTML root element (<html>). This is where weâll apply our theme classes.root.classList.remove('light', 'dark');: It removes any existing âlightâ or âdarkâ classes from the root element to ensure a clean slate before applying the new theme.root.classList.add(theme);: It adds the currenttheme(either âlightâ or âdarkâ) as a class to the HTML root element. Your CSS can then target these classes (e.g.,html.dark { /* dark mode styles */ }).window.localStorage.setItem('theme', theme);: It saves the currentthemetolocalStorage, so the preference is remembered for future visits.}, [theme]);: The dependency array[theme]means thisuseEffectwill re-run whenever thethemestate variable changes.
6. Defining the Theme Toggle Function
const toggleTheme = () => { setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light')); };
This function, toggleTheme, provides the mechanism for users to switch between themes. It uses the setTheme updater function with a callback to get the prevTheme and then sets the theme to its opposite.
7. Returning Hook Values
return [theme, toggleTheme];
Finally, the useDarkMode hook returns an array containing the current theme state and the toggleTheme function. This allows any component that uses useDarkMode to access these values and functions.
8. Exporting the Hook
export default useDarkMode;
This line makes the useDarkMode custom hook available for import and use in other React components throughout your application.
Execution Environment and Browser APIs
The useDarkMode hook heavily relies on several Browser APIs to function correctly within a web environment:
window.localStorage: Used for client-side data storage, allowing the application to remember the userâs theme preference across browser sessions.window.matchMedia(): A powerful API for querying the document about the characteristics of the display device. Here, itâs used to detect the userâs operating systemâs preferred color scheme.window.document.documentElement: This property returns the<html>element of the document. By adding and removing classes on this element, the entire applicationâs styling can be dynamically changed via CSS.
These APIs are fundamental to how the hook interacts with the browser and persists user settings, making it a robust solution for dynamic theming.
useEffect is powerful, be cautious with its dependency array. An incorrect dependency array (e.g., missing a dependency or including too many) can lead to infinite loops or stale closures. Always ensure your dependencies accurately reflect all values from the component scope that your effect uses.Conclusion
The useDarkMode custom hook is a prime example of how React Hooks enable developers to write cleaner, more modular, and reusable code. By encapsulating the complex logic of theme detection, application, and persistence, it provides a simple and elegant API for any component to consume. This line-by-line breakdown should equip you with the knowledge to not only use this hook effectively but also to inspire you to create your own custom hooks for common patterns in your React projects.