Home > Net >  React setting the state of all rendered items with .map in parent component
React setting the state of all rendered items with .map in parent component

Time:11-13

I have a parent component with a handler function:

  const folderRef = useRef();
  const handleCollapseAllFolders = () => {
    folderRef.current.handleCloseAllFolders();
  };

In the parent, I'm rendering multiple items (folders):

        {folders &&
          folders.map(folder => (
            <CollapsableFolderListItem
              key={folder.id}
              name={folder.name}
              content={folder.content}
              id={folder.id}
              ref={folderRef}
            />
          ))}

In the child component I'm using the useImperativeHandle hook to be able to access the child function in the parent:

 const [isFolderOpen, setIsFolderOpen] = useState(false);
  // Collapse all
  useImperativeHandle(ref, () => ({
    handleCloseAllFolders: () => setIsFolderOpen(false),
  }));

The problem is, when clicking the button in the parent, it only collapses the last opened folder and not all of them.

Clicking this:

 <IconButton
            onClick={handleCollapseAllFolders}
          >
            <UnfoldLessIcon />
          </IconButton>

Only collapses the last opened folder.

When clicking the button, I want to set the state of ALL opened folders to false not just the last opened one.

Any way to solve this problem?

CodePudding user response:

You could create a "multi-ref" - ref object that stores an array of every rendered Folder component. Then, just iterate over every element and call the closing function.

export default function App() {
  const ref = useRef([]);

  const content = data.map(({ id }, idx) => (
    <Folder key={id} ref={(el) => (ref.current[idx] = el)} />
  ));

  return (
    <div className="App">
      <button
        onClick={() => {
          ref.current.forEach((el) => el.handleClose());
        }}
      >
        Close all
      </button>
      {content}
    </div>
  );
}

Codesandbox: https://codesandbox.io/s/magical-cray-9ylred?file=/src/App.js

CodePudding user response:

For each map you generate new object, they do not seem to share state. Try using context

CodePudding user response:

You are only updating the state in one child component. You need to lift up the state.

Additionally, using the useImperativeHandle hook is a bit unnecessary here. Instead, you can simply pass a handler function to the child component.

In the parent:

const [isAllOpen, setAllOpen] = useState(false);

return (
// ...
{folders &&
   folders.map(folder => (
      <CollapsableFolderListItem
          key={folder.id}
          isOpen={isAllOpen}
          toggleAll={setAllOpen(!isAllOpen)}
          // ...
       />
  ))}
)

In the child component:

const Child = ({ isOpen, toggleAll }) => {
  const [isFolderOpen, setIsFolderOpen] = useState(false);

  useEffect(() => {
    setIsFolderOpen(isOpen);
  }, [isOpen]);

  return (
    // ...
    <IconButton
      onClick={toggleAll}
    >
        <UnfoldLessIcon />
    </IconButton>
  )
}
  • Related