I was recently building a breadcrumb component for a React app, something that looked a bit a like this:
Those ">" symbols are SVG icons, and I have a React component for it called BreadcrumbIcon
.
function BreadcrumbIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="18" width="18"><path d="M12.1717 12.0005L9.34326 9.17203L10.7575 7.75781L15.0001 12.0005L10.7575 16.2431L9.34326 14.8289L12.1717 12.0005Z"></path></svg>
);
};
I use it like this:
function Breadcrumb(props) {
return (
<div>Home <BreadcrumbIcon /> Account <BreadcrumbIcon /> Settings</div>
);
};
And that's perfect for one breadcrumb. But I want to reuse the Breadcrumb
component throughout the app, so instead of hard coding the crumbs, I need to pass in a "crumbs" prop so I can return the right paths for each page.
I'll pass the crumbs prop as an array.
<Breadcrumb crumbs={[ "Home", "Account", "Settings" ]} />
And then I can join the array together, with the divider icon in between each item.
function Breadcrumb({ crumbs }) {
return (
// join the crumbs
);
};
Why array.join()
doesn’t work
With JavaScript, the first tool that comes to mind for joining an array is the .join
array method (obviously!). But it won't work here.
Because Array.join()
only works with strings.
And my breadcrumb icon <BreadcrumbIcon />
isn't a string. It's a React component, and it returns JSX.
Let's see what happens when we try to use Array.join()
.
function Breadcrumb({ crumbs }) {
return (
crumbs.join(<BreadcrumbIcon />)
);
};
// Home[object Object]Account[object Object]Settings
😬 [object Object]
Nope. Array.join() doesn't work with objects, and it certainly doesn't work with React components. If it's not a string, it's not going to work.
As an easy fix, I could change the divider to a string (e.g. ">") and get this to work. But looking ahead, while my crumbs are strings at the moment (for these examples), they probably won't be in the future. I'll use <Link>
components - so that they are clickable for navigation.
And then I'll run into problems with Array.join()
again.
How to join an array with a React component
Ok, let's figure out how to get this to work as we intended.
We want to:
- Pass in an array (of strings or JSX) as a prop
- Join the array items with a React component as the separator
I'm going to use my favourite Array method .map()
to map over each item in the crumbs
array, and as long as it's not the first item in the array, I will add the separator first (ourBreadcrumbIcon
).
function Breadcrumb({ crumbs }) {
return (
crumbs.map((crumb, index) => (
<React.Fragment key={index}>
{ !!index && <BreadcrumbIcon /> }
{ crumb }
</React.Fragment>
))
);
};
I'm using a <React.Fragment>
here to avoid adding any additional markup (like a <div>
), but if you want to add styles or classes, or want a wrapper for your breadcrumb for other reasons, you can swap out the <React.Fragment>
for a <div>
or other element.
If you are wondering why !!index &&
and not index &&
- using !!
turns index
into a boolean so 0
changes to false
. If I used index &&
instead, I'd get a 0
showing up before the first breadcrumb item!
Now when you use Breadcrumb
with any crumbs, of any length, the BreadcrumbIcon
will display between each item.
<Breadcrumb crumbs={[ "Home", "Account", "Settings" ]} />
<Breadcrumb crumbs={[ "Home", "Super", "Cool", "With a cherry on top" ]} />
How to join children
Maybe you are happy passing crumbs as a prop. I know I am pretty happy with it. But I mentioned that I might want to pass links or React components as my crumbs so that they are clickable and can be used for navigation.
Wouldn't it be cool if we could use the Breadcrumb
component like this:
<Breadcrumb2>
<a href="/home">Home</a>
<a href="/account">Account</a>
<a href="/account/settings">Settings</a>
</Breadcrumb2>
I'm using <a>
elements for this example, but in a real React project, I would probably use React Routers <Link>
component.
I think when I'm passing components or JSX to Breadcrumb
, it looks nicer to pass them as children like in the example above. So let's see how we can make that work.
function Breadcrumb({ children }) {
return (
children.map((crumb, index) => (
<React.Fragment key={index}>
{ !!index && <BreadcrumbIcon /> }
{ crumb }
</React.Fragment>
))
);
};
Well, that was easy! Just switch crumbs
for children
and we are good to go!
And if you want either to work, you can do something like:
function Breadcrumb({ crumbs, children }) {
return (
(children || crumbs).map((crumb, index) => (
<React.Fragment key={index}>
{ !!index && <BreadcrumbIcon /> }
{ crumb }
</React.Fragment>
))
);
};
Now you can pass the crumbs
prop or give your crumbs as children to the component (with children
taking priority).