Handling a universal breadcrumb in a flux application with react-breadcrumbs

If you are using react-router, the odds are that you have already tried react-breadcrumbs to handle breadcrumbs based on your routing configuration, which can come in handy.

But what if your application is not a SPA and part of your routing (and your breadcrumb) happens server-side? This is what we will achieve today with some dirty DOM magic.

First, let's consider our HTML page:

<!DOCTYPE html>
<html>

<head>
<!-- ... -->
</head>

<body>
<div class="breadcrumb"><!-- server-side breadcrumb --></div>
<div id="react"><!-- React app will be rendered here --></div>
<!-- scripts, etc. -->
</body>

And here is our root component:

'use strict';

let React = require('react'),
Breadcrumb = require('react-breadcrumbs');

module.exports = React.createClass({
/**
* @return {Object}
*/
render: function() {
return (
<div className="container">
<Breadcrumb routes={this.props.routes} />
{this.props.children}
</div>
);
}
});

The Breadcrumb JSX tag will be rendered as our client-side breadcrumb; what we rather want is to have both breadcrumbs concatenated in our div.breadcrumb.

Let's make some additions to the root component:

'use strict';

let React = require('react'),
Breadcrumb = require('react-breadcrumbs');

module.exports = React.createClass({
/**
* Maintains breadcrumb up-to-date with dynamic, react-router-powered part
*
* The actual breadcrumb is outside React's scope, so we basically render
* a hidden breadcrumb with the aforementioned part and copy its contents
* at the end of it after wiping it clean, all through DOM manipulation.
*/
updateBreadcrumb: function() {
let breadcrumb = document.querySelector('.breadcrumb');

// We use React's own specific HTML attributes to clear the breadcrumb from previous additions
Array.prototype.slice.call(document.querySelectorAll('.breadcrumb > [data-reactid]')).forEach(function(toRemove) {
breadcrumb.removeChild(toRemove);
}, 0);

breadcrumb.innerHTML += document.querySelector('.breadcrumbHolder').innerHTML;
},

componentDidUpdate: function() {
// Trigger breadcrumb update everytime our route changes
this.updateBreadcrumb();
},

/**
* @return {Object}
*/
render: function() {
return (
<div className="container">
{/* Hide client-side breadcrumb and prevent it from explicitly displaying missing segments, if any */}
<Breadcrumb routes={this.props.routes} customClass="breadcrumbHolder hide" displayMissing={false} />
{this.props.children}
</div>
);
}
});

And it works! This is a total hack, but React's very own philosophy prevents us from more interaction with a preexistant DOM, at least for now. Stay tuned!