Modern applications allow users to change themes, a common feature called Dark Mode. This will enable users to choose the application’s appearance preferences. Whether you’re on team light or team dark, it’s becoming more usual for apps to support these two color schemes. In this article, we will control Light and Dark modes in React using the useContext and useReducer hooks.
The process of building this project can be outlined in steps.
Prerequisite
Fundamental knowledge of React and basic React hooks.
Text editor e.g(Vs code)
Web browser e.g(Google chrome)
Setting up our context and reducer files
The useContext is a React hook that can be used to change the state of a component in React, this works by creating a context object that surrounds a React component, causing it to subscribe to the context object, hereby making it read the current context value from the closest matching Provider above it in the tree, and makes it possible to change the current context value from almost anywhere in the project. It works hand in hand with useReducer to intricately change the state of a component in the React App. To implement dark mode in react using useContext and useReducer, we will be doing the following:
Inside your React app project within the src
folder create a new folder called Context, and then also create sub-files called DarkModeContext.js and DarkModeReducer.js, these will be the files where the majority of the useContext and useReducer logic will be written.
Importing our createContext and useReducer hook
In the darkModeContext.js file import createContext and useReducer from react
, Also we will then import darkModeReducer
from the darkModeReducer.js file, this will be used later on in the course.
import {createContext,useReducer} from 'react';
import darkModeReducer from './darkModeReducer.js';
Creating an INITIAL_STATE object
We will then create an object called INITIAL_STATE
and give it a darkMode
parameter and set its value to false
import {createContext,useReducer} from 'react';
import darkModeReducer from './darkModeReducer.js';
const INITIAL_STATE = {
darkMode: false
}
After that, we will then create a variable called DarkModeContext
that uses createContext to set its value to INITIAL_STATE
. The variable created should also be exported so that it can also be used in other parts of our react app.
import {createContext,useReducer} from 'react';
import darkModeReducer from './darkModeReducer.js';
const INITIAL_STATE = {
darkMode: false
}
export const DarkModeContext = createContext(INITIAL_STATE);
Creating a DarkModeContextProvider JSX component
We will also create a new function called DarkModeContextProvider
and set its argument to be an object called {children}
. Then create a destructured variable that has state
as the first array value and dispatch
as the second array value. The variable should then be equal to useReducer
which takes in darkModeReducer as the first argument, and INITIAL_STATE as the second argument. The DarkModeContextProvider
function then returns a <DarkModeContext.Provider>
jsx component that has the {children}
object in it, and has a value which is an object that has two parameters, the first being darkMode
which has a value of state.darkMode
and a second parameter called dispatch
.
import {createContext,useReducer} from 'react';
import darkModeReducer from './darkModeReducer.js';
const INITIAL_STATE = {
darkMode: false
}
export const DarkModeContext = createContext(INITIAL_STATE);
export const DarkModeContextProvider = ({children}) => {
const [state,dispatch] = useReducer(darkModeReducer,INITIAL_STATE)
return(
<DarkModeContext.Provider value={{darkMode:state.darkMode,dispatch}}>{children}</DarkModeContext.Provider>
)
}
Setting up darkModeReducer
In the darkModeReducer.js file, we will then create a function called darkModeReducer that takes in state
and action
as parameters, and switches the value of darkMode based on the current value of darkMode currently is.
const darkModeReducer = (state, action) => {
switch(action.type) {
case "DARK": {
return{
darkMode: true
}
}
case "LIGHT": {
return{
darkMode: false
}
}
case "TOGGLE": {
return{
darkMode: !state.darkMode
}
}
default:
return state
}
}
export default darkModeReducer
Using DarkModeContextProvider in index.js
In index.js, we will then wrap the App.js
react component in a<DarkModeContextProvider>
react component that was imported from ./context/darkModeContext
. It will then look like this.
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { DarkModeContextProvider } from './context/darkModeContext';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<DarkModeContextProvider>
<App />
</DarkModeContextProvider>
</React.StrictMode>
);
Stying the page
Inside the src
folder we will also create a file called dark.css
and give it a darkMode styling.
.app.dark{
background-color: #222;
color: white;
display: flex-start;
height: 100vh;
}
.app{
display: flex;
background-color: white;
color: black;
}
Using the created DarkModeContext
The context can then be used in whatever React file we need to use it in. I will be using App.js
file for the description. In the App.js
We will then import useContext
from react, and DarkModeContext
from ‘./context/darkModeContext’, we’ll also import ./dark.css
.
import {useContext} from 'react';
import {DarkModeContext} from "./context/darkModeContext";
import "./dark.css";
Creating an App.js functional component
In the main function of App.js,
we’ll then create a variable of {darkMode}
that is equal to useContext
that takes DarkModeContext
as a parameter. We will also create a basic jsx structure for our page, that’ll have a navbar with a className of navbar, which will have two divs that’ll be styled to be buttons for light mode and dark mode.
import {useContext} from 'react';
import {DarkModeContext} from "./context/darkModeContext";
import "./dark.css";
import "./app.css";
export default function App(){
const {darkMode} = useContext(DarkModeContext)
return(
<div>
<div className="navbar">
<div className="lightmode"></div>
<div className="darkmode"></div>
</div>
<h1>Tutorial on how to use UseContext and UseReducer to create darkmode theme</h1>
</div>
);
}
Styling the mode change buttons
Next, we’ll create an app.css
file in the src
folder and create two boxes, that’ll be styled and used to switch from light mode to dark mode.
.navbar{
display: flex;
background-color: grey;
justify-content: start;
padding-left: 80px;
}
.lightmode{
width: 50px;
height: 50px;
background-color: white;
}
.darkmode{
width: 50px;
height: 50px;
background-color: black;
}
Creating the dispatch event handlers
Inside of App.js
we will create another variable called {dispatch}
and set it equal to useContext
which takes in a parameter of DarkModeContext
. Then we will create onClick event handlers for lightmode and darkmode that dispatch the various styles based on the darkMode type.
import {useContext} from 'react';
import {DarkModeContext} from "./context/darkModeContext";
import "./dark.css";
import "./app.css";
export default function App(){
const {darkMode} = useContext(DarkModeContext);
const {dispatch} = useContext(DarkModeContext);
return(
<div className={darkMode?"dark app": "app"}>
<div className="navbar">
<div className="lightmode" onClick={()=>dispatch({type:"LIGHT"})}></div>
<div className="darkMode" onClick={()=>dispatch({type:"DARK"})}></div>
</div>
<h1>Tutorial on how to use UseContext and UseReducer to create darkmode theme</h1>
</div>
);
}
Creating an alternate toggle dispatch event handler
We can also create an event handler in App.js
that works on a toggle icon and changes the background theme anytime the toggle icon is clicked. We will be using Material-UI
icons ToggleOff and ToggleOn icons and use the useState
hook to render each of the icons conditionally. We can use these icons by installing material-ui in your project from https://mui.com/material-ui/getting-started/installation/#npm
and then installing the icons from https://mui.com/material-ui/icons/
. The installed icons can then be used by importing them into our project.
We then create a new function to handle our onClick event for the toggleOn and toggleOff buttons. The function will use setToggle to change the state value of toggle to be the opposite of what it currently is, making the buttons change dynamically on click. Inside the handleSwitch
function they will also be a dispatch value that sets the action.type
of the dispatch reducer to "TOGGLE"
.
import {useContext} from 'react';
import {DarkModeContext} from "./context/darkModeContext";
import "./dark.css";
import "./app.css";
import { ToggleOff,ToggleOn } from '@mui/icons-material';
export default function App(){
const {darkMode} = useContext(DarkModeContext);
const {dispatch} = useContext(DarkModeContext);
const [switch,setSwitch] = useState(false);
const handleSwitch = () => {
setToggle(prevSwitch=>!prevSwitch)
dispatch({type:"TOGGLE"})
}
return(
<div className={darkMode?"dark app": "app"}>
<div className="navbar">
<h1>Navbar</h1>
<div className="right-side">
<div className="lightmode" onClick={()=>dispatch({type:"LIGHT"})}></div>
<div className="darkmode" onClick={()=>dispatch({type:"DARK"})}></div>
{toggle?
<ToggleOff onClick={handleToggle} className="icons" style={{fontSize:"50px"}}/>:
<ToggleOn onClick={handleToggle} className="icons" style={{fontSize: "50px"}}/>
}
</div>
</div>
<h1>Tutorial on how to use UseContext and UseReducer to create darkmode theme</h1>
</div>
);
}
We then add a bit of styling in the app.css for the buttons and the navbar.
.navbar{
display: flex;
background-color: grey;
justify-content: space-between;
padding-left: 80px;
padding-right: 80px;
}
.lightmode{
width: 50px;
height: 50px;
background-color: white;
}
.darkmode{
width: 50px;
height: 50px;
background-color: black;
}
.right-side{
display: flex;
}
.icons{
color: white;
}
After all of that, without any problems and functioning properly, the project should look like this
CONCLUSION
While it is still practically okay to use basic props to implement dark mode in a react app project, it is necessary to note that passing props from children to parents is not possible in react, except through the use of function callbacks, which is not good practice. That is why useContext and useReducer is the most efficient method in implementing dark mode, the created context can now be used in any part of the react app to implement dark mode without the use of props.