Kim Duyên
Kim Duyên

Reputation: 53

How to Collapsibles in reactjs?

In the Section 1 div, if it is active the content will be displayed, otherwise not. The problem in the Section 2 div, when I click on it it shows active but it doesn't display the content.

File App.js

const App = () => {
  return (
    <div>
      <Accordition title={`Section 1`}>
        <p>
          Lorem ipsum 1 dolor sit amet, consectetur adipisicing elit, sed do
          eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
          minim veniam, quis nostrud exercitation ullamco laboris nisi ut
          aliquip ex ea commodo consequat.
        </p>
      </Accordition>
      <Accordition title={`Section 2`}>
        <p>
          Lorem ipsum 2 dolor sit amet, consectetur adipisicing elit, sed do
          eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
          minim veniam, quis nostrud exercitation ullamco laboris nisi ut
          aliquip ex ea commodo consequat.
        </p>
      </Accordition>
    </div>
  );
};
export default App;

File components/Accordition.js

const Accordition = ({ title, children }) => {
  const [isActive, setisActive] = useState(false);

  const toggleCollapseContent = () => {
    setisActive(!isActive);
  };

  useEffect(() => {
    let panelContent = document.querySelector(".content");
    window.addEventListener("resize", function () {
      if (isActive) {
        panelContent.style.maxHeight = panelContent.scrollHeight + "px";
      } else {
        panelContent.style.maxHeight = null;
      }
    });
  }, [isActive]);
  useEffect(() => {
    let panelContent = document.querySelector(".content");
    return () =>
      window.removeEventListener("resize", function () {
        if (isActive) {
          panelContent.style.maxHeight = panelContent.scrollHeight + "px";
        } else {
          panelContent.style.maxHeight = null;
        }
      });
  });

  useEffect(() => {
    let panelContent = document.querySelector(".content");
    if (isActive) {
      panelContent.style.maxHeight = panelContent.scrollHeight + "px";
    } else {
      panelContent.style.maxHeight = null;
    }
  });
  return (
    <div className="lp-collapsible-content">
      <div
        className={`title ${isActive ? "active" : ""}`}
        onClick={toggleCollapseContent}
      >
        {title}
      </div>
      <div className="content">{children}</div>
    </div>
  );
};
export default Accordition;

Or see: https://codesandbox.io/s/demoaccordion-otkoo

Thank you everyone

Upvotes: 1

Views: 94

Answers (3)

Abrah_dago_313
Abrah_dago_313

Reputation: 210

First, check your two useEffect without dependency list, this cause performance issue.

Second, you can just render the content conditionaly to avoid to manipulate style.maxHeight, etc.

{isActive  && <div className="content">{children}</div>}

your animation will work fine if there is.

document.querySelector(".content");

the querySelector is global and return the first node found with class '.content' in your case, and may be that is why the second is not shown when active.

Upvotes: 1

Mike
Mike

Reputation: 56

You have to provide an id for each <Accordition> component:

<Accordition title={`Section 1`} id='section-1'>

And in Accordition.js use getElementById:

import React, { useEffect, useState } from "react";

const Accordition = ({ title, children, id }) => {
  const [isActive, setisActive] = useState(false);

  const toggleCollapseContent = () => {
    setisActive(!isActive);
    
  };

  useEffect(() => {
    let panelContent = document.getElementById(id);
    window.addEventListener("resize", function () {
      if (isActive) {
        panelContent.style.maxHeight = panelContent.scrollHeight + "px";
      } else {
        panelContent.style.maxHeight = null;
      }
    });
  }, [isActive,id]);
  useEffect(() => {
    let panelContent = document.getElementById(id);
    return () =>
      window.removeEventListener("resize", function () {
        if (isActive) {
          panelContent.style.maxHeight = panelContent.scrollHeight + "px";
        } else {
          panelContent.style.maxHeight = null;
        }
      });
  });

  useEffect(() => {
    let panelContent = document.getElementById(id);
    if (isActive) {
      panelContent.style.maxHeight = panelContent.scrollHeight + "px";
    } else {
      panelContent.style.maxHeight = null;
    }
  });
  return (
    <div className="lp-collapsible-content">
      <div
        className={`title ${isActive ? "active" : ""}`}
        onClick={toggleCollapseContent}
      >
        {title}
      </div>
      <div className="content" id={id}>{children}</div>
    </div>
  );
};
export default Accordition;

Enjoy

Upvotes: 0

lbsn
lbsn

Reputation: 2412

You need to specify an id on every Accordion item. Otherwise when you retrieve the content section like this: document.querySelector(".content") it will just retrieve the first div with content class and that's always Section 1.

Set a unique id on every section, pass it as a prop to Accordion component and set it on <div className="lp-collapsible-content" id={id}>. Than you will be able to fetch the content of a specific section like this:

document.querySelector(`#${id} .content`)

Here's a refactored version of your example

Upvotes: 1

Related Questions