Build dark mode theme toggle in React
Tags: react, css, javascript
Published: 2021-06-23 07:47:59

I got my inspiration for this post from [Article](https://dev.to/murtuzaalisurti/dark-mode-toggle-animation-using-css-27il) By @murtuzaalisurti 

Pre-requisite:
{% link https://dev.to/raviojha/setting-up-react-project-from-scratch-1g3k %}

In this post we will build a dark mode theme toggle. This is what we are aiming for:

![sun-moon](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rujidbrcznzs10apd1jo.gif)


Nice, Let's start !!
#### 1. Follow everything from [Here](https://dev.to/raviojha/setting-up-react-project-from-scratch-1g3k) (Also mentioned in pre-requisite) to start with react project from basic.
>I just created a new folder `sun-moon` and copied everything from the previous [post](https://dev.to/raviojha/setting-up-react-project-from-scratch-1g3k) (`react-from-scratch`) except `node_modules` and did a fresh `npm i`.

#### 2. Add `css-loader` and `mini-css-extract-plugin` as dependency:
```bash
npm i css-loader mini-css-extract-plugin -D
```

This will allow us to import `css` in our `js` file, as shown below:
```javascript
// in App.js file
import "./App.css";
```
So, let's create `App.css` in `src` folder and import it in `App.js`.

#### 3. Update `webpack.config.js` to include `css-loader` and `mini-css-extract-plugin` to this:
```javascript
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "./public"),
    filename: "bundle.js",
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: "babel-loader",
      },
      {
        test: /(\.scss|\.css)$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
  plugins: [new HtmlWebpackPlugin({ template: "./public/index.html" }), new MiniCssExtractPlugin()],
};
```
#### 4. Create `Sun` and `Moon` component
> This is basically going to contain `Sun/Moon` `svg` as react component.

We can get Sun and Moon SVG from here:
* [Sun](https://www.svgrepo.com/svg/19198/sun)
* [Moon](https://www.svgrepo.com/svg/327439/moon)

Although, We can manually create `React Component` from `SVG` but we can also use this `playground` which will just convert the `SVG` to react component for us: [React SVGR](https://react-svgr.com/playground/)

Now, After doing all above, this is how our project will look like:
```javascript
---sun-moon
  |--node_modules
  |--...
  |--src
     |--App.js
     |--App.css
     |--index.js
     |--Sun.js
     |--Moon.js
```
#### 5. Create `DarkContext` context
* Create a file `DarkContext.js` in `src` folder, with this content:
```javascript
import { createContext } from "react";

export const DarkContext = createContext(false);
```
* This creates a context object `DarkContext` using createContext method from React library.
* `DarkContext` has the default theme value as `false`.

#### 6. Wrap the `App` with `DarkContext.Provider`
* Wrap `App` with `DarkContext.Provider` and specify the initial value.
* Now, other components can access the value of the theme by using `DarkContext`.
* For our example, `Sun` and `Moon` Component needs to know if current context is `Dark` or `Light` (not-dark).

> Note: We could have passed the value of theme using props, and that would also be okay, but let's try to get a feel of react's context api.

So, after this our `App.js` will look like this:
```javascript
import React, { useState } from "react";
import { DarkContext } from "./DarkContext";

import "./App.css";

const App = () => {
  const [isDark, setIsDark] = useState(false);

  return (
    <DarkContext.Provider value={isDark ? true : false}>
      <div className="instruction">Click On Sun/Moon &#128071;</div>
      <div className={`app${isDark ? " dark" : ""}`}>
        Hello World
      </div>
    </DarkContext.Provider>
  );
};

export default App;
```

Here, `App` is also `maintaining` an internal state `isDark`, which helps to update our context value:
```javascript
<DarkContext.Provider value={isDark ? true : false}>
...
</DarkContext.Provider>
```
#### 7. Create `ThemeIcon` component to hold `Sun` & `Moon` component:
```javascript
import React, { useContext } from "react";
import Moon from "./Moon";
import Sun from "./Sun";

function ThemeIcon(props) {
  const { onClick } = props;

  return (
    <div className="theme-icon" onClick={onClick}>
      <Sun></Sun>
      <Moon></Moon>
    </div>
  );
}

export default ThemeIcon;
```

Nothing fancy here, it's just a component which holds our `Sun` and `Moon` component, it also needs to have an `onClick` handler which will change the value of `isDark` from `App's` internal state and that has to be passed down from `App` component.

#### 8. Include `ThemeIcon` component in `App` component and pass down the `onClick` handler:
```javascript
import React, { useState } from "react";
import { DarkContext } from "./DarkContext";
import ThemeIcon from "./ThemeIcon";

import "./App.css";

const App = () => {
  const [isDark, setIsDark] = useState(false);

  return (
    <DarkContext.Provider value={isDark ? true : false}>
      <div className={`app${isDark ? " dark" : ""}`}>
        <ThemeIcon onClick={() => setIsDark(!isDark)}></ThemeIcon>
      </div>
    </DarkContext.Provider>
  );
};

export default App;
```

If we look at this line:
```javascript
<div className={`app${isDark ? " dark" : ""}`}>
```

And also take a look at some corresponding `css`:
```css
.app {
  width: 100vw;
  height: 100vh;
  ...
}

.dark {
  ...
}
```
We will notice that, We are saying html element `div.app` is going to be `full width` and `full height`. And whenever the value of `isDark` is `true` i am going to add css class `dark` as well. 

#### 9. Use context in `Sun` and `Moon` Component:
As for the `useContext`
* It accepts a React Context object as an input. For us it will be `DarkContext`.
* The output of `useContext` is the current value of the context (`true/false`).

So, we will use `useContext` to get the current value of the context in `Sun/Moon` component.

This is how we will be doing it:
```javascript
const isDark = useContext(DarkContext);
```
Our, `Sun` and `Moon` will now look like this:
```javascript
import React, { useContext } from "react";
import PropTypes from "prop-types";
import { DarkContext } from "./DarkContext";

function Sun() {
  // Things to notice here:
  const isDark = useContext(DarkContext);

  return (
    <svg
      version="1.1"
      id="Capa_1"
      x="0px"
      y="0px"
      viewBox="0 0 457.32 457.32"
      // Things to notice here:
      className={`sun ${isDark ? " sun-dark" : ""}`}
    >
      ...
    </svg>
  );
}

export default Sun;
```
It will exactly be same for `Moon` component as well.
Now, if we do:
```
npm run start:dev
```

We should be able to see our app at `http://localhost:8080/`.

The only thing that we didn't discuss in entirety is it's `css` part, but if we are able to understand how `sun` is disappearing and how `moon` comes up, and how `moon` disappears and `sun` comes up, then we can say we understood it completely.

Although, we can see the `css` code here or in `github` (link shared at the end of this post) and try to understand what is going on, but instead let's look at these two screenshots, I believe that should be enough to understand the logic, rest all is just writing code for that logic.

So, to understand this, Let's look at this screenshot first, this is our initial state:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sbs7usg0o0fu1fi9xsgv.png)
  
So, We see there is `IconContainer` (in terms or our code it is `ThemeIcon` component, that we created above), that is holding `Sun` icon/component, the opacity of sun is `1` and there we also see `Moon` but it's not going to be visible because it's opacity is `0` `{opacity: 0}`, when i click on `IconContainer` our `Sun` is going to be invisible by making its opacity as `Zero(0)` (by adding extra class `sun-dark`) and moon's opacity will become `One(1)` (by adding extra class, `moon-dark` to the moon svg, based on the value of `isDark`), we can have another look at `Sun/Moon` component and see how `css` `classes` are getting added.

When we click on `IconContainer`, this is what it will look like:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aeok9xws34trwx4fkjyb.png) 

So, this is what happened when we clicked:

* `DarkContext`'s value is going to be true.
* `Sun/Moon` is going to know that change, through context, based on that they are going to act.
* `Sun` is going to attach `sun-dark` class to it's `SVG`, which will make it invisible (`opacity: 0`)
* `Moon` is going to attach `moon-dark` class as well to it's `SVG`, which will make it visible (`opacity: 1`)

When we click again, same thing is going to happen just in reverse.

There are some `transitions` and `transformation` that I have added to make it more interesting, but not to worry, if we are able to understand the logic above, things will be easy.

We can see the live demo here: [Sun Moon](https://sun-moon.vercel.app/)
We can see the entire code [here](https://github.com/rojha-mdsol/devto-my-examples/tree/master/theme-using-hooks)

--Thanks, Ravi