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 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
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;
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 stringfalse
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> ); };