Skip to content
Advertisement

Send order to children

const Parent = ({list}) => {

   const closeAll = () => {
   // What should be in here?
   }

   return (
       <>
        <button onClick={() => closeAll()}>Close All</button>
        {list.map(item => <Accordion item={item}/>)}
       </>
   )
     
}


const Accordion = ({item}) => {
  const [open, setOpen] = useState(false);

  return (
      <div onClick={() => setOpen(o => !o)}>
          <p>item.name</p>
          {open && <p>item.detail</p>
      </div>   
  )
}

Basically, as above, there is the Accordion components and a parent component that hosts all of them. Each Accordion component has a state called open. I want to change state of each child from parent component. How can I send an order to a child component to change its state?

Advertisement

Answer

Lift your state up into Parent.

  • closeAll can just map over the list and set all the open properties to false.
  • Have a handleClick callback that you pass down to Accordion which sets the state of the clicked item’s open property to the inverse in Parent
  • Take a look at the react docs for lifting state up.
import { useState } from "react";

const data = [
  {
    detail: "foo",
    name: "bar"
  },
  {
    detail: "foo1",
    name: "bar1"
  }
];

const Parent = ({ defaultList = data }) => {
  const [list, setList] = useState(
    defaultList.map((i) => ({
      ...i,
      open: false
    }))
  );

  const closeAll = () => {
    setList(
      list.map((i) => ({
        ...i,
        open: false
      }))
    );
  };

  const handleClick = (i) => {
    const newList = [...list];
    newList[i].open = !list[i].open;

    setList(newList);
  };

  return (
    <>
      <button onClick={() => closeAll()}>Close All</button>
      {list.map((item, i) => (
        <Accordion item={item} handleClick={() => handleClick(i)} />
      ))}
    </>
  );
};

const Accordion = ({ item, handleClick }) => {
  return (
    <div>
      <button onClick={handleClick}>{item.name}</button>
      {item.open && <p>{item.detail}</p>}
    </div>
  );
};

export default Parent;

Edit fervent-allen-9wlmh


If you are unable to lift your state there is an alternative approach using react refs.

Create ref (initially an empty array) that each Accordion will push its own close state setting function into when it first renders.

In Parent, loop over the the array of close state settings functions inside the ref and execute each.

const Parent = ({ list = data }) => {
  const myRef = useRef([]);

  const closeAll = () => {
    myRef.current.forEach((c) => c());
  };

  return (
    <>
      <button onClick={() => closeAll()}>Close All</button>
      {list.map((item, i) => (
        <Accordion item={item} myRef={myRef} />
      ))}
    </>
  );
};

const Accordion = ({ item, myRef }) => {
  const [open, setOpen] = useState(false);

  useEffect(() => {
    myRef.current.push(() => setOpen(false));
  }, [myRef]);

  return (
    <div>
      <button onClick={() => setOpen((o) => !o)}>{item.name}</button>
      {open && <p>{item.detail}</p>}
    </div>
  );
};

export default Parent;

Edit close from above using refs

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement