I just started learning React and I'm building a website in Next.js and I'm stuck on the basics.
I'm a web designer and CSS developer, I've been using jQuery for 10 years mainly for toggling classes on elements on clicks, hover, etc. Now I want to do the same in React, but it's not as straightforward as jQuery and I just can't wrap my head around it 'cause it's a bit too logical for me.
Here is an example: toggle open only one accordion panel at a time:
$(".panel").on("click", function() {
$(".panel").not($(this)).removeClass("open")
$(this).toggleClass("open")
})
.accordion {
max-width: 300px;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-family: Arial, sans-serif;
}
.panel {
width: 100%;
border: 1px solid black;
cursor: pointer;
margin-bottom: 10px;
}
.panel h6 {
font-size: 14px;
margin: 0;
padding: 10px;
background: wheat;
}
.panel p {
font-size: 0;
padding: 0 10px;
margin: 0;
transition: all 0.3s linear;
}
.panel.open p {
font-size: 14px;
padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<div class="accordion">
<div class="panel">
<h6>Panel One</h6>
<p>Lorem ipsum dolor sit amet</p>
</div>
<div class="panel">
<h6>Panel Two</h6>
<p>Lorem ipsum dolor sit amet</p>
</div>
<div class="panel">
<h6>Panel Three</h6>
<p>Lorem ipsum dolor sit amet</p>
</div>
</div>
Just 3 lines of JS, job done. Now I want to do the same in React, but have no idea how. My attempt:
import { useState } from "react"
export default function Accordion() {
const [isActive, setIsActive] = useState(false)
let classNames = 'panel ' + (isActive ? 'active' : '')
function handleClick() {
setIsActive(!isActive)
}
return (
<div className="accordion">
<Panel className={classNames} onClick={handleClick}>
<h6>Panel One</h6>
<p>Lorem Ipsum Dolor Sit Amet</p>
</Panel>
<Panel className={classNames} onClick={handleClick}>
<h6>Panel Two</h6>
<p>Lorem Ipsum Dolor Sit Amet</p>
</Panel>
<Panel className={classNames} onClick={handleClick}>
<h6>Panel Three</h6>
<p>Lorem Ipsum Dolor Sit Amet</p>
</Panel>
</div>
)
}
function Panel({ children, className, onClick }) {
return (
<div className={className} onClick={onClick}>
{children}
</div>
)
}
But it doesn't work well, when I click on a panel, all panels open/close together.
All tutorials online use .map()
, but ideally I'd like to avoid it.
I understand that in React useState
triggers a re-render, but useRef()
doesn't, so I thought I can combine them, but then again, it's not that I can do panel.current.toggleClass('open')
, so I don't know.
TLDR: Can anyone help me understand the basics of React, specifically how to "translate" the jQuery function on click $(element).not($(this)).removeClass(class); $(this).toggleClass(class)
?
Since here your requirement is to control each Panel
separately, and then only one of them can be open at time (according to your example), then instead of a boolean your state must store what panel is open. And since you don't want to use map
, and to avoid even more repetition, you should consider moving the classNames
into the Panel
component.
export default function Accordion() {
const [activePanel, setActivePanel] = useState(null);
const handleClick = (panelId) => {
setActivePanel((prevActivePanel) => (
prevActivePanel === panelId ? null : panelId
));
};
return (
<div className="accordion">
<Panel
isActive={activePanel === 1}
onClick={() => handleCLick(1)}
>
<h6>Panel One</h6>
<p>Lorem Ipsum Dolor Sit Amet</p>
</Panel>
<Panel
isActive={activePanel === 2}
onClick={() => handleClick(2)}
>
<h6>Panel Two</h6>
<p>Lorem Ipsum Dolor Sit Amet</p>
</Panel>
<Panel
isActive={activePanel === 3}
onClick={() => handleClick(3)}
>
<h6>Panel Three</h6>
<p>Lorem Ipsum Dolor Sit Amet</p>
</Panel>
</div>
);
}
function Panel({ children, isActive, onClick }) {
const classNames = 'panel ' + (isActive ? 'active' : '');
return (
<div className={className} onClick={onClick}>
{children}
</div>
);
}