Let's say I want to create a UI component for an "accordion" (a set of collapsible panels). The parent component controls the state of which panels are open, while the child panels should be able to read the context to determine whether or not they're open.
const Accordion = ({ children }) => {
const [openSections, setOpenSections] = useState({})
const isOpen = sectionId => Boolean(openSections[sectionId])
const onToggle = sectionId => () =>
setOpenSections({ ...openSections, [sectionId]: !openSections[sectionId] })
const context = useMemo(() => createContext(), [])
// Can't tell children to use *this* context
return (
<context.Provider value={useMemo(() => ({ isOpen, onToggle }), [isOpen, onToggle])}>
{children}
</context.Provider>
)
}
const AccordionSection = ({ sectionId, title, children }) => {
const { isOpen, onToggle } = useContext(context)
// No way to infer the right context
return (
<>
<button onClick={onToggle(sectionId)}>{isOpen(sectionId) ? 'Close' : 'Open'}</button>
{isOpen && children}
</>
)
}
The only way I could think of accomplishing this would be to have Accordion
run an effect whenever children
changes, then traverse children
deeply and find AccordionSection
components, while not recursing any nested Accordion
components -- then cloneElement()
and inject context
as a prop to each AccordionSection
.
This seems not only inefficient, but I'm not even entirely sure it will work. It depends on children
being fully hydrated when the effect runs, which I'm not sure if that happens, and it also requires that Accordion
's renderer gets called whenever deep children change, which I'm not sure of either.
My current method is to create a custom hook for the developer implementing the Accordion. The hook returns a function which returns the isOpen
and onToggle
functions which have to manually be passed to each rendered AccordionSection
. It works and is possibly more elegant than the children solution, but requires more overhead as the developer needs to use a hook just to maintain what would otherwise be state encapsulated in Accordion
.
useMemo
) – Pricilla