Only one active button in React - reactjs

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

Related

the navbar with reacte route and darkmode doesnt work together

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.

How to create a tabs-like menu but then slightly different in React app

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>

How can I toggle accordion panels individually using React Hooks?

Currently, all accordion panels are being toggled simultaneously. I've tried passing in the index to the click handler, but no luck. How do I compare the current index with the current setActive variable to open and close accordion panels individually? The data I'm working with in production do not have unique ids which is why I'm using index. Thanks for any suggestions!
demo: https://codesandbox.io/s/react-accordion-using-react-hooks-forked-fup4w?file=/components/Accordion.js:141-1323
const Accordion = (props) => {
const [setActive, setActiveState] = useState(0);
const [setHeight, setHeightState] = useState("0px");
const [setRotate, setRotateState] = useState("accordion__icon");
const content = useRef(null);
const toggleAccordion = (index) => {
setActiveState(setActive === index ? "active" : "");
setHeightState(
setActive === "active" ? "0px" : `${content.current.scrollHeight}px`
);
setRotateState(
setActive === "active" ? "accordion__icon" : "accordion__icon rotate"
);
}
return (
<div>
{data.map((item, index) => (
<div key={index} className="accordion__section">
<button
className={`accordion ${setActive}`}
onClick={() => toggleAccordion(index)}
>
<p className="accordion__title">{item.title}</p>
<Chevron className={`${setRotate}`} width={10} fill={"#777"} />
</button>
<div
ref={content}
style={{ maxHeight: `${setHeight}` }}
className="accordion__content"
>
<div>{item.content}</div>
</div>
</div>
))}
</div>
);
};
The problem in your code is that you are generating a general setActive state that is then passed to all your item in your map function. You have to change your state management in order to be able to find for each of the item if they are active or not.
I'd rework a bit your component:
const Accordion = (props) => {
const [activeIndex, setActiveIndex] = useState(0);
const content = useRef(null);
return (
<div>
{data.map((item, index) => {
const isActive = index === activeIndex
return (
<div key={index} className="accordion__section">
<button
className={`accordion ${isActive ? "active" : ""}`}
onClick={() => setActiveIndex(index)}>
<p className="accordion__title">{item.title}</p>
<Chevron className={`${isActive ? "accordion__icon" : "accordion__icon rotate" }`} width={10} fill={"#777"} />
</button>
<div
ref={content}
style={{ maxHeight: `${isActive ? "0px" : `${content.current.scrollHeight}px`}` }}
className="accordion__content">
<div>{item.content}</div>
</div>
</div>
)})}
</div>
);
};
The idea is that for each item in loop you find which on is active and then assign the classes / styles according to this. Could be even more refactored but I let you clean up now that the idea should be there :)

React <details> - have only one open at a time

I have a component with several elements. I'm trying to figure out how to update the code with hooks so that only one element will be open at a time - when a element is open, the other's should be closed. This is the code:
const HowItWorks = ({ content, libraries }) => {
const Html2React = libraries.html2react.Component;
return (
<HowItWorksContainer>
{content.fields.map((tab, i) => {
const [open, setOpen] = useState(false);
const onToggle = () => {
setOpen(!open);
};
return (
<details
key={i}
onToggle={onToggle}
className={`tab ${open ? "open" : "closed"}`}
>
<summary className="tab__heading">
<div className="wrapper">
<p>{tab.heading}</p>
{open ? (
<i className="icon kap-arrow-minus" />
) : (
<i className="icon kap-arrow-plus" />
)}
</div>
</summary>
<div className="tab__content">
<Html2React html={tab.content} />
</div>
</details>
);
})}
</HowItWorksContainer>
);
};
Instead of having the open state be a boolean, make it be the ID of the element that is open. Then you can have a function that returns if the element is open by comparing the state with the ID.
const HowItWorks = ({ content, libraries }) => {
const [open, setOpen] = useState(0); //Use the element ID to check which one is open
const onToggle = (id) => {
setOpen(id);
};
const isOpen = (id) => {
return id === open ? "open" : "closed";
}
const Html2React = libraries.html2react.Component;
return (
<HowItWorksContainer>
{content.fields.map((tab, i) => {
return (
<details
key={i}
onToggle={onToggle}
className={`tab ${isOpen(i)}`}
>
<summary className="tab__heading">
<div className="wrapper">
<p>{tab.heading}</p>
{!!isOpen(i) ? (
<i className="icon kap-arrow-minus" />
) : (
<i className="icon kap-arrow-plus" />
)}
</div>
</summary>
<div className="tab__content">
<Html2React html={tab.content} />
</div>
</details>
);
})}
</HowItWorksContainer>
);
};

Passing class name down from one component to another in React

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.

Resources