I got an accordion component in my React application. The user is able to add items to this accordion. Now, when the users clicks a button to add an item to the accordion, I would like for the newly added accordion item to automatically open. Here is a codesandbox displaying my problem.
To open and close the accordion, I use a bit of state: const [clicked, setClicked] = useState("0");
.
When an accordionItem
is clicked, the following function gets called:
const handleToggle = (index) => {
if (clicked === index) {
return setClicked("0");
}
setClicked(index);
};
In my codesandbox demo, I use an array of question and answers as accordionItems
and I loop through them. So I thought that all I had to do was just use the setClicked
function to set the clicked
state equal to the length of the faqs array minus 1 in order to open the right accordion, but that is not working like expected...
Code for Reference
Accordion.jsx:
import AccordionItem from "./AccordionItem";
import { useState } from "react";
export default function Accordion() {
const initialFaqs = [
{
question: "Lorem ipsum dolor sit amet?",
answer:
"Tenetur ullam rerum ad iusto possimus sequi mollitia dolore sunt quam praesentium. Tenetur ullam rerum ad iusto possimus sequi mollitia dolore sunt quam praesentium.Tenetur ullam rerum ad iusto possimus sequi mollitia dolore sunt quam praesentium."
},
{
question: "Dignissimos sequi architecto?",
answer:
"Aperiam ab atque incidunt dolores ullam est, earum ipsa recusandae velit cumque. Aperiam ab atque incidunt dolores ullam est, earum ipsa recusandae velit cumque."
},
{
question: "Voluptas praesentium facere?",
answer:
"Blanditiis aliquid adipisci quisquam reiciendis voluptates itaque."
}
];
const [faqs, setFaqs] = useState(initialFaqs);
const [clicked, setClicked] = useState("0");
const handleToggle = (index) => {
if (clicked === index) {
return setClicked("0");
}
setClicked(index);
};
const addFaq = () => {
let newFaq = [...faqs];
newFaq.push({
question: "This accordion item should automatically open",
answer:
"Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur."
});
setFaqs(newFaq);
//I want to open the accordion of the item that is just openend.
//Currently, setting the clicked state equal to the length of the faqs gives an error.
//Setting clicked equal to faqs.length - 1 does not open the newly added faq...
//How to solve?
setClicked(faqs.length);
};
return (
<div>
<ul className="accordion">
{faqs.map((faq, index) => (
<AccordionItem
onToggle={() => handleToggle(index)}
active={clicked === index}
key={index}
faq={faq}
/>
))}
</ul>
<button onClick={() => addFaq()} className="add-faq-btn">
Add question
</button>
</div>
);
}
AccordionItem.jsx
:
import { useRef } from "react";
export default function AccordionItem({ faq, active, onToggle }) {
const contentEl = useRef();
const { question, answer } = faq;
return (
<li className={`accordion_item ${active ? "active" : ""}`}>
<button className="button" onClick={onToggle}>
{question}
<span className="control">{active ? "—" : " "} </span>
</button>
<div
ref={contentEl}
className="answer_wrapper"
style={
active
? { height: contentEl.current.scrollHeight }
: { height: "0px" }
}
>
<div className="answer">{answer}</div>
</div>
</li>
);
}
CodePudding user response:
You have few problems.
One is you must use newFaq
below during adding (because state variable doesn't get updated immediately):
setClicked(newFaq.length - 1);
After fixing it you will get another error. That one is because you are trying to access ref
of a DOM element during render in style
object: since component was not placed on DOM yet, the ref
was null. You will need something like this:
React.useEffect(() => {
setHeight(contentEl.current.scrollHeight);
}, []);
NOTE: Honestly I am not sure which is the right dependency for useEffect above, if we use it like this, it will always use the initial height. This can be ok if the height of some item doesn't change afterwards.
and use height
in style
style={active ? { height } : { height: "0px" }}