I am building this simple accordion and trying to grab the state (isActive) from the App component, the problem here is all contents open and close together when I try to open each one separately. these are two different components
Although the problem can be fixed if I create the state in the Accordion component, but I need to find a way to fix the problem when the state is passed from App component because I will need to use that state in App component for other things
can anyone help with example please
thanks
import React, { useState } from "react";
import Accordion from "./Accordion";
const App = () => {
const [isActive, setIsActive] = useState(false)
const accordionData = [
{
title: "Section 1",
content: `Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quis`
},
{
title: "Section 2",
content: `Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quis`
},
{
title: "Section 3",
content: `Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quis`
}
];
return (
<div>
<h1>React Accordion Demo</h1>
<div className="accordion">
{accordionData.map(({ title, content }) => (
<Accordion
setIsActive={setIsActive}
isActive={isActive}
title={title}
content={content}
/>
))}
</div>
</div>
);
};
export default App;
This is the Accordion component
import React from "react";
const Accordion = ({ title, content, isActive, setIsActive }) => {
return (
<div className="accordion-item">
<div className="accordion-title" onClick={() => setIsActive(!isActive)}>
<div>{title}</div>
<div>{isActive ? "-" : " "}</div>
</div>
{isActive && <div className="accordion-content">{content}</div>}
</div>
);
};
export default Accordion;
This is what happens when I click on one of the buttons it opens all contents
click on image
CodePudding user response:
You need to pass a function that calls setIsActive
as a props, try that in App
:
const App = () => {
const [isActive, setIsActive] = useState(false)
...
const setIsItemActive = (isItemActive) => {
setIsActive(isItemActive)
}
return (
<div>
<h1>React Accordion Demo</h1>
<div className="accordion">
{accordionData.map(({ title, content }) => (
<Accordion
setIsActive={setIsItemActive}
isActive={isActive}
title={title}
content={content}
/>
))}
</div>
</div>
);
};
export default App;
CodePudding user response:
I've introduced id
and active
classes to the dataset (you can do this "in-component" if you prefer). When a panel heading is clicked get the panel id
, and then map
over the panel state returning a new array of updated objects - if the panel id matches the id from the clicked heading set active
to true
, otherwise false
.
Update the new state to cause a new render. When you map
over the panel state to produce the JSX you can check the active prop of each panel object and set the text to show/hide.
const { useState } = React;
function Example({ data }) {
// Use the state to hold the panel data, introducing
// an active prop
const [ panels, setPanels ] = useState(data);
function handleClick(e) {
// When the accordion is clicked check that
// the clicked element was the heading...
if (e.target.matches('h4')) {
// Get the id from the parent element (the panel)
const panel = e.target.closest('.panel');
const { id } = panel.dataset;
// `map` over the panel data and, if the id matches
// the current panel id return an object with the
// active prop set to true, otherwise return an object
// with the prop set to false
const updated = panels.map(panel => {
if (panel.id === id) {
return { ...panel, active: true };
}
return { ...panel, active: false };
});
// Update the panel state with the new array
setPanels(updated);
}
}
return (
<section onClick={handleClick} >
{panels.map(panel => {
const { id, title, content, active } = panel;
// Determine whether the `p` element should be
// shown based on the panel's active prop
return (
<section
key={id}
data-id={id}
className="panel"
>
<h4>{title}</h4>
<p className={active && 'active'}>{content}</p>
</section>
);
})}
</section>
);
}
const data = [
{ id: 1, title: 'Heading 1', content: 'Text 1', active: false },
{ id: 2, title: 'Heading 2', content: 'Text 2', active: false },
{ id: 3, title: 'Heading 3', content: 'Text 3', active: false }
];
ReactDOM.render(
<Example data={data} />,
document.getElementById('react')
);
.panel p { display: none; }
.panel h4, .panel p { padding: 0.25em; }
.panel h4:hover { background-color: lightgreen; cursor: pointer; }
.active { display: block !important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>