Implementing Dark Mode in React with the Provider Pattern
Written on
Chapter 1: Understanding the Provider Pattern
In React development, managing state across multiple components can lead to complex code, especially when prop drilling is involved. To mitigate this, the Provider pattern is commonly utilized alongside React Context. This approach allows you to share data seamlessly across various components without needing to pass props down through every level of your component hierarchy.
Before we get started, ensure that you have Node.js version 18 or higher installed on your system. You can initiate your project with the following command:
npm create vite@latest
Now, let's dive into how to effectively implement dark mode using the Provider pattern in React. First, we’ll look at the advantages of this method.
- Eliminates the necessity of passing props through multiple component layers.
- Simplifies code comprehension and maintenance.
- Useful for managing global application state.
However, it’s important to be cautious. Overusing the Provider pattern can lead to performance issues. If the data within the context changes, all components consuming that context will re-render, even if they don’t utilize the updated information. This can be particularly inefficient for larger applications.
Section 1.1: Setting Up the Dark Mode Provider
The Provider component acts as the context provider for dark mode. It utilizes the useState hook to manage the dark mode state and provides a function to toggle that state.
We can elegantly separate the Provider from other logic. It will maintain the current dark mode state, passing both the isDarkMode value and the toggle function to child components.
import { createContext, PropsWithChildren, useCallback, useState } from "react";
export type DarkModeContextProps = {
isDarkMode: boolean;
toggle(): void;
};
export const DarkModeContext = createContext({
isDarkMode: false,
toggle() {},
});
export function DarkModeProvider({ children }: PropsWithChildren) {
const [isDarkMode, setDarkMode] = useState(false);
const toggle = useCallback(() => setDarkMode((v) => !v), []);
const contextValue = { isDarkMode, toggle };
return (
<DarkModeContext.Provider value={contextValue}>
{children}</DarkModeContext.Provider>
);
}
By wrapping your application with the DarkModeProvider, you make this context available to all descendant components without intertwining your code with context logic.
import "./App.css";
import { Dashboard } from "./components/Dashboard.tsx";
import { DarkModeProvider } from "./providers/DarkModeProvider.tsx";
function App() {
return (
<DarkModeProvider>
<Dashboard /></DarkModeProvider>
);
}
export default App;
Section 1.2: Utilizing a Custom Hook for Dark Mode
To streamline access to the dark mode context, we can create a custom hook that uses useContext, enabling components to easily retrieve the current isDarkMode value and the toggle function without the need for explicit prop drilling.
import { useContext } from "react";
import { DarkModeContext } from "../providers/DarkModeProvider.tsx";
export function useDarkMode() {
return useContext(DarkModeContext);
}
Subsection 1.2.1: Creating the Button Component
The button component leverages the useDarkMode hook to access the isDarkMode value, which dynamically adjusts the button’s background color based on the current mode.
import { CSSProperties, PropsWithChildren } from "react";
import { useDarkMode } from "../hooks/useDarkMode.tsx";
type Props = {
onClick?(): void;
};
export function Button({ children, onClick }: PropsWithChildren) {
const { isDarkMode } = useDarkMode();
const style: CSSProperties = {
backgroundColor: isDarkMode ? "#133337" : "#fc6c85",
border: "none",
color: "inherit",
};
return (
<button style={style} onClick={onClick}>
{children}</button>
);
}
Subsection 1.2.2: Toggle Button Functionality
By clicking the button, users can activate the toggle function, which will switch the dark mode state within the provider.
import { Button } from "./Button.tsx";
import { useDarkMode } from "../hooks/useDarkMode.tsx";
export function ToggleButton() {
const { toggle } = useDarkMode();
return (
<Button onClick={toggle}>Toggle mode</Button>);
}
Section 1.3: Building the Header Component
This component features a simple menu that incorporates the ToggleButton, allowing users to easily switch between dark and light modes.
import { CSSProperties } from "react";
import { ToggleButton } from "./ToggleButton.tsx";
export function Header() {
const style: CSSProperties = {
display: "flex",
gap: "1rem",
};
return (
<div style={style}>
<span>Products</span>
<span>Categories</span>
<span>Settings</span>
<ToggleButton />
</div>
);
}
Chapter 2: Finalizing the Dashboard Component
The Dashboard component retrieves the isDarkMode value from the context, allowing it to dynamically adjust its styling.
import { CSSProperties } from "react";
import { Header } from "./Header.tsx";
import { useDarkMode } from "../hooks/useDarkMode.tsx";
export function Dashboard() {
const { isDarkMode } = useDarkMode();
const style: CSSProperties = {
color: isDarkMode ? "white" : "black",
backgroundColor: isDarkMode ? "black" : "white",
padding: "8px",
height: "100%",
};
return (
<div style={style}>
<Header />
<h1>Dashboard page</h1>
</div>
);
}
Folder Structure Overview
To keep your project organized, let's review the recommended folder structure.
Project folder structure
Testing Your Implementation
If everything is set up correctly, you should be able to toggle between dark and light themes seamlessly.
Light mode | Dark mode
Thank you for engaging with this article. I hope you found it informative. If you have any feedback or questions, feel free to share them with me!
In Plain English 🚀
Thank you for being a part of the In Plain English community! Before you leave, consider following us on various platforms for more valuable insights.
Discover how to implement a dark/light theme toggle in React using state and context with this tutorial.
Learn the right way to toggle dark mode using the React Context API in this detailed video guide.