Home > front end >  React: How to open a newly added accordion item?
React: How to open a newly added accordion item?

Time:11-02

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" }}
  • Related