DEV Community

Cover image for How to add dark mode functionality with React Hooks
Aman Mittal
Aman Mittal

Posted on • Originally published at blog.crowdbotics.com

How to add dark mode functionality with React Hooks

Originally published at Crowdbotics.com

Developers seems to love dark mode. A lot of popular websites like Twitter, Reddit, and YouTube are now supporting this dark mode on their websites and applications as well. It is more than a trend. It's easy on user's eyes and decreasing the problem of eye fatigue.

In this post, I am going to show you how you can add this functionality in your React apps whether it is an admin dashboard that is consuming some third party API hosted elsewhere or a web blog using static site generators like Gatsby. The steps discussed below are going to be same whether you decide to use create-react-app utility to generate a React app or using any static site generator.

This tutorial is going to use React hooks. If you are not familiar with them at all, it is recommended that you get familiar with them from the official React docs. Otherwise, whenever hooks are used, a brief overview is provided.

Table of Contents

  • Requirements
  • Setting up a base theme
  • Add a toggle button to change the theme
  • Adding theme persistence using local storage
  • Connecting Crowdbotics support to Your Github Repo
  • Conclusion

Requirements

Note: These requirements are needed if you are looking to follow this post from a technical point of view, which means, if you are looking to try out Crowdbotics for the first using a custom template from the marketplace or are actually interested in building a custom dating app using Crowdbotics template, React Native, and Expo. If later is your scenario, this post can act as a guide and an entry point to the template.

Setting up a base theme

In this section, let us start by adding a base React app that uses light mode by default. To start, using create-react-app create a new directory. After you have created a new directory, navigate inside the project directory. You can copy and paste the following steps in your terminal window.

create-react-app react-darkmode-app

cd react-darkmode-app
Enter fullscreen mode Exit fullscreen mode

Once inside the project directory, open App.css file and the following styles for the base light theme.

body {
  margin: 0;
  text-align: center;
}

.light-theme {
  background-color: #fff;
  color: #444444;
}

nav {
  display: flex;
  text-align: center;
  background-color: #503d81;
  width: 100%;
  padding: 20px;
  color: #f8f8f8;
}

.content {
  padding: 0.5em;
  margin: 0 auto;
  max-width: 600px;
  font-size: 1.2rem;
  line-height: 1.1;
  height: 90vh;
}
Enter fullscreen mode Exit fullscreen mode

To apply these styles, open App.js component file and replace the default code with the below snippet.

import React from 'react'
import './App.css'

