DEV Community

Cover image for A simple way of adding dark mode to your React app using Hooks and saving it to the local storage
Ece Tunca
Ece Tunca

Posted on • Updated on

A simple way of adding dark mode to your React app using Hooks and saving it to the local storage

In this article I will share my approach for the dark/light mode toggle that I recently implemented in a React project, which I think is quite easy to understand also for beginners.

This is how it looks in my application

First I add a <span> element into my App.tsx file. It can either be a <button>, <div>, whatever you prefer. This will act as a switch for dark/light mode :

import React, { useEffect, useState } from 'react';

function App() {
    return (
      <div className='container'>
        <span className='mode-switch'></span>
        {/* my other elements */}
      </div>
    )
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Then I add some basic styles. I prefer my switch to be positioned absolutely on the right top corner of my container element:

.container {
  position: relative;
  max-width: 1400px;
  padding: 40px 30px;
}

.mode-switch {
  position: absolute;
  right: 15px;
  top: 15px;
  font-size: 11px;
  cursor: pointer;
  transition: color 0.2s ease-in-out;

  &:hover {
    color: #50bbf1;
  }
}

Enter fullscreen mode Exit fullscreen mode

I go back to my App component and add the useState hook. I define a mode variable and a setMode function. For now I pass the default mode as 'light' inside the useState hook.
Then I add an onClick event to my switch and in this event, I call the setMode function with a conditional parameter.
This function makes sure that it sets the mode to dark if it was light, and vice versa.
I also add the text content into the switch dynamically :

function App() {
    const [mode, setMode] = useState('light'); 

    return (
      <div className='container'>
        <span 
           className='mode-switch'
           onClick={() => 
             setMode(mode === 'dark' ? 'light' : 'dark')
           }
        >
           {mode === 'dark' ? 'Light mode' : 'Dark mode'}
        </span>
      </div>
    )
}

Enter fullscreen mode Exit fullscreen mode

Next step is switching between modes and adding/removing relevant styles, which will be achieved using the useEffect hook.
It will simply add a '.dark' class to the <body> when switched to the dark mode, and remove it when the selected mode is light.
I pass [mode] as the second parameter to useEffect because it will work as the side effect of the changing 'mode':

function App() {
    const [mode, setMode] = useState('light'); 

    useEffect(() => {
      if (mode === 'dark') {
        document.body.classList.add('dark');
      } else {
        document.body.classList.remove('dark');
      }
    }, [mode]);

    return (

Enter fullscreen mode Exit fullscreen mode

Then I add the necessary styles, which make the background-color black and turn all the text to white if they were not originally assigned any color and were black by default:

.dark {
  background-color: #222;
  color: #f5f5f5; 
}

Enter fullscreen mode Exit fullscreen mode

In order to style other elements other than the <body> in dark mode, I use the & selector.
Let's say I have a button with 'primary-button' class. I want to change its color and background-color when the dark mode is active:

.primary-button {
  // default style: black button with white text
  background-color: #222;
  color: #f5f5f5;

  // dark mode style: white button with black text 
  .dark & {
    background-color: #f5f5f5;
    color: #222;
  }
}

Enter fullscreen mode Exit fullscreen mode

Now it's time to save the selected mode to the local storage, so that the selected mode will persist even if the app is reset. To achieve this, first I go back to the useEffect hook and include the following code into it:

useEffect(() => {
  if (mode === 'dark') {
     document.body.classList.add('dark');
  } else {
     document.body.classList.remove('dark');
  }
  localStorage.setItem('mode', mode); // mode saved to local storage
}, [mode]);

Enter fullscreen mode Exit fullscreen mode

Then I go up and create a utility function called getDefaultMode on a global level. This function will get the saved mode from the local storage and determine the default mode accordingly when the app starts. If dark mode was not selected previously, the default mode will be 'light':

function getDefaultMode() {
  const savedMode = localStorage.getItem('mode');
  return savedMode ? savedMode : 'light';
}

Enter fullscreen mode Exit fullscreen mode

Now I need to call this function inside the useState hook that I previously added inside my App component. I replace the light parameter with the getDefaultMode function:

const [mode, setMode] = useState(getDefaultMode());
Enter fullscreen mode Exit fullscreen mode

The final code looks like this in the end:

import React, { useEffect, useState } from 'react';

function getDefaultMode() {
  const savedMode = localStorage.getItem('mode');
  return savedMode ? savedMode : 'light';
}

function App() {
    const [mode, setMode] = useState(getDefaultMode()); 

    useEffect(() => {
      if (mode === 'dark') {
        document.body.classList.add('dark');
      } else {
        document.body.classList.remove('dark');
      }
      localStorage.setItem('mode', mode);
    }, [mode]);

    return (
      <div className='container'>
        <span 
           className='mode-switch'
           onClick={() => 
             setMode(mode === 'dark' ? 'light' : 'dark')
           }
        >
           {mode === 'dark' ? 'Light mode' : 'Dark mode'}
        </span>
        {/* my other elements */}
      </div>
    )
}

Enter fullscreen mode Exit fullscreen mode

Discussion (7)

Collapse
email2vimalraj profile image
Vimalraj Selvam

This is good, however you might have to use the css variables instead of maintaining a child class for each and every style class.

Collapse
ece profile image
Ece Tunca Author

Great suggestion, I will definitely look into that. Thank you!

Collapse
urakymzhan profile image
Ulan Rakymzhanov

Is it a valid approach? Do you have example app or website that you implemented this aproach?

Collapse
ece profile image
Ece Tunca Author

Hi Ulan,
Yes I recently implemented it in a Pomodoro timer application. The project is not live yet since it is still work in progress, but the source code is visible on my github: github.com/etunka/leila-tomato-tim...

Collapse
ece profile image
Ece Tunca Author • Edited on
Collapse
urakymzhan profile image
Ulan Rakymzhanov

Thanks. It would be useful if you make it a chrome extension rather than an app.

Collapse
nikhilroy2 profile image
Nikhil Chandra Roy

good, keep up. nice work but if you include bootstrap to toggle the mode that will also good though keep up your work.