I have a simple component with a few nested bits of mark-up:
import React, { ReactElement } from "react";
type MenuItem = {
title: string;
subItems?: Array<string>;
};
type MenuConfig = Array<MenuItem>;
function Item({ item }: { item: MenuItem }): ReactElement {
const [showItem, setShowItem] = React.useState(false);
const handleShowItem = (): void => {
setShowItem(!showItem);
};
return (
<>
{item.subItems && (
<button onClick={() => handleShowItem()}>
{showItem ? "Hide" : "Expand"}
</button>
)}
{showItem && <SubItem item={item} />}
</>
);
}
function SubItem({ item }: { item: MenuItem }): ReactElement {
const { title } = item;
return (
<ul>
{item?.subItems?.map((subitem: string, i: number) => (
<li key={i}>
{subitem}
</li>
))}
</ul>
);
}
function Solution({ menuConfig }: { menuConfig: MenuConfig }): ReactElement {
return (
<>
{menuConfig.map((item: MenuItem, i: number) => (
<div key={i}>
<span>{item.title}</span>
<Item item={item} />
</div>
))}
</>
);
}
export default Solution;
This is what I am passing in:
menuConfig={[
{
title: "About",
},
{
title: "Prices",
subItems: ["Hosting", "Services"],
},
{
title: "Contact",
subItems: ["Email", "Mobile"],
},
]}
Now, it functions as expected, if an item contains subItems
then an Expand
button will be show which, if clicked, will only expand the relevant list.
How should I go about making sure only one list would be open at a time, given my implementation?
So if the user clicks Expand
on a button, the other previously expanded lists should close.
I can't mess with the data that's coming in, so can't add ids to the object, but titles are unique.
I've searched and whilst there are a few examples, they don't help me, I just can't wrap my head around this.
CodePudding user response:
This is a perfect use case for React context. You essentially want a shared state among your menu. You can achieve this with prop drilling however contexts are a much cleaner solution.
const MenuContext = createContext<{
expandedState?: [number, React.Dispatch<React.SetStateAction<number>>];
}>({});
function Solution(): ReactElement {
const expandedState = useState<number>(null);
const value = { expandedState };
return (
<>
<MenuContext.Provider value={value}>
{menuConfig.map((item: MenuItem, i: number) => (
<div key={i}>
<span>{item.title}</span>
<Item i={i} item={item} />
</div>
))}
</MenuContext.Provider>
</>
);
}
function Item({ item, i }: { item: MenuItem; i: number }): ReactElement {
const [expanded, setExpanded] = useContext(MenuContext)?.expandedState ?? [];
const shown = expanded === i;
return (
<>
{item.subItems && (
<button onClick={() => setExpanded?.(i)}>
{shown ? "Hide" : "Expand"}
</button>
)}
{shown && <SubItem item={item} />}
</>
);
}