I'm trying to build a nav, but the way I currently have the data setup I'm having to map within a map with a map to get all of my data out - I have a feeling that this is a poor way of doing something and there may be a better solution?
My code can also be found in a codesandbox here
Both the data and jsx are up for debate and I'm more than happy to modify either or both for a better solution.
My JSX:
<div className="App">
<nav>
{/* Top Level Nav Items */}
{nav.map((item) => (
<a href={item.url} key={item.category}>
{item.category}
</a>
))}
</nav>
{/* Nav Sub-Categories */}
<div className="subCategories">
{nav.map((item) => (
<div className="subCategory">
<img src={item.img} />
{item.subCatergories.map((subCategory) => (
<>
<h3>{subCategory.title}</h3>
{subCategory.links.map((link) => (
<a href={link.url}>{link.name}</a>
))}
</>
))}
</div>
))}
</div>
</div>
My data:
const nav = [
{
category: "LINK NAME",
url: "/link",
img: "https://via.placeholder.com/100/?text=Image",
subCatergories: [
{
title: "Sub Category 1",
links: [
{ name: "link 1", url: "/1" },
{ name: "link 2", url: "/2" },
{ name: "link 3", url: "/3" },
{ name: "link 4", url: "/4" },
{ name: "link 5", url: "/5" },
{ name: "link 6", url: "/6" },
{ name: "link 7", url: "/7" },
{ name: "link 8", url: "/8" },
{ name: "link 8", url: "/9" }
]
},
{
title: "Sub Category 2",
links: [
{ name: "link 1", url: "/1" },
{ name: "link 2", url: "/2" },
{ name: "link 3", url: "/3" },
{ name: "link 4", url: "/4" },
{ name: "link 5", url: "/5" },
{ name: "link 6", url: "/6" },
{ name: "link 7", url: "/7" },
{ name: "link 8", url: "/8" },
{ name: "link 8", url: "/9" }
]
},
{
title: "Sub Category 3",
links: [
{ name: "link 1", url: "/1" },
{ name: "link 2", url: "/2" },
{ name: "link 3", url: "/3" },
{ name: "link 4", url: "/4" },
{ name: "link 5", url: "/5" },
{ name: "link 6", url: "/6" },
{ name: "link 7", url: "/7" },
{ name: "link 8", url: "/8" },
{ name: "link 8", url: "/9" }
]
}
]
},
{
category: "LINK NAME 2",
url: "/link2",
img: "https://via.placeholder.com/100/?text=Image",
subCatergories: [
{
title: "Sub Category 1",
links: [
{ name: "link 1", url: "/1" },
{ name: "link 2", url: "/2" },
{ name: "link 3", url: "/3" },
{ name: "link 4", url: "/4" },
{ name: "link 5", url: "/5" },
{ name: "link 6", url: "/6" },
{ name: "link 7", url: "/7" },
{ name: "link 8", url: "/8" },
{ name: "link 8", url: "/9" }
]
},
{
title: "Sub Category 2",
links: [
{ name: "link 1", url: "/1" },
{ name: "link 2", url: "/2" },
{ name: "link 3", url: "/3" },
{ name: "link 4", url: "/4" },
{ name: "link 5", url: "/5" },
{ name: "link 6", url: "/6" },
{ name: "link 7", url: "/7" },
{ name: "link 8", url: "/8" },
{ name: "link 8", url: "/9" }
]
},
{
title: "Sub Category 3",
links: [
{ name: "link 1", url: "/1" },
{ name: "link 2", url: "/2" },
{ name: "link 3", url: "/3" },
{ name: "link 4", url: "/4" },
{ name: "link 5", url: "/5" },
{ name: "link 6", url: "/6" },
{ name: "link 7", url: "/7" },
{ name: "link 8", url: "/8" },
{ name: "link 8", url: "/9" }
]
}
]
}
];
Any help or guidance here would be great, thank you!
2 Answers 2
The use of nested maps is a fine solution, I just have a few suggestions.
I would rename some keys of your object to make them consistent:
const nav = [
{
name: "LINK NAME",
url: "/link",
img: "https://via.placeholder.com/100/?text=Image",
categories: [
{
name: "Sub Category 1",
items: [
{name: "link 1", url: "/1"},
]
},
{
name: "Sub Category 2",
items: [
{name: "link 1", url: "/1"},
{name: "link 2", url: "/2"},
]
},
{
name: "Sub Category 3",
items: [
{name: "link 1", url: "/1"},
{name: "link 2", url: "/2"},
{name: "link 8", url: "/9"}
]
}
]
},
]
I suggest using object destruction in the parameter definition to make the code more concise by avoiding dot notation.
Also, I would create a separate component for subcategories to make the code for navigation simpler to read.
Here is the code for React components:
export default function App() {
return (
<div className="App">
<nav>
{/* Top Level Nav Items */}
{nav.map(({ name, url }) => (
<a href={url} key={name}>{name}</a>
))}
</nav>
{/* Nav Sub-Categories */}
<div className="subCategories">
{nav.map(({ categories, img }) => (
<SubCategory categories={categories} img={img} />
))}
</div>
</div>
);
}
function SubCategory({ categories, img }) {
return (
<div className="subCategory">
<img src={img} />
{categories.map(({ name, items}) => (
<>
<h3>{name}</h3>
{items.map(({ name, url }) => (
<a href={url}>{name}</a>
))}
</>
))}
</div>
)
}
Although there is nothing wrong with nested maps you are correct to seek a better solution.
Controls nest or compose much cleaner than maps. Allowing controls to have at most one map is a very good practice.
CodeSandbox
export default function LeafLinkList({ links }) {
return links.map(({name, url}) => <a href={url} key={url}>{name}</a>);
}
export default function SubCategory({ title, links }) {
return (
<Fragment>
<h3>{title}</h3>
<LeafLinkList links={links} />
</Fragment>
);
}
export default function SubCategoryList({ item }) {
const { subCatergories } = item || {};
return subCatergories.map(({ title, links }) => (
<SubCategory key={title} title={title} links={links} />
));
}
export default function App() {
return (
<div className="App">
<nav>
{/* Top Level Nav Items */}
{nav.map((item) => (
<a href={item.url} key={item.category}>
{item.category}
</a>
))}
</nav>
{/* Nav Sub-Categories */}
<div className="subCategories">
{nav.map((item) => (
<div className="subCategory" key={item.url}>
<img src={item.img} alt="box" />
<SubCategoryList item={item} />
</div>
))}
</div>
</div>
);
}
One of the realities of modern development is constantly changing requirements. Nested or composed controls handles change better. Don't take my word for it. Consider the two solutions and now add the requirement that each Sub Category is collapsible.
How long did it take you to find Sub Category?
Where do you add state?
Here is the new sub Category to handle the new requirement.
export default function SubCategory({ title, links }) {
const [isOpen, setOpen] = useState(false);
const indicator = isOpen ? "-" : "+";
const header = `${indicator} ${title}`;
const handleClick = () => {
setOpen(toggleOpen);
};
return (
<Fragment>
<h3 onClick={handleClick}>{header}</h3>
{isOpen && <LeafLinkList links={links} />}
</Fragment>
);
}
n
totallinks
items, there's no avoiding iteratingn
times. \$\endgroup\$