3

I am trying to implement React Router Breadcrumbs for v4

Following are my routes:

    const routes = {
      '/': 'Home',
      '/page1': 'Page 1',
      '/page2': 'Page 2'
    };

I could put the breadcrumbs using this library in my application, however I am having following questions:

Que. #1:

When I click on Home in my breadcrumbs, I can see the URL changes to http://localhost:8080 However, browser still shows the same page I am on.

Que. #2:

When I navigate to Page2 from Page1, url changes from http://localhost:8080/page1 to http://localhost:8080/page2.

So the breadcrumbs shown changes to Home / Page 2 instead of changing like Home / Page 1 / Page 2

I know this may be because the url just has /page2 after hostname. But, can I achieve the display like: Home / Page 1 / Page 2?

Below is the code in my main App.jsx:

<Router>
  <div>
    <Link to="/"><div className="routerStyle"><Glyphicon glyph="home" /></div></Link>
    <Route exact path="/" component={LandingPage}/>
    <Route path="/page1" component={Page1}/>
    <Route path="/page2" component={Page2}/>
  </div>
</Router>

and if I use like belowto cater for breadcrumbs, then my page2 gets rendered below page1 stuff:

    <Router>
      <div>
        <Link to="/"><div className="routerStyle"><Glyphicon glyph="home" /></div></Link>
        <Route exact path="/" component={LandingPage}/>
        <Route path="/page1" component={Page1}/>
        <Route path="/page1/page2" component={Page2}/>
      </div>
    </Router>

Answer:

Que. #1: No need to wrap <Breadcrumbs ..../> element inside <Router> element inside each Component of application. This may be because, inclusion of <Router> element inside each Component leads to "nesting" of Router elements (note we have Router tag in landing page as well); which does not work with react router v4.

Que. #2: Refer to answer formally marked here (answered by palsrealm below)

Akshay Lokur
  • 6,680
  • 13
  • 43
  • 62

3 Answers3

2

Your breadcrumbs are based on links and they work as designed. To display the pages, you need to set up a Switch with Routes in it which would load the appropriate components when the path changes. Something like

<Switch> 
    <Route path='/' component={Home}/>
    <Route path='/page1' component={Page1}/>
    <Route path='/page2' component={Page2}/>
</Switch>

If you want the breadcrumb to show Home/Page1/Page2 your routes should be '/page1/page2' : 'Page 2'. The Route should also change accordingly.

Edit: Your Router should be

 <Router>
      <div>
        <Link to="/"><div className="routerStyle"><Glyphicon glyph="home" /></div></Link>
        <Switch>
        <Route exact path="/" component={LandingPage}/>
        <Route exact path="/page1" component={Page1}/>
        <Route path="/page1/page2" component={Page2}/>
        </Switch>
      </div>
    </Router>
palsrealm
  • 5,083
  • 1
  • 20
  • 25
  • I do not think that is something you will get out of the box. You would have to implement the logic yourself; maybe by storing the last url visited and adding it to the breadcrumbs manually using a Link or NavLink component. – palsrealm Oct 11 '17 at 15:41
  • 3
    @palsrealm is right. You need a `Switch`. Also, if you want to capture _both_ '/page1' and '/page1/page2', you'll need `exact` on the route for '/page1'. – Luke Willis Oct 11 '17 at 15:48
  • 2
    Updated my answer with the code for the `Router` that you would want to use. Exactly as specified by @Luke M Willis – palsrealm Oct 11 '17 at 15:51
  • Thanks, now with use of "exact", the problem of having both pages render in one screen is resolved. However, when I click on breadcrumb, just the browser URL changes but not the contents. Any clue? – Akshay Lokur Oct 11 '17 at 18:33
  • when the browser url changes as expected, this means your links are working fine. When the contents do not render, this means the component that is supposed to render at that url is not rendering and you need to check the component and the route. – palsrealm Oct 11 '17 at 18:36
  • Worked, I have explained solution to problem #1 faced, in my updated question above – Akshay Lokur Oct 11 '17 at 19:11
2

This can also be accomplished with a HOC which would allow you to use a route config object to set breadcrumbs instead. I've open-sourced it here, but the source code is below as well:

