I have begun using ReactStrap because it makes it simpler to build common bootstrap elements with React. At the same time, I have started to experiment with the Ramda functional Library to try to make our React/Redux code more declarative.
Recently, a colleague of mine pointed out a way to use Ramda in our Breadcrumb component and I wanted to share.
Breadcrumbs Pre-Ramda
This is the component, pre-Ramda. It was using the Array#map method with an if
statement inside to render the last breadcrumb item without the link. The last breadcumb item also needs the active
property set.
import React from "react";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import { Breadcrumb, BreadcrumbItem } from "reactstrap";
const AppBreadcrumb = ({ breadcrumbs }) => (
<Breadcrumb>
<BreadcrumbItem><a href="/">Home</a></BreadcrumbItem>
{
breadcrumbs.map((bc, i, arr) => {
if (i === arr.length - 1) {
return (<BreadcrumbItem key={bc.path} active>{bc.text}</BreadcrumbItem>);
}
return (
<BreadcrumbItem key={bc.path}>
<Link to={bc.path}>{bc.text}</Link>
</BreadcrumbItem>
);
})
}
</Breadcrumb>
);
AppBreadcrumb.propTypes = {
breadcrumbs: PropTypes.arrayOf(PropTypes.shape({
path: PropTypes.string.isRequired,
text: PropTypes.string.isRequired
})).isRequired
};
export default AppBreadcrumb;
While this code may be simple to the person that wrote it, it will require mental parsing by future readers of the code.
Some confusing things:
- Why is the “
if
” statement that renders the active component first when it is really rendered last? - Why are we evaluating
arr.length
and using an indexi
? (The index really has nothing to do with this code.) - Why is so hard for me to see the
active
in<BreadcrumbItem key={bc.path} active>
? This one of the key distinguishers but I have to mentally parse 3 different variables to grasp this.
Breadcrumbs with Ramda
The component was refactored into two separate component: ActiveBreadcrumbItem
and
LinkedBreadcrumbItem
. These are very simple stateless functional components.
Next we can use two Ramda statements, one to render the linked items and one to render the active. The refactored code looks like this:
import React from "react";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import { Breadcrumb, BreadcrumbItem } from "reactstrap";
const R = require("ramda");
const ActiveBreadcrumbItem = ({ path, text }) => (
<BreadcrumbItem key={path} active>{text}</BreadcrumbItem>
);
const LinkedBreadcrumbItem = ({ path, text }) => (
<BreadcrumbItem key={path}>
<Link to={path}>{text}</Link>
</BreadcrumbItem>
);
const AppBreadcrumb = ({ breadcrumbs }) => (
<Breadcrumb>
<BreadcrumbItem><a href="/">Home</a></BreadcrumbItem>
{R.map(LinkedBreadcrumbItem, R.slice(0, -1, breadcrumbs))}
{R.pipe(R.last, ActiveBreadcrumbItem)(breadcrumbs)}
</Breadcrumb>
);
ActiveBreadcrumbItem.propTypes = {
path: PropTypes.string.isRequired,
text: PropTypes.string.isRequired
};
LinkedBreadcrumbItem.propTypes = ActiveBreadcrumbItem.propTypes;
AppBreadcrumb.propTypes = {
breadcrumbs: PropTypes.arrayOf(PropTypes.shape(ActiveBreadcrumbItem.propTypes)).isRequired
};
export default AppBreadcrumb;
While this code may take a minute to grok, it has the following advantage: