New to React hooks and state, trying to figure out why my code isn't working.
I have an App.js
file and a child component called Menu.js
. Essentially, Menu.js
contains a menu that I want to show if a variable called isOpen
is true
, and hide if isOpen
is false
. I have added the function at the parent level on App.js
, as I have another component that needs to be able to update the state of isOpen
as well, but that component is not relevant to my question. Here is the relevant code:
App.js:
import Menu from "./components/Menu";
import { useState } from "react";
export default function App() {
const [isOpen, setIsOpen] = useState(false);
const toggleState = () => setIsOpen(!isOpen);
console.log(isOpen);
// This will console log either true or false
const menuBtn = document.querySelector(".hamburgermenu");
const mobileMenu = document.querySelector(".mobilemenu");
// These are elements on the child component, Menu.js
if (isOpen) {
menuBtn.classList.add("open");
mobileMenu.style.zIndex = "5";
// This should reveal the menu if the value of isOpen is true
} else {
menuBtn.classList.remove("open");
mobileMenu.style.zIndex = "0";
// This should hide the menu if the value of isOpen is false
}
return (
<div>
<Menu togglestate={toggleState} />
// Passing togglestate down to Menu.js as a prop
</div>
);
}
Menu.js:
export default function Menu(props) {
return (
<div>
<div className="hamburgermenu" onClick={props.togglestate}>Stuff in here</div>
// Clicking this div will toggle the value of isOpen between true and false
<div className="mobilemenu">Stuff in here</div>
</div>
);
}
When I try to run that, my whole application breaks and I get the error TypeError: Cannot read properties of null (reading 'classList')
at menuBtn.classList.remove("open");
I know that my problem lies in that the default state is false
, so it will first try to run the else
statement. However, because the class open
hasn't been added to the menuBtn
element yet, there is no open
class to remove, and it's erroring out.
I tried to add another nested if/else
statement within the original else
statement, basically saying that if the element has the class called open
, remove it, like this:
if (isOpen) {
menuBtn.classList.add("open");
mobileMenu.style.zIndex = "5";
} else {
if (menuBtn.classList.contains("open")) {
menuBtn.classList.remove("open");
mobileMenu.style.zIndex = "0";
} else {
mobileMenu.style.zIndex = "0";
}
}
I also tried moving away from classes entirely and just changing the styles directly as a test, like this:
if (menuOpen) {
menuBtn.style.display = "block";
mobileMenu.style.zIndex = "5";
} else {
menuBtn.style.display = "none";
mobileMenu.style.zIndex = "0";
}
But I still got an error at the same line, TypeError: Cannot read properties of null (reading 'style')
at menuBtn.style.display = "block";
I know this is all a little messy. I'm sure there's a better way of doing this, but this is how I would go about it with the knowledge that I do have. If there is an easier way, I am open to suggestions!
CodePudding user response:
General idea in React is to not have to deal with the DOM directly with stuff like document.querySelector(".hamburgermenu")
Instead of creating a new function toggleState
, you can directly pass the setter function setIsOpen
into the Menu function as follows (along with the current open state)
<Menu isOpen={isOpen} togglestate={setIsOpen} />
Then your whole if-else condition can be simplified down to a ternary operator as follows:
export default function Menu({ isOpen, togglestate }) {
return (
<>
<div
className={'hamburgermenu' isOpen ? ' open' : ''}
onClick={() => togglestate(!isOpen)}
>
// Clicking this div will toggle the value of isOpen between true and
false
</div>
<div className="mobilemenu" style={{ zIndex: isOpen ? 5 : 1 }}>
Stuff in here
</div>
</>
);
}