Home > Blockchain >  How can I toggle accordion panels individually using React Hooks?
How can I toggle accordion panels individually using React Hooks?

Time:10-28

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>
  );
};

CodePudding user response:

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 :)

  • Related