Home > front end >  Send order to children
Send order to children

Time:11-25

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?

CodePudding user response:

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

    CodePudding user response:

    Using an internal state for the component is not recommended, at least from my point of view for what you are doing.

    you can control the open state of each list item from its properties like the example here:

    const Parent = ({ list }) => {
        const [isAllClosed, setIsAllClosed] = useState(false);
    
        const closeAll = () => {
            setIsAllClosed(true)
        };
    
        return (
            <>
                <button onClick={closeAll}>Close All</button>
                {list.map((item) => (
                    item.open = isAllClosed != null ? (!isAllClosed) : true;
                    <Accordion item={item} />
                ))}
            </>
        );
    };
    
    const Accordion = ({ item }) => {
        return (
            <div onClick={() => console.log('item clicked')}>
                <p>item.name</p>
                {item.open ? <p>item.detail</p> : null}
            </div>
        );
    };
    

    I also replaced you short circuit evaluation open && <p>item.detail</p> to a ternary. The reason for that is that you will get a string false being printed if not true, does it make sense?

    You will need somehow control the state of the whole list whether an item is open or not from whoever is using the parent.

    But avoid using internal state when you can.

    CodePudding user response:

    I think you can try creating a state variable inside the parent and passing it as a prop to the child to control the behavior of the child.

    const Parent = ({ list }) => {
      const [masterOpen, setMasterOpen] = useState(true);
        <>
          <button onClick={() => setMasterOpen(false)}>Close All</button>
          {list.map((item) => (
            <Accordion item={item} parentOpen={masterOpen} />
          ))}
        </>
      );
    };
    
    const Accordion = ({ item, parentOpen }) => {
      const [open, setOpen] = useState(false);
      if (!parentOpen) {
        setOpen(false);
      }
      return (
        <div onClick={() => setOpen((o) => !o)}>
          <p>{item.name}</p>
          {open && <p>item.detail</p>}
        </div>
      );
    };
    
  • Related