React toggle body class when state is changed - reactjs

I have a menu button on my site that when I click it I am toggling the visibility of the site menu. How can I also toggle a class on the site Body element when the menu button is visible, so that I can prevent vertical scrolling?
// App.js
function App() {
return (
<div className="App">
<Header />
</div>
);
}
--
// Header.js
function Header() {
// Set menu button default state
const [active, setActive] = useState(false);
return (
<>
// Menu Button
<button
onClick={() => setActive(!active)}
type="button">
<span className="is-block">{active ? 'Close' : 'Menu'}</span>
</button>
// Menu Links
<div className={active ? 'c-navigation is-active' : 'c-navigation'}>
<li>Link 1</li>
<li>Link 2</li>
<li>Link 3</li>
<li>Link 4</li>
</div>
</>
)
}

doing something like this:
document.body.style.overflow = "hidden";
and don't worry you're not manipulating the DOM that React is rendering, you're manipulating its parent.
function Header() {
// Set menu button default state
const [active, setActive] = useState(false);
useEffect(() => {
if (active) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "unset";
}
}, [active]);
return (
<>
// Menu Button
<button onClick={() => setActive(!active)} type="button">
<span className="is-block">{active ? "Close" : "Menu"}</span>
</button>
// Menu Links
<div className={active ? "c-navigation is-active" : "c-navigation"}>
<li>Link 1</li>
<li>Link 2</li>
<li>Link 3</li>
<li>Link 4</li>
</div>
</>
);
}

Related

How to close an opened menu with submenu items when another menu is clicked

