Toggle active class for only currently selected link in React hooks - javascript

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>

Related

Output ul tags conditionally in JSX?

Setup: Headless Wordpress CMS, GatsbyJs, GraphQL
I think this is a JavaScript ES6 problem rather than a GatsbyJS problem.
I've got a query that outputs an author, title and url.
The data is queried from an ACF repeater field, so there could be one, none or many items.
Here's the code:
{citationPath &&
citationPath.map(item => {
return (
<li key={item.url}>
<a href={item.url} target="_blank" rel="noopener noreferrer">
{item.title}
</a>
<p>Author: {item.author}</p>
</li>
)
})}
THIS WORKS, no problem.
I now want to put a <ul> tag before the code, and a </ul> tag after the code ONLY if some citationPath items
are returned.
OPTION #1
I could do this:
{citationPath &&
citationPath.map(item => {
return (
<ul>
<li key={item.url}>
<a href={item.url} target="_blank" rel="noopener noreferrer">
{item.title}
</a>
<p>Author: {item.author}</p>
</li>
</ul>
)
})}
but then I get a <ul></ul> around each list item.
OPTION #2
I could do this:
<ul>
{citationPath &&
citationPath.map(item => {
return (
<li key={item.url}>
<a href={item.url} target="_blank" rel="noopener noreferrer">
{item.title}
</a>
<p>Author: {item.author}</p>
</li>
)
})}
</ul>
but then I get <ul></ul> when NO citationPath items are returned.
OPTION #3
When I try this:
{citationPath && parse("<ul>")}
{citationPath &&
citationPath.map(item => {
return (
<li key={item.url}>
<a href={item.url} target="_blank" rel="noopener noreferrer">
{item.title}
</a>
<p>Author: {item.author}</p>
</li>
)
})}
{citationPath && parse("</ul>")}
No HTML is printed out on those posts which do not return citationPath data. However, on those that do I get the following HTML output:
<ul></ul>
<li>
<a href=[my url] target="_blank" rel="noopener noreferrer">
<li>
[more list items ...]
So it seems that {citationPath && parse("<ul>")} and {citationPath && parse("</ul>")} are being executed before:
{citationPath &&
citationPath.map(item => {
return (
<li key={item.url}>
<a href={item.url} target="_blank" rel="noopener noreferrer">
{item.title}
</a>
<p>Author: {item.author}</p>
</li>
)
})}
Note: the reason I don't simply go for OPTION #2 is that I don't want screen readers bumping into empty <ul></ul> tags.
Does any one have any ideas on how to solve this?
You just need to put the condition above and make sure to check the condition of length.
{citationPath && citationPath.length > 0 && (
<ul>
{citationPath.map(item => {
return (
<li key={item.url}>
<a href={item.url} target="_blank" rel="noopener noreferrer">
{item.title}
</a>
<p>Author: {item.author}</p>
</li>
);
})}
</ul>
)}
Since this checks both the length and the truethy value of citationPath, this is the solution you're looking for and as an industry professional, I use this code in production.

How to optimize toggling className between different menu items by React?

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>
);
};

Submenu is not working for multiple items in ReactJs?

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

use React Hooks to change the elements class Name and siblings

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>
)
}

Converting JavaScript to work with React component

New to React and am trying to write my script in React but not sure how to do it.I've tried using states but that has just clouded me with more confusion. The below code is a sample of something I might commonly create.
Here's my script:
const hamburger = document.querySelector(".hamburger");
hamburger.addEventListener("click", function () {
hamburger.classList.toggle("is-active");
document.querySelector(".navigation").classList.toggle("slide-in");
document.querySelector("body").classList.toggle("menu-active");
document.querySelector(".shell-ui-main").classList.toggle("menu-overlay");
});
Here's a basic react component for a navbar:
import React from 'react';
export default class NavComponent extends React.Component {
render() {
return (
<div className="container">
<button className="hamburger hamburger--squeeze" type="button">
<span className="hamburger-box">
<span className="hamburger-inner"></span>
</span>
</button>
<a className="logo-link" href="/">
<img width="94" height="31" src="/img/logos/logo.png" srcSet="/img/logos/logo.png 1x, /img/logos/logo#2x.png 2x, /img/logos/logo#3x.png 3x"
alt="logo" className="logo" />
</a>
<nav className="navigation">
<ul className="nav">
<li className="single-item">
Home
</li>
<li className="single-item">
Item 2
</li>
<li className="single-item">
Item 3
</li>
<li className="single-item">
Item 4
</li>
</ul>
</nav>
</div>
);
}
}
Here's the solution in case anyone is interested. Additionally I pass the props down to the component and control from there instead of the individual const.
import React from 'react';
const HamburgerToggle = (props) => (
<button className={"hamburger hamburger--squeeze" + (props.active ? " is-active" : "")} onClick={props.clickHandler} type="button">
<span className="hamburger-box">
<span className="hamburger-inner"></span>
</span>
</button>
);
const Logo = () => (
<a className="logo-link" href="/">
<img width="94" height="31" src="/img/logos/logo.png" srcSet="/img/logos/logo.png 1x, /img/logos/logo#2x.png 2x, /img/logos/logo#3x.png 3x" alt="Logo" className="logo" />
</a>
);
const Navigation = (props) => (
<nav className={"navigation" + (props.active ? " slide-in" : "")}>
<ul className="nav">
<li className="single-item">
Home
</li>
<li className="single-item">
intem 2
</li>
<li className="single-item">
item 3
</li>
<li className="single-item">
item 4
</li>
</ul>
</nav>
);
export default class NavComponent extends React.Component {
state = {active: false};
handleClick(e){
this.setState({active: !this.state.active});
console.log(this.state.active);
}
render() {
return (
<div className="container">
<HamburgerToggle active={this.state.active} clickHandler={this.handleClick.bind(this)} />
<Logo />
<Navigation active={this.state.active} clickHandler={this.handleClick.bind(this)} />
</div>
);
}
}

Categories

Resources