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 theopen
properties tofalse
.- Have a
handleClick
callback that you pass down toAccordion
which sets the state of the clicked item’sopen
property to the inverse inParent
- 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;
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;