function Navbar() {
const [isDark, setIsDark] = useState(false);
useEffect(() => {
if (isDark) {
let body = document.getElementsByTagName("body")[0];
body.classList.add("dark");
let darkIcon = document.getElementsByClassName("dark-icon")[0];
darkIcon.style.display = "none";
} else {
let lightIcon = document.getElementsByClassName("light-icon")[0];
lightIcon.style.display = "none";
}
}, [isDark])
return (
<div class="navbar">
<h1>devfinder</h1>
<div className="toggle">
<p onClick={() => {
setIsDark(!isDark);
}}className="light-icon">Light <img src={sunIcon} alt="Sun"/> </p>
<p onClick={() => {
setIsDark(!isDark);
}} className="dark-icon">Dark <img src={moonIcon} alt="Moon"/></p>
</div>
</div>
)
}
export default Navbar;
The onClick functions that I have in my paragraph tags aren't working and I have no clue why. There are no errors in the console, the function is just never called. Any suggestions?
CodePudding user response:
In React, it is incorrect to use DOM methods. React handles everything for you. Here is your code refactored a bit to likely do what you want it to:
import moonIcon from 'some/directory'
import sunIcon from 'some/directory'
function Navbar() {
const [isDark, setIsDark] = useState(false);
const [text, setText] = useState("Light")
const [iconClass, setIconClass] = useState("light-icon")
const [icon, setIcon] = useState(sunIcon)
const darkIcon = ( <img src={moonIcon} alt="Moon"/> )
const lightIcon = ( <img src={sunIcon} alt="Sun"/> )
useEffect(() => {
if (isDark) {
setIcon(moonIcon)
setText("Dark")
setIconClass("dark-icon")
} else {
setIcon(sunIcon)
setText("Light")
setIconClass("light-icon")
}
}, [isDark])
return (
<div class="navbar">
<h1>devfinder</h1>
<div className="toggle">
<p className={iconClass} onClick={() => { setIsDark(!isDark)}}>
{text}{icon}
</p>
</div>
</div>
)
}
export default Navbar;
Breaking this code down is important because it changes a lot about your approach and makes it more of a "React" approach by using the useState
hook to store the state of several different elements.
const [text, setText] = useState("Light")
const [iconClass, setIconClass] = useState("light-icon")
const [icon, setIcon] = useState(sunIcon)
We can store the state of text, class names, and even which icon to display. useState
is very powerful!
const darkIcon = ( <img src={moonIcon} alt="Moon"/> )
const lightIcon = ( <img src={sunIcon} alt="Sun"/> )
You can also store html code as a constant using jsx, you aren't limited to just returning it. This means we can pass html code around as a variable!
useEffect(() => {
if (isDark) {
setIcon(moonIcon)
setText("Dark")
setIconClass("dark-icon")
} else {
setIcon(sunIcon)
setText("Light")
setIconClass("light-icon")
}
}, [isDark])
The useEffect
section just replaces your DOM methods with state updates. How does it work?
<p className={iconClass} onClick={() => { setIsDark(!isDark)}}>
{text}{icon}
</p>
Notice how in your returned html, I've reduced your paragraphs down from two just to one. That's because instead of having two paragraphs that conditionally set their display
css, you can actually just have one paragraph that conditionally displays html elements and images!
useEffect
in React will run code within it once per frame. So what it's doing is checking if the state isDark
has changed (via onClick). If it has, then perform some update. The updates: set the icon (to either moon or sun), set the text (the text next to the icon, either "Light" or "Dark"), and set the class of the paragraph (because I noticed that class changes based on dark vs light mode).
Referring back to the paragraph: you can pass your JavaScript variables into your html if you wrap them with curly braces (className={iconClass}
will set the className to whatever iconClass
's value is, etc.).
When you click the paragraph, the variables update, and the html will also update to have the new values.
CodePudding user response:
I guess you need to switch between light and dark mode here is my simple illustration for that to get a basic idea for you
export default function App() {
const [dark, setDark] = useState(false);
return (
<div className="App">
<div class="navbar">
<h1>devfinder</h1>
<h4>color mode is {!dark ? "light" : "dark"}</h4>
<div className="toggle">
<div onClick={() => setDark(false)} className="light-icon">
Light
<i class="fas fa-sun"></i>
</div>
<div onClick={() => setDark(true)} className="dark-icon">
Dark
<i class="fas fa-moon"></i>
</div>
</div>
</div>
</div>
);
}
this switch state and you can do whatever you want with state working example - https://codesandbox.io/s/silly-framework-rsgue
CodePudding user response:
If you simply want to toggle between dark and light remove the unnecessary useEffect
(don't use native DOM methods with React), and use a ternary operator in your return
to determine which paragraph/icon should be used when the component re-renders when the state changes.
Note: I've used font-awesome here because I don't have access to your images.
const { useState } = React;
function Example() {
const [isDark, setIsDark] = useState(false);
return (
<div class="navbar">
<h1>devfinder</h1>
<div className="toggle">
{isDark ? (
<p
onClick={() => setIsDark(!isDark)}
className="dark"
>Dark
<i className="fa-solid fa-moon"></i>
</p>
) : (
<p
onClick={() => setIsDark(!isDark)}
className="light"
>Light
<i class="fa-solid fa-sun"></i>
</p>
)}
</div>
</div>
);
}
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
p { padding: 5px 0 5px 5px; }
p:hover { cursor: pointer; }
.light { background-color: #FAFAD2; color: black; }
.dark { background-color: #333333; color: white; }
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta2/css/all.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>