I have a component that creates a collapsible accordion, using the following code:
import React, {useState} from 'react';
const Collapsible = (props) =>{
const [open, setOpen] = useState(false);
const toggle = () => {
setOpen(!open);
}
return(
<div>
<button className={props.level} onClick={toggle}>{props.label}</button>
{open && (
<div className="toggle">
{props.children}
</div>
)}
</div>
)
}
export default Collapsible;
I use this in multiple places in my main app, sometimes using accordions within other accordions. In multiple instances, I don’t actually know how many accordions will be on the page, because they are dynamically rendered based on the data. With this in mind, I want to create a button in the main app that would open (and another that would close) all accordions, without having a set number in mind, and without rendering them all in the main app (i.e. some accordions are rendered in other components, that are then imported into the main app).
I’ve tried using ref hooks to accomplish this:
- Added ref in the Collapsible component’s button, accessed from parent through props:
<button className={props.level} onClick={toggle} ref={props.innerRef}>{props.label}</button>
- Adding the following ref in the main app:
const childRef = useRef();
const openClick = () => {
childRef.state = true;
}
const closeClick = () => {
childRef.state = false;
}
- Using the following buttons in the main app:
<button onClick = {openClick}>
Expand all
</button>
<button onClick = {closeClick}>
Collapse all
</button>
- Adding the ref to the accordion when rendering:
<Collapsible label="" level="content" innerRef={childRef}>
This does absolutely nothing, likely because you can’t access state in the way I’m attempting in #2, but I thought it was worth a shot…
Any thoughts on whether this is doable?
2
Answers
You can use
Redux
.openAllAccordions
that loop throw IDs and set the accordion belongs to that id to open=truecloseAllAccordions
that loop throw IDs and set the accordion belongs to that id to open=falseIt’s pretty common to have a more-or-less arbitrary collection of component instances that need some coordination. An approach that I have had success with is to create a Context with an associated hook that components can use to register. The hook returns a view of the shared state and functions to modify that state, subject to your needs.
Here, you could create a Context that stores the
opener
functions for each of the registered components, and providesopenAll
/closeAll
functions:… and a hook called by each child that registers with the context, and returns your familiar
toggle
/open
values:It’s also handy to have a separate hook for the actions that you can execute en masse:
Sandbox
Note that for simplicity this is just using the individual
opener
(akasetOpen
) functions as unique identifiers for each registered component. A flexible alternative would be use some other identifier, so you could open/close arbitrary accordions on navigation etc.