Breadcrumbs.jsx

import React from 'react';
import { NavLink } from 'react-router-dom';
import { withBreadcrumbs } from 'withBreadcrumbs';

const UserBreadcrumb = ({ match }) =>
  <span>{match.params.userId}</span>; // use match param userId to fetch/display user name

const routes = [
  { path: 'users', breadcrumb: 'Users' },
  { path: 'users/:userId', breadcrumb: UserBreadcrumb},
  { path: 'something-else', breadcrumb: ':)' },
];

const Breadcrumbs = ({ breadcrumbs }) => (
  <div>
    {breadcrumbs.map(({ breadcrumb, path, match }) => (
      <span key={path}>
        <NavLink to={match.url}>
          {breadcrumb}
        </NavLink>
        <span>/</span>
      </span>
    ))}
  </div>
);

export default withBreadcrumbs(routes)(Breadcrumbs);

withBreadcrumbs.js

import React from 'react';
import { matchPath, withRouter } from 'react-router';

const renderer = ({ breadcrumb, match }) => {
  if (typeof breadcrumb === 'function') { return breadcrumb({ match }); }
  return breadcrumb;
};

export const getBreadcrumbs = ({ routes, pathname }) => {
  const matches = [];

  pathname
    .replace(/\/$/, '')
    .split('/')
    .reduce((previous, current) => {
      const pathSection = `${previous}/${current}`;

      let breadcrumbMatch;

      routes.some(({ breadcrumb, path }) => {
        const match = matchPath(pathSection, { exact: true, path });

        if (match) {
          breadcrumbMatch = {
            breadcrumb: renderer({ breadcrumb, match }),
            path,
            match,
          };
          return true;
        }

        return false;
      });

      if (breadcrumbMatch) {
        matches.push(breadcrumbMatch);
      }

      return pathSection;
    });

  return matches;
};

export const withBreadcrumbs = routes => Component => withRouter(props => (
  <Component
    {...props}
    breadcrumbs={
      getBreadcrumbs({
        pathname: props.location.pathname,
        routes,
      })
    }
  />
));
Justin Schrader
  • 201
  • 1
  • 3
  • 7
1

The following component should return a breadcrumb at any depth, except on the home page (for obvious reasons). You won't need React Router Breadcrumb. My first public contribution, so if I'm missing an essential part, it would be great if somebody could point it out. I added &raquo; for crumbs splits, but you can obviously update that to match what you need.

import React from 'react'
import ReactDOM from 'react-dom'
import { Route, Link } from 'react-router-dom'
// styles
require('./styles/_breadcrumbs.scss')

// replace underscores with spaces in path names
const formatLeafName = leaf => leaf.replace('_', ' ')

// create a path based on the leaf position in the branch
const formatPath = (branch, index) => branch.slice(0, index + 1).join('/')

// output the individual breadcrumb links
const BreadCrumb = props => {
  const { leaf, index, branch } = props,
    leafPath = formatPath(branch, index),
    leafName = index == 0 ? 'home' : formatLeafName(leaf),
    leafItem =
      index + 1 < branch.length 
        ? <li className="breadcrumbs__crumb">
          <Link to={leafPath}>{leafName}</Link>
          <span className="separator">&raquo;</span>
        </li>
        : <li className="breadcrumbs__crumb">{leafName}</li>
  // the slug doesn't need a link or a separator, so we output just the leaf name

  return leafItem
}

const BreadCrumbList = props => {
  const path = props.match.url,
    listItems =
      // make sure we're not home (home return '/' on url)
      path.length > 1
      && path
        // create an array of leaf names
        .split('/')
        // send our new array to BreadCrumb for formating
        .map((leaf, index, branch) => 
          <BreadCrumb leaf={leaf} index={index} branch={branch} key={index} />
        )

  // listItem will exist anywhere but home
  return listItems && <ul className="breadcrumbs">{listItems}</ul>
}

const BreadCrumbs = props => 
  <Route path="/*" render={({ match }) => <BreadCrumbList match={match} />} />


export default BreadCrumbs
FranCarstens
  • 1,203
  • 8
  • 13