I have submenu that is working for only when there is single item single LI but if I increase LI then its not working fine. How can I solve this?
My Code:-
import React, { useState } from "react";
import { Link } from "react-router-dom";
const Home = (props) => {
const [subMenuOpen, setSubMenuOpen] = useState(false);
return (
<>
<ul className="submenu">
<li>
<Link to="/">
<div
onClick={() => setSubMenuOpen(!subMenuOpen)}
>
<span>test</span>
</div>
</Link>
<ul class={`sub-menu ${subMenuOpen ? "is-open" : ""}`}>
<li className="menu-item">Sub-Item 1</li>
<li className="menu-item">Sub-Item 2</li>
<li className="menu-item">Sub-Item 3</li>
</ul>
</li>
<li>
<Link to="/">
<div
onClick={() => setSubMenuOpen(!subMenuOpen)}
>
<span>test</span>
</div>
</Link>
<ul class={`sub-menu ${subMenuOpen ? "is-open" : ""}`}>
<li className="menu-item">Sub-Item 1</li>
<li className="menu-item">Sub-Item 2</li>
<li className="menu-item">Sub-Item 3</li>
</ul>
</li>
</ul>
</>
);
};
export default Home;
.sub-menu { display: none;}
.sub-menu.is-open { display:block;}
ThankYou!
Because you have multiple submenus, you need to keep track of which one is open, and you can't do that with a single bool value.
If you will have only 1 level of child menu items, and at most one of them can be open, you can use a number: -1 -> no subitem is open, 0 -> the first item is open, etc. like this:
const [subMenuOpen, setSubMenuOpen] = useState(-1);
1st div: <div onClick={() => setSubMenuOpen(0)}>
2nd div: <div onClick={() => setSubMenuOpen(1)}>
(etc)
1st ul: <ul class={`sub-menu ${subMenuOpen === 0 ? "is-open" : ""}`}>
2nd ul: <ul class={`sub-menu ${subMenuOpen === 1 ? "is-open" : ""}`}>
(etc)
you probably also need to provision something that clears the menu, so clicking outside of the menu would run setSubMenuOpen(-1).
To get toggling behaviour, you can use a helper function like this:
helper function: const toggleMenu = (x) => setSubMenuOpen(subMenuOpen === x ? -1 : x)
1st div: <div onClick={() => toggleMenu(0)}>
2nd div: <div onClick={() => toggleMenu(1)}>
If you plan to have multiple levels of menu items, then a tree-like data structure will be more suitable, perhaps to both define the menu and to keep track of the open (sub)(sub)(sub)item.
try using "className" instead of "class"
JSX seems to be right
I think you have a problem in css
Related
I'm drawing a blank on how to toggle an active class on links for a header. I currently have the class applied, but it toggles the class for all links with the function. I need to be able to toggle the current class only the selected link:
const [isActive, setActive] = useState(false);
const toggleClass = () => {
setActive(!isActive);
};
<li>
<a className={isActive ? "current" : null} href="#home" onClick={toggleClass}>
Home
</a>
</li>
<li>
<a className={isActive ? "current" : null} href="#about" onClick={toggleClass}>
About
</a>
</li>
<li>
<a className={isActive ? "current" : null} href="#contact" onClick={toggleClass}>
Contact
</a>
</li>
You are using the same state for all your links. This wouldn't work, in less you change it a little bit. You can for example set isActive to the clicked link's hash. Like so:
const [isActive, setActive] = useState("");
const toggleClass = (e) => {
setActive(e.currentTarget.hash);
};
<li>
<a className={isActive == "#home" ? "current" : ""} href="#home" onClick={toggleClass}>
Home
</a>
</li>
<li>
<a className={isActive == "#about" ? "current" : ""} href="#about" onClick={toggleClass}>
About
</a>
</li>
<li>
<a
className={isActive == "#contact" ? "current" : ""}
href="#contact"
onClick={toggleClass}
>
Contact
</a>
</li>
function Navbar() {
const [shownavcontents, setShownavcontents] = useState(false)
if(shownavcontents){
document.getElementsByClassName("navbardivofmobiledevice").style.display = "none";
}else{
document.getElementsByClassName("navbardivofmobiledevice").style.display = "block";
}
return (
<>
<div className="top">
<Searchbar />
<AiOutlineMenu size={20} className="outlinemenu" onClick={() => {setShownavcontents(true)}} />
</div>
<div className="navbardivofmobiledevice">
<ul>
<li>
Home
</li>
<li>
Members
</li>
<li>
All Posts
</li>
<li>
My Posts
</li>
</ul>
</div>
</>
);
}
As you see I am trying to make responsive navbar, in this case, for mobile devices. I've faced one problem. I've made button on top of navbar and some navbar contents which I want to display only whenever user will click this button and vice versa. So I tried using hooks to check if the user clicked the button which works perfectly, only thing that doesn't works is this if else statements it seems like document.getElementsByClassName("navbardivofmobiledevice").style.display = "none"; doesn't have an effect here. So my question is what is the alternative of this? What can I do here?
This is imperative code:
document.getElementsByClassName("navbardivofmobiledevice").style.display = "none";
With React, you rarely get references to DOM elements and update them manually, and in any case, you do it using Refs, not with the getElement... or querySelector... methods). Instead, you write declarative code and let React take care of the DOM updates for you.
In this case, simply add or remove a hidden attribute or CSS class that has display: none from your JSX:
function Navbar() {
const [shownavcontents, setShownavcontents] = useState(false);
return (
<>
<div className="top">
<Searchbar />
<AiOutlineMenu size={20} className="outlinemenu" onClick={() => {setShownavcontents(true)}} />
</div>
<div className="navbardivofmobiledevice" hidden={ !shownavcontents }>
<ul>
<li>
Home
</li>
<li>
Members
</li>
<li>
All Posts
</li>
<li>
My Posts
</li>
</ul>
</div>
</>
);
}
If you prefer to use a class, assuming you have defined a CSS class .isHidden { display: none; } you would use this line instead:
<div className={ `navbardivofmobiledevice${ shownavcontents ? '' : ' isHidden' }` }>
Regarding what some comments are mentioning about rendering that conditionally like so:
function Navbar() {
const [shownavcontents, setShownavcontents] = useState(false);
return (
<>
<div className="top">
<Searchbar />
<AiOutlineMenu size={20} className="outlinemenu" onClick={() => {setShownavcontents(true)}} />
</div>
{ shownavcontents && (
<div className="navbardivofmobiledevice">
<ul>
<li>
Home
</li>
<li>
Members
</li>
<li>
All Posts
</li>
<li>
My Posts
</li>
</ul>
</div>
) }
</>
);
}
I would avoid that, as hiding your main navigation from Google and other search engines will harm your SEO. You need to hide it visually but still have it in the DOM.
If you want to do better than that, add all the appropriate ARIA attributes and logic for a navigation menu with nested submenus, as explained here:
https://www.w3.org/WAI/ARIA/apg/example-index/menubar/menubar-navigation
I have header component, where I want to toggle className between all the elements of menu (if one of the elements of menu is active and user is clicking to another element - this element become active and all others no). I have a code like this
import React, { useState } from 'react';
import './header.scss';
export const Header = ({ favoriteCount }) => {
const [activeIndex, setActiveIndex] = useState(0);
function toggleClass(index) {
setActiveIndex(index);
}
return (
<header className="header">
<div className="container header-container">
<ul className="header-menu">
<li>
<a
className={
activeIndex === 0
? 'header-menu__link active'
: 'header-menu__link'
}
onClick={() => {
toggleClass(0);
}}
href="##"
>
Characters
</a>
</li>
<li>
<a
className={
activeIndex === 1
? 'header-menu__link active'
: 'header-menu__link'
}
onClick={() => {
toggleClass(0);
}}
href="##"
>
Favorites
</a>
</li>
</ul>
<div className="header-favorite-count">
<i className="far fa-heart"></i>
{favoriteCount}
</div>
</div>
</header>
);
};
and styles to visualise toggling classes
&-menu__link {
color: lightgray;
}
.active {
color: #fff;
}
This approach is working but looks creepy. Maybe somebody knows how to optimize it?
I wouldn't use the index, I'd use the text of the item. I'd also include that text in the href so that there's an indication of what the anchor leads to. To avoid repeated code, you might put the menu items in a reusable array, something like this:
const menuItems = [
"Characters",
"Favorites",
];
export const Header = ({ favoriteCount }) => {
const [activeItem, setActiveItem] = useState("");
const setActiveItem = useCallback((event) => {
setActiveItem(event.currentTarget.href.substring(2));
}, []);
const list = menuItems.map(item =>
<li key={item}>
<a
className={`header-menu__link ${item === activeItem ? "active" : ""}`}
onClick={setActiveItem}
href={"##" + item}>
{item}
</a>
</li>
);
return (
<header className="header">
<div className="container header-container">
<ul className="header-menu">
{list}}
</ul>
<div className="header-favorite-count">
<i className="far fa-heart"></i>
{favoriteCount}
</div>
</div>
</header>
);
};
I have this function component in react hooks.
and need when I click in any element in the list to change className to 'active'
and remove the className from the other elements
const SideBar = () =>{
const [activeClass, setClass] = useState('');
return (
<div className="sidebar">
<ul>
<li> <Link onClick={() => setClass('active')} className={activeClass} to="/"> Home </Link> </li>
<li> <Link to="/about"> About </Link> </li>
<li> <Link to="/form"> Form </Link> </li>
</ul>
</div>
)
}
I hope you must be using react-router-dom so you can use rect-router-dom api to determine the active item
import {
useLocation
} from "react-router-dom";
const SideBar = () =>{
const [activeClass, setClass] = useState('');
let { pathname } = useLocation();
return (
<div className="sidebar">
<ul>
<li> <Link className={pathname === '/' ? 'active' : ''} to="/"> Home </Link> </li>
<li> <Link to="/about" className={pathname === '/about' ? 'active' : ''}> About </Link> </li>
<li> <Link to="/form" className={pathname === '/form' ? 'active' : ''}> Form </Link> </li>
</ul>
</div>
)
}
import { NavLink } from 'react-router-dom';
const SideBar = () => {
const isActive = path => (match, location) => {
if (!match) {
return false;
}
return match.url === path;
};
return (
<div className="sidebar">
<ul>
<li> <NavLink isActive={isActive('/')} activeClassName="active" to="/"> Home </NavLink> </li>
<li> <NavLink to="/about" isActive={isActive('/about')} activeClassName="active"> About </NavLink> </li>
<li> <NavLink to="/form" isActive={isActive('/form')} activeClassName="active" > Form </NavLink> </li>
</ul>
</div>
)
}
So instead of maintaining only active/not active, you need to maintain which index in your links is active.
You can do this by this way:
const SideBar = () =>{
//setting the initial index as 0 to activate `/` route by default.
const [activeLinkIndex, setActiveLinkIndex] = useState(0);
return (
<div className="sidebar">
<ul>
<li> <Link onClick={() => setActiveLinkIndex(0)} className={activeLinkIndex === 0 ? 'active' : ''} to="/"> Home </Link> </li>
<li> <Link onClick={() => setActiveLinkIndex(1)} className={activeLinkIndex === 1 ? 'active' : ''} to="/about"> About </Link> </li>
<li> <Link onClick={() => setActiveLinkIndex(2)} className={activeLinkIndex === 2 ? 'active' : ''} to="/form"> Form </Link> </li>
</ul>
</div>
)
}
I am trying to pass the data from Child > parent > child
Child
{this.state.data.map((item, index) => (
<li className='card' key={index}>
<span>{item.continent} </span>
<ul className="accordion-body">
{item.regions.map((c, i) => (
<li key={i} onClick={this.props.toggleContent}>
<img src={c.flag}/> {c.country}
</li>
))}
</ul>
</li>
))}
Basically I need to get selected country and some other values from the child and pass to parent
and pass those values to another child.
My Parent
<div className="modal-header">
<h2>Choose your {title}</h2>
<a href="#" className="model-close" data-dismiss="modal" aria-label="Close"><i
className="fa fa-times-circle"></i></a>
</div>
<div className="modal-body">
{showCountry && <CountryList toggleContent={this.toggleContent}/>}
{showLanguages && <RegionList country={country} flag={flag} languages={languages}
toggleContent={this.toggleContentRegion.bind(this)}/>}
</div>
and
toggleContent = () => {
this.setState({
...this.state,
showCountry: !this.state.showCountry,
showLanguages: !this.state.showLanguages,
title: 'language',
country: 'country',
languages: [],
flag: 'flag'
});
}
I tried to use below
<li key={i} onClick={this.props.toggleContent(c.country)}>
<img src={c.flag}/> {c.country}
</li>
and access it from parent
toggleContent = (country) => {
this.setState({
...this.state,
showCountry: !this.state.showCountry,
showLanguages: !this.state.showLanguages,
title: 'language',
country: country,
languages: [],
flag: 'flag'
});
}
But, my components not working correctly When do that and always shows the 2 child component.
Are there any proper way to pass the data to parent from a json array?
So the best way I would handle this would be to make the import your parent class components into the child , place it at the very top of the child JSX but hide it by default. The modal would be fixed, background covering the full page and at a z-index higher than the rest of the child components, so that way only the modal contents are the only accessible things . You would have a state that "toggles on" the modal for each click of the item list and a close button that toggles it off. You would update the modal content and toggle it on for every click
In terms of the second child, you can just show it on the same modal
Found a way to do this :)
render() {
var toggleContent = this.props.toggleContent;
return (
<div className="modal-wrapper">
<ul className="country-list">
{this.state.data.map((item, index) => (
<li className='card' key={index}>
<span>{item.continent} </span>
<ul className="accordion-body">
{item.regions.map((c, i) => (
**<li key={i} onClick={() => toggleContent(c.country,c.flag, c.languages, c.region)} >**
<img src={c.flag}/> {c.country}
</li>
))}
</ul>
</li>
))}
</ul>
</div>
);
}
Changed below line
onClick={() => toggleContent(c.country,c.flag, c.languages, c.region)