I have two files, header.js and toggle.js. I'm trying to change the class name of one of the elements in the parent component.
How can I add the active class to my <ul className="nav-wrapper"> when the button is clicked?
Here's my code:
header.js
const Header = ({ siteTitle, menuLinks, }) => (
<header className="site-header">
<div className="site-header-wrapper wrapper">
<div className="site-header-logo">
<Link to="/" className="brand">Brand Logo</Link>
</div>
<div className="site-header-right">
<nav className="nav">
<Toggle />
<ul className="nav-wrapper">
{menuLinks.map(link => (
<li
key={link.name}
className="nav-item"
>
<Link to={link.link}>
{link.name}
</Link>
</li>
))}
</ul>
</nav>
</div>
</div>
</header>
)
Header.propTypes = {
siteTitle: PropTypes.string,
}
Header.defaultProps = {
siteTitle: ``,
}
export default Header
toggle.js
export default function Toggle() {
const [isActive, setActive] = useState("false");
const handleToggle = () => {
setActive(!isActive);
};
return (
<button
className="nav-toggle"
className={isActive ? "app" : null}
onClick={handleToggle}
aria-expanded="false"
type="button">
MENU
</button>
);
}
Thanks for any help!
Easiest way will be refactoring your code to have the useState in the Header component and then passing that state to your Toggle component as props. This will make the isActive prop available in the header so you can do something like this:
const Header = ({ siteTitle, menuLinks, }) => {
const [isActiveNav, setActiveNav] = useState(false);
const activeClass = isActiveNav ? 'active' : ''
return (
// All your jsx
<Toggle isActive={isActiveNav} setActive={setActiveNav} />
<ul className={`nav-wrapper ${activeClass}`}>
{// More JSX}
</ul>
)
Now in your Toggle component
export default function Toggle({ isActive, setActive }) {
const handleToggle = () => {
setActive(!isActive);
};
return (
<button
className="nav-toggle"
className={isActive ? "app" : ''}
onClick={handleToggle}
aria-expanded={isActive}
type="button">
MENU
</button>
);
}
I did some changes in your code:
Don't use null as a className, use an empty string instead
The useState value should be false not "false".
You can pass the isActive value to the aria-expanded prop.
This will do the trick, and is the easiest approach.
Related
I have created a navbar with light and dark mode, everything working well. Now I want to update it to multi-page with react-router and with a layout. If I gave to the path name to the url is working well. The problem is the url shows me the page but the navbar doesn't navigate to the url and doesn't toggle the dark/light mode.
import React, { useRef, useEffect } from "react";
import "./header.css";
import { Link, useMatch, useResolvedPath } from "react-router-dom"
const nav__links = [
{
to: "home",
display: "Home",
},
{
to: "service",
display: "Service",
},
{
to: "preise",
display: "Preise",
},
{
to: "kontakt",
display: "Kontakt",
},
];
const Header = ({ theme, toggleTheme }) => {
const headerRef = useRef(null);
const menuRef = useRef(null);
const headerFunc = () => {
if (
document.body.scrollTop > 80 ||
document.documentElement.scrollTop > 80
) {
headerRef.current.classList.add("header__shrink");
} else {
headerRef.current.classList.remove("header__shrink");
}
};
useEffect(() => {
window.addEventListener("scroll", headerFunc);
return () => window.removeEventListener("scroll", headerFunc);
}, []);
const handleClick = (e) => {
e.preventDefault();
const targetAttr = e.target.getAttribute("to");
const location = document.querySelector(targetAttr).offsetTop;
window.scrollTo({
left: 0,
top: location - 80,
});
};
const toggleMenu = () => menuRef.current.classList.toggle("menu__active");
function CustomLink({ to, children, ...props }) {
const resolvedPath = useResolvedPath(to)
const isActive = useMatch({ path: resolvedPath.pathname, end: true })
return (
<li className={isActive ? "active" : ""}>
<Link to={to} {...props}>
{children}
</Link>
</li>
);
};
return (
<header className="header" ref={headerRef}>
<div className="container">
<div className="nav__wrapper">
<div className="logo" to="home">
<Link to="home"><h2>Q-Tech</h2></Link>
</div>
{/* ========= navigation ============= */}
<div className="navigation" ref={menuRef} onClick={toggleMenu}>
<ul className="menu">
{nav__links.map((item, index) => (
<li className="menu__item" key={index}>
<CustomLink
to={item.to}
onClick={handleClick}
className="menu__link"
>
{item.display}
</CustomLink>
</li>
))}
</ul>
</div>
{/* ============ light mode ============= */}
<div className="light__mode">
<span onClick={toggleTheme}>
{theme === "light-theme" ? (
<span>
<i class="ri-moon-line"></i>Dark
</span>
) : (
<span>
<i class="ri-sun-line"></i> Light
</span>
)}
</span>
</div>
<span className="mobile__menu" onClick={toggleMenu}>
<i class="ri-menu-line"></i>
</span>
</div>
</div>
</header>
);
};
export default Header;
Why navigation is not working?
Look at the navigation link definition - CustomLink:
<CustomLink
onClick={handleClick} // <--
...
>
You have an onClick handler on the link. Inside this event handler you call e.preventDefault(), which prevents the navigaton behaviour.
Why toggle light/dark theme it not working?
The toggle button here looks fine. So it is probably a problem in the code outside Header component. Try to debug toggleTheme functionality.
I have multiple pages in my React app where I want to have this functionality. So in a certain React page I add CopyToClipboard and the children TabIcon:
<CopyToClipboard>
<TabIcon type="link" title="Test one" />
<TabIcon type="embed" title="Test two" />
</CopyToClipboard>
In another page I maybe have only:
<CopyToClipboard>
<TabIcon type="link" title="Test one" />
</CopyToClipboard>
The TabIcon component:
const TabIcon = ({ title, type, onClick }: Props) => {
const theme = useTheme();
const styles = Styles({ theme });
return (
<li>
<ButtonBase type="button" onClick={onClick} title={title} disableRipple>
<span css={styles.icon}>{type === 'embed' ? <EmbedIcon /> : <LinkIcon />}</span>
</ButtonBase>
</li>
);
};
The CopyToClipboard component:
const CopyToClipboard = ({ children }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const [isCopied, setIsCopied] = useState(false);
const stringToCopy =
type === 'link'
? `https://www.mysite${path}`
: `<iframe>// iframe code etc here</iframe>`;
const handleClick = () => {
setIsOpen((current) => !current);
};
return (
<div>
<ul>
{children.map((item) => (
<TabIcon type={item.type} onClick={handleClick} />
))}
</ul>
{isOpen && (
<div>
<p>{stringToCopy}</p>
<ButtonBase type="button" onClick={handleCopyClick} disableRipple>
<span>{isCopied ? `${suffix} copied` : `Copy ${suffix}`}</span>
<span>{isCopied ? <CheckIcon /> : <CopyIcon />}</span>
</ButtonBase>
</div>
)}
</div>
);
};
export default CopyToClipboard;
So what I like to achieve is the following:
When clicking a button it toggles/shows the correct div with its associated content. When clicking the same button again it hides the div. When clicking the other button it shows the associated content of that button. When you click that button again it hides the div.
How to achieve this within my example?
You need a single state (text). If the text has a truthy value (actual text in this case), the div is displayed.
When you set the text, the handleClick compares the prevText and the new text (txt), and if they are equal it sets null as the value of text, which hides the div. If they are not equal, the new text is set, and the div is displayed.
const { useState } = React;
const Demo = () => {
const [text, setText] = useState(null);
const handleClick = txt => {
setText(prevText => prevText === txt ? null : txt);
};
return (
<div>
<ul>
<li>
<button onClick={() => handleClick('Button 1 text')}>
Button 1
</button>
</li>
<li>
<button onClick={() => handleClick('Button 2 text')}>
Button 2
</button>
</li>
</ul>
{text && (
<div>
<p>{text}</p>
</div>
)}
</div>
);
}
ReactDOM.createRoot(root)
.render(<Demo />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>
I have a list of buttons. I want to make one of them to have a active state and when that one has every other shouldn't have that state. So styling is applied only to the active one. I almost did it, i mean it change styling when i press on each other but i have no idea how to make it to deactivate a button when i click on another one.
Bellow is my code. props.planets is array of object {id: 'name', title: 'name'}
MenuList it takes a props.planets and generate list of MenuButton components
const MenuList = (props) => {
return (
<ul className={classes.planet_list}>
{props.planets.map((planet) => (
<MenuButton key={planet.id} title={planet.title} />
))}
</ul>
);
};
MenuButton is my button component i want it to have module classes: modulebutton as a main styling, class based on title, and an active class if button is active
const MenuButton = (props) => {
const [active, setActive] = useState();
const clickHandler = () => {
setActive({ active: props.title });
};
return (
<li>
<div
className={`${classes.menubutton} ${classes[props.title]} ${
active && classes.active
}`}
onClick={clickHandler}
>
{props.title}
</div>
</li>
);
};
One of the easiest solution for an active state on button. For this purpose you need to have an active state. I hope this would help you!
const {useState,Fragment} = React;
const App = () => {
const [active, setActive] = useState("");
const handleClick = (event) => {
setActive(event.target.id);
}
return (
<Fragment>
<button
key={1}
className={active === "1" ? "active" : undefined}
id={"1"}
onClick={handleClick}
>
Solution
</button>
<button
key={2}
className={active === "2" ? "active" : undefined}
id={"2"}
onClick={handleClick}
>
By
</button>
<button
key={3}
className={active === "3" ? "active" : undefined}
id={"3"}
onClick={handleClick}
>
Jamal
</button>
</Fragment>
);
}
ReactDOM.render(
<App/>,
document.getElementById("react")
);
.active{
background-color:red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You need to have the active state in the parent not in the child component
MenuList.js
const MenuList = (props) => {
const [active, setActive] = useState();
function activeButton(value){
setActive(value)
}
return (
<ul className={classes.planet_list}>
{props.planets.map((planet) => (
<MenuButton onChange={activeButton} disabled={active} key={planet.id} title={planet.title} />
))}
</ul>
);
};
MenuButton.js
const MenuButton = (props) => {
const clickHandler = (keyID) => {
props.onChange(keyID)
};
return (
<li>
<div
className={props.active === props.key ? "activeClass" : "inactiveClass"}
onClick={() => clickHandler(props.key)}
>
{props.title}
</div>
</li>
);
};
That should be it. May need a useEffect but let me know of any bugs
I am trying to click on one card of a dynamically created list using map(). I want to click on one card from the array and add a class to it, while at the same time deselecting the other card that was previously clicked. How can I accomplish this? This is what I have so far:
const CardList = () => {
return (
<div className='card-list'>
{CardData.map(({ id, ...otherData }) => (
<Card key={id} {...otherData} />
))}
</div>
);
};
export default CardList;
const Card = ({
headline,
time,
views,
thumbImg,
trainerImg,
workouts,
id
}) => {
const [isSelected, setIsSelected] = useState(false);
const [clickId, setClickId] = useState('');
function handleClick(id) {
setIsSelected(!isSelected);
setClickId(id);
}
return (
<div
className={`card ${isSelected && clickId === id ? 'clicked' : ''}`}
onClick={() => handleClick(id)}
>
<div className='thumbnail-div'>
<img className='thumbnail-img' src={thumbImg} alt='video' />
{workouts ? (
<div className='workout-overlay'>
<p>{workouts}</p>
<p className='workouts'>workouts</p>
</div>
) : null}
</div>
<div className='card-info'>
<div className='card-headline'>
<p>{headline}</p>
<img src={trainerImg} alt='trainer' />
</div>
{time && views ? (
<div className='trainer-data'>
<span>
<i className='glyphicon glyphicon-time'></i>
{time}
</span>
<span>
<i className='glyphicon glyphicon-eye-open'></i>
{views}
</span>
</div>
) : null}
</div>
</div>
);
};
export default Card;
The parent component should control what card is clicked. Add className property to card component:
const Card = ({
//...
className,
onClick
}) => {
//...
return (
<div
className={`card ${className}`}
onClick={() => onClick(id)}
>...</div>
)
}
In parent component pass the className 'clicked' and add the onClick callback to set the selected card:
const CardList = () => {
const [isSelected, setIsSelected] = useState(null);
const handleClick = (id) => {
setIsSelected(id);
}
return (
<div className='card-list'>
{CardData.map(({ id, ...otherData }) => (
<Card key={id} className={isSelected===id && 'clicked'} onClick ={handleClick} {...otherData} />
))}
</div>
);
};
You can do something like this.
First you don't have to set state to each card. Instead Lift state Up.
You define which card is selected in parent so you can pass that to children and add classes if current selected is matching that children.
const CardList = () => {
const [isSelected, setIsSelected] = useState();
const handleCardClick = (id) => {
setIsSelected(id);
}
return (
<div className='card-list'>
{CardData.map(({ id, ...otherData }) => (
<Card key={id} {...otherData} handleClick={handleCardClick} isSelected={isSelected}/>
))}
</div>
);
};
export default CardList;
const Card = ({
headline,
time,
views,
thumbImg,
trainerImg,
workouts,
id,
isSelected,
handleClick
}) => {
return (
<div
className={`card ${isSelected === id ? 'clicked' : ''}`}
onClick={() => handleClick(id)}
>
<div className='thumbnail-div'>
<img className='thumbnail-img' src={thumbImg} alt='video' />
{workouts ? (
<div className='workout-overlay'>
<p>{workouts}</p>
<p className='workouts'>workouts</p>
</div>
) : null}
</div>
<div className='card-info'>
<div className='card-headline'>
<p>{headline}</p>
<img src={trainerImg} alt='trainer' />
</div>
{time && views ? (
<div className='trainer-data'>
<span>
<i className='glyphicon glyphicon-time'></i>
{time}
</span>
<span>
<i className='glyphicon glyphicon-eye-open'></i>
{views}
</span>
</div>
) : null}
</div>
</div>
);
};
export default Card;
I have a header component as a function component. I want show a popup when logo text is clicked. After for a time it should close automatically. I use hooks for state of popup. But set state function doesn't work in setTimeout function. How can fix this?
import Link from 'next/link'
import style from './header.module.css'
const Header = () => {
const [popupOpen, setPopupOpen] = React.useState(false)
return (
<header className={style.header}>
<nav className={style.nav}>
<div
className={style.popupContainer}
onClick={() => {
setPopupOpen(!popupOpen)
console.log(popupOpen)
setTimeout(() => {
console.log(popupOpen)
setPopupOpen(!popupOpen)
console.log(popupOpen)
}, 1000)
}}
>
<span className={style.logo}>Logo</span>
<span
className={`${style.popupText} ${
popupOpen ? style.show : style.hide
}`}
>
Popup Text
</span>
</div>
<ul className={style.ul}>
<li>
<Link href='/'>
<a>.home</a>
</Link>
</li>
<li>
<Link href='/contact'>
<a>.contact</a>
</Link>
</li>
</ul>
</nav>
</header>
)
}
export default Header
Console log:
Let me suggests, this is the same question as:
React - useState - why setTimeout function does not have latest state value?
const _onClick = () => {
setPopupOpen(!popupOpen);
setTimeout(() => {
setPopupOpen(popupOpen => !popupOpen)
}, 2000);
};
Its happening because setPopupOpen is asynchronous. So by the time setPopupOpen(!popupOpen) is called it has same value as onClick first setPopupOpen(!popupOpen) so eventually when it called both setPopup doing same state update i.e both updating as false. Better way is to usesetPopupOpen callback function to update the value. I added this code.
import { useState } from "react";
import Link from "next/link";
import style from "./style.module.css";
const Header = () => {
const [popupOpen, setPopupOpen] = useState(false);
const toggle = () => {
setPopupOpen((prev) => !prev);
};
const onClick = () => {
setPopupOpen(!popupOpen);
setTimeout(() => {
toggle();
}, 1000);
};
return (
<header className={style.header}>
<nav className={style.nav}>
<div className={style.popupContainer} onClick={onClick}>
<span className={style.logo}>Logo</span>
{popupOpen && (
<span
className={`${style.popupText} ${
popupOpen ? style.show : style.hide
}`}
>
Popup Text
</span>
)}
</div>
<ul className={style.ul}>
<li>
<Link href="/">
<a>.home</a>
</Link>
</li>
<li>
<Link href="/contact">
<a>.contact</a>
</Link>
</li>
</ul>
</nav>
</header>
);
};
export default function IndexPage() {
return (
<div>
<Header />
</div>
);
}
Here is the demo: https://codesandbox.io/s/pedantic-haibt-iqecz?file=/pages/index.js:0-1212