0

How can I make a separate trigger for each Collapsible element with a single useState (preferably)?

CodeSandBox - https://codesandbox.io/s/separate-triggers-question-forked-vgs5b

App.js

import React from "react";
import Collapsible from "react-collapsible";

function App() {
  const database = [
    { id: 1, name: "Name1", description: "Desc1" },
    { id: 2, name: "Name2", description: "Desc2" },
    { id: 3, name: "Name3", description: "Desc3" },
    { id: 4, name: "Name4", description: "Desc4" },
    { id: 5, name: "Name5", description: "Desc5" }
  ];

  const [items, setItems] = React.useState(database);
  const [open, setOpen] = React.useState(false);

  return (
    <div className="tracker_master">
      {items.map((item, index) => (
        <div onClick={() => setOpen(!open)} key={item.id}>
          {item.name.toUpperCase()}

          <Collapsible open={open}>
            <div>{item.description.toUpperCase()}</div>
          </Collapsible>
        </div>
      ))}
    </div>
  );
}

export default App;
Hartaithan.
  • 326
  • 3
  • 14
  • Just check this, if this might resolve your issue https://stackoverflow.com/questions/53574614/multiple-calls-to-state-updater-from-usestate-in-component-causes-multiple-re-re – Badal S Jan 18 '21 at 09:57

2 Answers2

2

To have seperate triggers for each item I recommend to abstract each element into its own component that has its own open state.

So you could do something like this:

const Item = ({ item }) => {
  const [open, setOpen] = React.useState(false);
  return (
    <div onClick={() => setOpen(!open)} key={item.id}>
      {item.name.toUpperCase()}

      <Collapsible open={open}>
        <div>{item.description.toUpperCase()}</div>
      </Collapsible>
    </div>
  );
};

function App() {
  const database = [
    { id: 1, name: "Name1", description: "Desc1" },
    { id: 2, name: "Name2", description: "Desc2" },
    { id: 3, name: "Name3", description: "Desc3" },
    { id: 4, name: "Name4", description: "Desc4" },
    { id: 5, name: "Name5", description: "Desc5" }
  ];

  return (
    <div className="tracker_master">
      {database.map((item) => (
        <Item item={item} key={item.id} />
      ))}
    </div>
  );
}

sandbox example

5eb
  • 14,798
  • 5
  • 21
  • 65
2

If you want to use single useState then your single state object should manage open flag of all the items as shown below (I have updated your code and tested its working fine).

openFlags is single state which maintains the open flag of each item by id and use it for triggering collabse and expand the items independently.

import React from "react";
import Collapsible from "react-collapsible";

function App() {
  const database = [
    { id: 1, name: "Name1", description: "Desc1" },
    { id: 2, name: "Name2", description: "Desc2" },
    { id: 3, name: "Name3", description: "Desc3" },
    { id: 4, name: "Name4", description: "Desc4" },
    { id: 5, name: "Name5", description: "Desc5" }
  ];

  const [items, setItems] = React.useState(database);
  let initialOpenFlags = {}
   items.forEach((i) => {
    initialOpenFlags = {
      ...initialOpenFlags,
      [i.id]: false
    };
  });
  const [openFlags, setOpenFlags] = React.useState(initialOpenFlags);

  return (
    <div className="tracker_master">
      {items.map((item, index) => (
        <div
          onClick={() =>
            setOpenFlags({ ...openFlags, [item.id]: !openFlags[item.id] })
          }
          key={item.id}
        >
          {item.name.toUpperCase()}

          <Collapsible open={openFlags[item.id]}>
            <div>{item.description.toUpperCase()}</div>
          </Collapsible>
        </div>
      ))}
    </div>
  );
}

export default App;

Pavan
  • 819
  • 6
  • 18