The Problem
I have a sidebar with menus and some of the menus has submenus. I would like to close any opened menu with submenus when a different menu is clicked.
Stack
Written in React with react hooks
Sidebar
const [open, setOpen] = useState(true);
return (
<div>
<ul>
{SidebarData.map((item, index) => {
return (
<SubMenu
item={item}
key={index}
titleOpen={!open}
dropOpen={open}
subMenuOpen={open}
/>
);
})}
</ul>
</div>
);
}
SubMenu
function SubMenu({ item, titleOpen, dropOpen, subMenuOpen }) {
const [subnav, setSubnav] = useState(false);
const showSubnav = () => setSubnav(!subnav);
return (
<div>
<ul>
<Link to={item.path} onClick={item.subNav && showSubnav}>
<li>
<span>{item.icon}</span>
<span>
{item.title}
</span>
{item.subNav && dropOpen && (
<KeyboardArrowDownIcon/>
)}
</li>
</Link>
{subnav &&
subMenuOpen &&
item.subNav.map((item, index) => {
return (
<ul>
<Link to={item.path} key={index}>
<li>
<div>{item.title}</div>
</li>
</Link>
</ul>
);
})}
</ul>
</div>
);
}
Each menu has a unique id
Link to Sandbox
https://codesandbox.io/s/laughing-flower-x5rwkh
To achieve this you would need to move state of opened menus outside of the Submenu component and consume it as a prop instead.
Sidebar
const [openedMenuIndex, setOpenedMenuIndex] = useState(0);
return (
<div>
<ul>
{SidebarData.map((item, index) => (
<SubMenu
item={item}
key={index}
isOpened={openedMenuIndex === index}
onClickSubnav={() => setOpenedMenuIndex(index)}
/>
)}
</ul>
</div>
);
}
Submenu
function SubMenu({ item, isOpened, onClickSubnav}) {
return (
<div>
<ul>
<Link to={item.path} onClick={item.subNav && onClickSubnav}>
....
// later in the code show/hide subnav based on value in isOpened property
There are probably a couple ways to do it, but I think the easiest would be to have a useState to keep track of the menu item that is expanded:
const [expanded, setExpanded] = React.useState("name of default menu item to expand"); // or React.useState(null) to collapse all by default
and then use that variable when determining whether or not to show something (and how you do it is up to you), e.g.:
<SubMenuItem visible={expanded === 'subitem3'} onChange={setExpanded('subitem3')}>
visible is probably not a real property, but whatever it is that you use as the condition to determine whether the subitem is in an expanded state or not. Changing expanded then re-evaluates all the other submenu item conditions, which will collapse them.

Why my useState doesn't work as it should?

So I have a sidebar in my react app, and when the screen is mobile, it goes to the top and only the button "menu" remains, and when clicked it should display the sidebar.
Example is below:
Sidebar:
Sidebar with mobile screen(with clicked button):
So I have a code like this:
const Sidebar = () => {
const [sideBar, setSidebar] = useState(false);
return (
<div className="sidebar">
<span class="btn">Menu</span>
<div className="profile">
<span>Alim Budaev</span>
<span>Available for work</span>
</div>
<ul className="sidebarlist" id={setSidebar ? "hidden" : ""}>
{SlidebarData.map((val,key) =>{
return (
<li
className="row"
id={window.location.pathname == val.link ? "active" : ""}
key={key}
onClick={()=> {
window.location.pathname = val.link
}}>
{""}
<div>
{val.title}
</div>
</li>
);
})}
</ul>
</div>
);
}
export default Sidebar;
So as you could see, I'm using useState(), so when state is true, the "hidden" id should be added to ul and it should display, but state is false in my code, and it display anyway:
I'm not sure what could be the problem, but I think if state is false, the "hidden" id shouldn't be added, and the sidebar shouldn't be displayed.

Vertical menu in react

I need to have a vertical menu like below picture, in react
here is my code:
function DropDownMenu({className}) {
const [isOpen, setIsopen] = useState(false);
const toggleDropDown = () => {
setIsopen(isOpen => !isOpen)
}
return (
<nav className={styles.nav}>
<ul>
<li onClick={toggleDropDown} ><a href='#settings'>item1</a>
{isOpen && <ul>
<li><a href='#settings'>sub-item1</a></li>
<li><a href='#settings'>sub-item2</a></li>
<li><a href='#settings'>sub-item-3</a></li>
</ul>
}
</li>
<li onClick={toggleDropDown} ><a href='#message'>item3</a>
{isOpen &&
<ul>
<li><a href='#settings'>sub-item1</a></li>
<li><a href='#settings'>sub-item2</a></li>
</ul>
}
</li>
</ul>
</nav>
);
}
It lacks hide/show each item separately, and an arrow with some animation when it opens/closes
You should create a separate component called MenuItem and isolate the toggle logic.
const MenuItem = ({ menuTitle, subMenus = [] }) => {
const [isOpen, setIsopen] = useState(false);
const toggleDropDown = () => {
setIsopen((isOpen) => !isOpen);
};
return (
<li onClick={toggleDropDown}>
{menuTitle}
{isOpen && (
<ul>
{subMenus.map((item) => (
<li>
{item}
</li>
))}
</ul>
)}
</li>
);
};
Then use MenuItem in DropDownMenu
function DropDownMenu({ className }) {
return (
<nav>
<ul>
<MenuItem
menuTitle="item1"
subMenus={["sub-item1", "sub-item2", "sub-item-3"]}
/>
<MenuItem menuTitle="item3" subMenus={["sub-item1", "sub-item2"]} />
</ul>
</nav>
);
}
Code sandbox

How to close mobile menu in react when click on link?

I am making an app in react. I am facing issue as my mobile menu remains open even after clicking the navbar items. Does anyone know how it can be closed upon clicking on the navbar items?
class Navbar extends Component {
state = { clicked: false};
handleClick = () => {
this.setState({clicked:!this.state.clicked})
};
render () {
return (
<nav className={"NavbarItems"}>
<h1 className={"navbar-logo"}>React<i className={"fab fa-react"}></i></h1>
<div className={"menu-icon"} onClick={this.handleClick}>
<i className={this.state.clicked ? 'fas fa-times': 'fas fa-bars'}></i>
</div>
<ul className={this.state.clicked?'nav-menu active':'nav-menu'}>
{MenuItems.map((item, index) => {
return (
<div key={index}>
<li >
<Link className={item.cName} to={item.url}>
{item.title}
</Link>
</li>
</div>
)
})}
</ul>
<Button><Link className={"nav-button"} to="/contact">Contact Us</Link></Button>
</nav>
)
}
}
export default Navbar;
Could be like this:
<Link to={() => {
this.handleClick();
return '/contact'
}} >Contact Us</Link>

Add a class and remove class from another in ReactJS

I want to create a tab using ul and li and the tab will be shown the same content which is selected and should be show a button which is send me to the next tab and that will be active for indication which tab is showing to me
render() {
return (
<div>
<ul>
<li>Tab 1</li>
<li>Tab 2</li>
<li>Tab 3</li>
</ul>
<div id="tab-menu-1">Tab 1 Content <button>Move to Tab 2</button></div>
//in default it should show the tab 1 content only and When i Press this button on above Div it should be moved to next tab and the class name will be transfer to the tab 2 on <li>
<div id="tab-menu-2">Tab 2 Content <button>Move to Tab 2</button></div>
//in default it should show the tab 2 content only and When i Press this button on above Div it should be moved to next tab and the class name will be transfer to the tab 3 on <li>
<div id="tab-menu-3">Tab 3 Content <button>Move to Tab 2</button></div>
//in default it should show the tab 3 content only and When i Press this button on above Div it should be moved to next tab and the class name will be transfer to the tab 1 on <li>
</div>
);
}
You can save on the state of the component the activeIndex id, and render conditionally the className.
class Demo extends React.Component {
state = {
activeIdx: 0
};
render() {
return (
<div>
<ul>
<li><a href="#tab-menu-1" className={this.state.activeIdx === 0 ? 'active' : ''} data-index={0} onClick={this.handleChange}>Tab 1</a>
</li>
<li><a href="#tab-menu-2" className={this.state.activeIdx === 1 ? 'active' : ''} data-index={0} onClick={this.handleChange}>Tab 2</a></li>
<li><a href="#tab-menu-3" className={this.state.activeIdx === 2 ? 'active' : ''} data-index={0} onClick={this.handleChange}>Tab 3</a></li>
</ul>
<div id="tab-menu-1">Tab 1 Content <button>Move to Tab 2</button></div>
<div id="tab-menu-2">Tab 2 Content <button>Move to Tab 2</button></div>
<div id="tab-menu-3">Tab 3 Content <button>Move to Tab 2</button></div>
</div>
);
}
handleChange = (evnt) => {
this.setState({activeIdx: evnt.target.data.index});
}
}
A better approach would be to generate the list of items out of an array.
class Demo extends React.Component {
state = {
activeIdx: 0,
list: ["content 1", "content 2", "content 3"]
};
render() {
return (
<div>
<ul>
{this.state.list.map((item, index) => (
<li key={item}>
<a
href={`#tab-menu-${index + 1}`}
className={this.state.activeIdx === index && "active"}
data-index={index}
onClick={this.handleChange}
>
Tab {index + 1}
</a>
</li>
))}
</ul>
{this.state.list.map((item, index) => (
<div id={`tab-menu-${index + 1}`} key={item}>
{item}
<button>Move to Tab {index + 2}</button>
</div>
))}
</div>
);
}
handleChange = evnt => {
this.setState({ activeIdx: evnt.target.data.index });
};
}

Resources