Implementing Dark Mode In React Using UseContext and UseReducer.

Implementing Dark Mode In React Using UseContext and UseReducer.

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-UIicons 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.