I got my inspiration for this post from Article By @murtuzaalisurti
Pre-requisite:
In this post we will build a dark mode theme toggle. This is what we are aiming for:
Nice, Let's start !!
1. Follow everything from Here (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 (react-from-scratch
) exceptnode_modules
and did a freshnpm i
.
2. Add css-loader
and mini-css-extract-plugin
as dependency:
npm i css-loader mini-css-extract-plugin -D
This will allow us to import css
in our js
file, as shown below:
// 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:
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:
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
Now, After doing all above, this is how our project will look like:
---sun-moon
|--node_modules
|--...
|--src
|--App.js
|--App.css
|--index.js
|--Sun.js
|--Moon.js
5. Create DarkContext
context
- Create a file
DarkContext.js
insrc
folder, with this content:
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 asfalse
.
6. Wrap the App
with DarkContext.Provider
- Wrap
App
withDarkContext.Provider
and specify the initial value. - Now, other components can access the value of the theme by using
DarkContext
. - For our example,
Sun
andMoon
Component needs to know if current context isDark
orLight
(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:
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 👇</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:
<DarkContext.Provider value={isDark ? true : false}>
...
</DarkContext.Provider>
7. Create ThemeIcon
component to hold Sun
& Moon
component:
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:
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:
<div className={`app${isDark ? " dark" : ""}`}>
And also take a look at some corresponding 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:
const isDark = useContext(DarkContext);
Our, Sun
and Moon
will now look like this:
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:
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:
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 attachsun-dark
class to it'sSVG
, which will make it invisible (opacity: 0
) -
Moon
is going to attachmoon-dark
class as well to it'sSVG
, 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
We can see the entire code here
--Thanks, Ravi
Top comments (1)
Really cool Ravi. Thanks for this!