function App() {
  return (
    <div className="light-theme">
      <nav>Toggle button will go here</nav>
      <div className="content">
        <h1>Light Mode</h1>
      </div>
    </div>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

Now, try to run the app using the command yarn start from a terminal window. Visit the URL http://localhost:3000 in a browser window and you will get the following result.

ss1

Head back to the App.css file and create base styles for the dark theme.

.dark-theme {
  background-color: #1f1b24;
  color: #f8f8f8;
}

.dark-theme nav {
  background-color: #332940;
}

.dark-theme code {
  color: red;
}
Enter fullscreen mode Exit fullscreen mode

Notice that using CSS selector property, you are changing the background color of the navbar and the rest of the styles remain the same as before. For example, the text color property is going to be the same as in light theme. To test out the dark theme, change the className of the first div element inside the App.js file to dark-theme.

function App() {
  return (
    <div className="dark-theme">
      <nav>Toggle button will go here</nav>
      <div className="content">
        <h1>Dark Mode</h1>
        <p>
          Do take a note of the <code>color</code> property in the nav bar.
        </p>
      </div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Looking back at the browser window, you get the following result.

ss2

At this point, you have a very simplified version of the app, but it doesn't fulfill the purpose. It has to keep track of which theme the user selects and show the styles accordingly.

Add a toggle button to change the theme

To let the end-user decide which theme they want to the view, your React app would be done by adding a toggle button. To change the theme between the light or dark, there is a need for a variable to keep track of what theme the user has selected. For this purpose, let us add the new concepts of React Hooks rather than converting the current App component into a class component.

To give a brief overview of React Hooks, one can say they are available to React since the version 16.8.x. They are functions that allow you to initialize and use React state and a component's life-cycle methods in a functional component. Hooks do not work with classes. If you are familiar with React, you know that the functional component has been called as a functional stateless component. Not any more.

React provides a few built-in Hooks such as useState to initialize a default state of a component similarly as in a class component with the keyword state. Open App.js file and initialize the state as below.

function App() {
  const [darkTheme, setDarkTheme] = React.useState(false)
  // rest remains same
}
Enter fullscreen mode Exit fullscreen mode

In the above snippet, the default value of darkTheme variable is false. This means that it is not the default set of styles that the app will use. Using conditional operator, update the return function inside App().

return (
  43 -
  (
    <div className={darkTheme ? 'dark-theme' : 'light-theme'}>
      <nav>
        <div className="button-container">
          <button onClick={() => setDarkTheme(prevTheme => !prevTheme)}>
            Toggle Theme
          </button>
        </div>
      </nav>
      <div className="content">
        <h1>{darkTheme ? 'Dark Mode' : 'Light Mode'}</h1>
        <p>
          Do take a note of the <code>color</code> property in the nav bar.
        </p>
      </div>
    </div>
  )
)
Enter fullscreen mode Exit fullscreen mode

Starting from the first div element, it uses the conditional operator to check whether the value of the state variable darkTheme is true or false. If it is true, the styles related to the dark theme are going to be applicable. Otherwise, the default light theme is activated. The same conditional operator goes on the h1 element to display the text of which theme is being currently used.

The button uses an onClick event to toggle the behavior of light and a dark theme. The prevTheme is used to apply functional updates. It computes the value of the previous state and then returns an updated value. In the current case, the value here represented is the theme itself.

Here is the output in the browser window. Click the Toggle Theme button to change the theme and the heading in the content.

ss3

Here are the corresponding styles for the button-container.

.button-container {
  display: flex;
  margin: 0 auto;
}
Enter fullscreen mode Exit fullscreen mode

Adding theme persistence using local storage

Right now, the user can easily switch between the two themes your app is running on. However, whenever the web page reloads, the default theme shown is light even though the last theme you selected was dark. To provide the pragmatic solution, in this section, you are going to store the value of dark theme in the browser's local storage.

As compared to lifecycle methods in class components, in modern-day React functional components, you can easily add the same working functionality using useEffect. It accepts a function in the form of an argument. This function can be written with the keyword function or use an arrow function. Also, this function passed to useEffect as the argument will run after every render is completed.

To avoid this, you can conditionally render the useEffect method. This is done by passing an empty array as the second argument. The value this array will contain is going to be the value of the darkTheme. After you have defined the state in the App component, add this effect method.

React.useEffect(() => {
  localStorage.setItem('dark', JSON.stringify(darkTheme))
}, [darkTheme])
Enter fullscreen mode Exit fullscreen mode

Using an arrow function as the first argument, it is setting the value of darkTheme in the browser's localStorage. To add a theme value to the local storage, there are two things required in combination. An identifier or a key has to be passed as the first argument to setItem() along with boolean value of darkTheme as the second argument.

Now, go back to the browser window and open dev tools. In the tab, Application look for the Storage > Local Storage as described in the image below. You will find a key dark that has the value of false.

ss4

On clicking the button Toggle Theme the value of the key dark in the local storage changes to true.

ss5

It works but on reloading the React app, switches back to the light theme. This is because the default value provided to the dark theme is always false. Let us change that.

Go back to the App component and create a new function, getDefaultTheme. It will keep track of getting the value of the dark from the local storage. Also, the default value of the darkTheme in the state is going to be read from this function (in other words, from the local storage) rather than a boolean false.

const [darkTheme, setDarkTheme] = React.useState(getDefaultTheme())

React.useEffect(() => {
  localStorage.setItem('dark', JSON.stringify(darkTheme))
}, [darkTheme])

function getDefaultTheme() {
  const selectedTheme = JSON.parse(localStorage.getItem('dark'))
  return selectedTheme || false
}
Enter fullscreen mode Exit fullscreen mode

Notice in below demo of how the value of the dark is saved in the local storage even when the React app reloads.

ss6

Connecting Crowdbotics support to your Github Repo

Once everything is working, now let us add git version to this React project and then, further add the support for Crowdbotics app building platform. Open a terminal window and execute:

git init

# add all files
git add .

# commit
git commit -m "update"
Enter fullscreen mode Exit fullscreen mode

Once all the files are committed, add this repository to your Github account. Crowdbotics app building platform now gives you an option to connect a Github repository directly using GitHub OAuth integration ( which means you need to have a Crowdbotics account or login into one using your Github account).

ss7

More and in-detail information about this process could be found here.

Conclusion

Congratulations! You have made to the end. I hope you learned something useful here. React Hooks is a powerful concept, and getting more commonly used as the time progress. Here are some resources related to this post:


I frequently write on Nodejs, Reactjs, and React Native. You can subscribe to my weekly newsletter to receive new updates straight to your inbox 💌

Top comments (5)

Collapse
 
waylonwalker profile image
Waylon Walker

👏 Thanks for sharing.

I am redesigning waylonwalker.com. The current redesign is dark. I might consider light and dark like you have. I like how you put the preference in localstorage.

What are your thoughts on the newprefers-color-scheme to tap into the users preference set in their os?

Collapse
 
amanhimself profile image
Aman Mittal

Thanks for reading it Waylon 🙌

The reason I used localStorage because I didn't know about prefers-color-scheme at the time of writing this post. I will have to try in practice. Hit me up, if you use this new syntax, seems legit as you can define it using media queries. :)

Collapse
 
waylonwalker profile image
Waylon Walker

I actually think that giving users the ability to change it in local storage like you have is still very good, but adding prefers-color-scheme as the first default would be best.

Collapse
 
gerbosan profile image
Carlos A.

Thanks for the article. Was simple and well explained.

As a comment, nav doesn't require width: 100%;

Collapse
 
amanhimself profile image
Aman Mittal

Thanks for the tip Carlos :)