It really grinds my gears when I see this pattern, in particular navigation bars and sidebars seem to attract it:
const navItems = [
{
title: 'home',
link: '/home',
},
{
title: 'about',
link: '/about',
},
{
title: 'contact us',
link: '/contact-us',
},
// ... whatever ...
];
and then later
<ul>
{navItems.map(({ link, title }) => {
return (
<li>
<Link to={link}>{title}</Link>
</li>
);
})}
</ul>
This is totally unnecessary.
The implementer of the nav bar could directly unroll the array:
<ul>
<li><Link to="/home">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/contact-us">Contact Us</Link></li>
</ul>
For one, it's a lot less code! The unrolled version conveys the same information as the list but instead of adding a level of indirection, it directly codifies what is desired by the developer which makes it much simpler to make changes.
For two, the JSX variant makes it much easier to incorporate element-specific logic. Say, for example, you want a conditional on only one nav item. With an array, you'll have to figure out how to mutate the array or conditionally produce it. JSX already has this baked in!
<ul>
<li><Link to="/home">Home</Link></li>
{showAbout && <li><Link to="/about">About</Link></li>}
<li><Link to="/contact-us">Contact Us</Link></li>
</ul>
There's no need to make your life harder for no reason. The problem is further exacerbated when these conditions are produced in hooks and I've seen some developers go as far as writing a function that produces an array of items that is then converted to JSX.
function getNavItems(someCondition: boolean) {
return [
{
title: 'home',
link: '/home',
},
{
title: 'about',
link: '/about',
},
{
title: 'contact us',
link: '/contact-us',
},
// ... whatever ...
];
}
// ... later ...
function NavComponent() {
const someCondition = useSomeHook();
const navItems = getNavItems(someCondition);
return <ul>
{navItems.map(({ link, title }) => {
return (
<li>
<Link to={link}>{title}</Link>
</li>
);
})}
</ul>;
}
A function that produces nav items... that almost sounds like a React component! So not only have we reinvented JSX, we've completely reinvented the component model except on top of React, so we have to convert from one component model to another.
I suspect this antipattern comes from the desire to not hardcode strings. I've seen a lot of developers go through some herculean efforts to avoid hardcoding strings and by extension, an array of objects that we convert to JSX is "more maintainable" than JSX itself.
Except therein lies the problem: React and JSX are declarative languages already.
The desire to not hardcode strings, in my opinion, is a desire to keep imperative logic simpler and move constants to a more declarative world to add semantic information. That makes total sense. What doesn't make sense is moving declarative logic from a declarative world to another declarative world. JSX is already sufficiently descriptive and it's in some sense a giant constant to begin with. Embrace it!
There are cases where creating an abstract list does make more sense than using JSX because JSX isn't a universal hammer.
For example, in React Router the use of
data APIs is essentially moving away from the JSX'ified <Route>
pattern.
But this is still in the spirit of the law! The routes here don't represent a list of UI elements, they represent an abstract concept so there is no need to convert it back to React. Whereas in our navigation example, the items directly correspond to UI elements. In that case, React already dictates JSX as the interface. We don't need to create yet another layer of abstraction because we have a perfectly suitable (and also mandated by the framework) one in front of us: JSX.
I've found myself actually rewriting "unrolled" code into the JSON-based pattern countless times. There are many, many reasons why having the navigation items as a dataset becomes necessary.
This mostly comes down to the fact that the "route" configuration becomes a high-level global configuration, and you can't have the low-level navigation UI become the source of truth. So instead, the source of truth needs to be a high-level shared value, and the navigation UI needs to pull from the source of truth. This result feels pretty inevitable for most projects that grow and grow, so for seasoned developers this becomes a pattern that we follow because we're really unconcerned about your core argument here.