React-router: Breadcrumbs Example in V4 Documentation

Created on 20 Feb 2017  路  31Comments  路  Source: ReactTraining/react-router

Could you add a breadcrumbs example to the V4 documentation? It was provided in the previous version. Thanks.

Most helpful comment

@shawnmclean, thank you.

A little cleaned and added a link Home in the beginning of breadcrumbs

import React from 'react';
import { Route, Link } from 'react-router-dom';
import { Breadcrumb, BreadcrumbItem } from 'reactstrap';

const routes = {
  '/': 'Home',
  '/settings': 'Settings',
  '/settings/a': 'A',
  '/settings/a/b': 'B',
};

const findRouteName = url => routes[url];

const getPaths = (pathname) => {
  const paths = ['/'];

  if (pathname === '/') return paths;

  pathname.split('/').reduce((prev, curr, index) => {
    const currPath = `${prev}/${curr}`;
    paths.push(currPath);
    return currPath;
  });

  return paths;
};

const BreadcrumbsItem = ({ ...rest, match }) => {
  const routeName = findRouteName(match.url);
  if (routeName) {
    return (
      match.isExact ?
      (
        <BreadcrumbItem active>{routeName}</BreadcrumbItem>
      ) :
      (
        <BreadcrumbItem>
          <Link to={match.url || ''}>
            {routeName}
          </Link>
        </BreadcrumbItem>
      )
    );
  }
  return null;
};

const Breadcrumbs = ({ ...rest, location : { pathname }, match }) => {
  const paths = getPaths(pathname);
  return (
    <Breadcrumb>
      {paths.map(p => <Route path={p} component={BreadcrumbsItem} />)}
    </Breadcrumb>
  );
};

export default props => (
  <div>
    <Route path="/:path" component={Breadcrumbs} {...props} />
  </div>
);

```

All 31 comments

You can adapt the code from the recursive path example to do this: https://reacttraining.com/react-router/examples/recursive-paths

@msaron Maybe something like this can do a job

const Breadcrumbs = (props) => (
    <div className="breadcrumbs">
        <ul className='container'>
            <Route path='/:path' component={BreadcrumbsItem} />
        </ul>
    </div>
)

const BreadcrumbsItem = ({ ...rest, match }) => (
    <span>
        <li className={match.isExact ? 'breadcrumb-active' : undefined}>
            <Link to={match.url || ''}>
                {match.url}
            </Link>
        </li>
        <Route path={`${match.url}/:path`} component={BreadcrumbsItem} />
    </span>
)

@garmjs have you figured out a way to do this without recursion? Trying to get this matched up with bootstrap breadcrumb styling, they expect the items to be siblings of each other.

Some late night coding that seems to work so far with bootstrap 4 breadcrumb styles.

Would like some feedback and suggestion on code cleanup if any.

Note: utils/routing is basically a lookup table that maps the route path to a name.

import React from 'react'
import { Route, Link } from 'react-router-dom'
import { findRouteName } from 'utils/routing'

export default (props) => (
  <div>
    <Route path='/:path' component={Breadcrumbs} />
  </div>
)

const BreadcrumbsItem = ({ ...rest, match }) => {
  const routeName = findRouteName(match.url)
  if (routeName) {
    return (
        match.isExact
          ? <span className='breadcrumb-item active'>{routeName}</span>
          : <Link className='breadcrumb-item' to={match.url || ''}>{routeName}</Link>
    )
  }
  return null
}

const Breadcrumbs = ({ ...rest, location : { pathname }, match }) => {
  const paths = []
  pathname.split('/').reduce((prev, curr, index) => {
    paths[index] = `${prev}/${curr}`
    return paths[index]
  })
  return (
    <nav className='breadcrumb'>
      {paths.map(p => <Route path={p} component={BreadcrumbsItem} />)}
    </nav>
  )
}

@shawnmclean it looks pretty clean to me, and the solution works , go for it 馃憤

@shawnmclean, thank you.

A little cleaned and added a link Home in the beginning of breadcrumbs

import React from 'react';
import { Route, Link } from 'react-router-dom';
import { Breadcrumb, BreadcrumbItem } from 'reactstrap';

const routes = {
  '/': 'Home',
  '/settings': 'Settings',
  '/settings/a': 'A',
  '/settings/a/b': 'B',
};

const findRouteName = url => routes[url];

const getPaths = (pathname) => {
  const paths = ['/'];

  if (pathname === '/') return paths;

  pathname.split('/').reduce((prev, curr, index) => {
    const currPath = `${prev}/${curr}`;
    paths.push(currPath);
    return currPath;
  });

  return paths;
};

const BreadcrumbsItem = ({ ...rest, match }) => {
  const routeName = findRouteName(match.url);
  if (routeName) {
    return (
      match.isExact ?
      (
        <BreadcrumbItem active>{routeName}</BreadcrumbItem>
      ) :
      (
        <BreadcrumbItem>
          <Link to={match.url || ''}>
            {routeName}
          </Link>
        </BreadcrumbItem>
      )
    );
  }
  return null;
};

const Breadcrumbs = ({ ...rest, location : { pathname }, match }) => {
  const paths = getPaths(pathname);
  return (
    <Breadcrumb>
      {paths.map(p => <Route path={p} component={BreadcrumbsItem} />)}
    </Breadcrumb>
  );
};

export default props => (
  <div>
    <Route path="/:path" component={Breadcrumbs} {...props} />
  </div>
);

```

Hi! @slava-viktorov
I have implemented your code but If I'm in the home route I don't get anything rendered

const Breadcrumbs = ({ ...rest, location : { pathname }, match }) => {
  console.log('Inside Breadcrumbs');

  const paths = getPaths(pathname);
  return (
    <ol className="breadcrumb page-breadcrumb">
      {paths.map(p => <Route path={p} component={BreadcrumbsItem} />)}
    </ol>
  );
};

In this modded Breadcrumbs function, I have added a simple console log. If I visit http://localhost:3000/ I don't get anything, but If I visit http://localhost:3000/users I get Home/Users as expected.

How can I get a Home breadcrumb item on home route?

Hi, @mgscreativa.
Breadcrumbs are not a classic menu. They show parts of the navigation path the user has moved.
What do you need to show on the home routing?
If I understood you correctly.

It would be nice to display the breadcrumbs code with a "Home" text with no link in the home route. At least is what I expect, is that possible?

@mgscreativa, In my code, the home page displays breadcrumbs with the text Home without a link.

Hi @slava-viktorov Just tested and I don't get anything if I'm at home

Accessing home (http://localhost:3000/) I get:

<div><!-- react-empty: 7 --></div>

Accessing /users (http://localhost:3000/users) I get:

<div>
  <ol class="breadcrumb page-breadcrumb">
    <li>
      <a href="/">Inicio</a>
    </li>
    <li class="active">Usuarios</li>
  </ol>
</div>

This is my actual code

import React from 'react';
import { PropTypes } from 'prop-types';
import { withRouter, Route, Link } from 'react-router-dom';

const routeNames = {
  '/': 'Inicio',
  '/users': 'Usuarios',
};

const findRouteName = url => (
  routeNames[url]
);

const getPaths = (pathname) => {
  const paths = ['/'];

  if (pathname === '/') return paths;

  pathname.split('/').reduce((prev, curr, index) => {
    const currPath = `${prev}/${curr}`;
    paths.push(currPath);
    return currPath;
  });

  return paths;
};

const BreadcrumbsItem = ({ match }) => {
  const routeName = findRouteName(match.url);

  if (routeName) {
    return (
      match.isExact ?
        (
          <li className="active">{routeName}</li>
        ) :
        (
          <li>
            <Link to={match.url || ''}>
              {routeName}
            </Link>
          </li>
        )
    );
  }
  return null;
};

const BreadcrumbsBase = ({ ...rest, location: { pathname } }) => {
  const paths = getPaths(pathname);

  return (
    <ol className="breadcrumb page-breadcrumb">
      {paths.map(p => <Route {...rest} key={p} path={p} component={BreadcrumbsItem} />)}
    </ol>
  );
};

const Breadcrumbs = (props) => {
  return (
    <div>
      <Route path="/:path" component={BreadcrumbsBase} {...props} />
    </div>
  );
};

BreadcrumbsItem.defaultProps = {
  match: null,
};

BreadcrumbsItem.propTypes = {
  match: PropTypes.object,
};

BreadcrumbsBase.defaultProps = {
  location: null,
};

BreadcrumbsBase.propTypes = {
  location: PropTypes.object,
};

Breadcrumbs.defaultProps = {
  location: null,
};

Breadcrumbs.propTypes = {
  location: PropTypes.object,
};

export default withRouter(Breadcrumbs);

I was struggling with this as well. @slava-viktorov's approach didn't seem to work for dynamic urls like /user/:id, so I modified it to work for my needs. Maybe @mgscreativa or someone else will find it useful, so I created an example repo. This is the gist of it:

import React from 'react';
import { Link, withRouter } from 'react-router-dom';
import Route from 'route-parser';

const isFunction = value => typeof value === 'function';

const getPathTokens = pathname => {
  const paths = ['/'];

  if (pathname === '/') return paths;

  pathname.split('/').reduce((prev, curr) => {
    const currPath = `${prev}/${curr}`;
    paths.push(currPath);
    return currPath;
  });

  return paths;
};

function getRouteMatch(routes, path) {
  return Object.keys(routes)
    .map(key => {
      const params = new Route(key).match(path);
      return {
        didMatch: params !== false,
        params,
        key
      };
    })
    .filter(item => item.didMatch)[0];
}

function getBreadcrumbs({ routes, match, location }) {
  const pathTokens = getPathTokens(location.pathname);
  return pathTokens.map((path, i) => {
    const routeMatch = getRouteMatch(routes, path);
    const routeValue = routes[routeMatch.key];
    const name = isFunction(routeValue)
      ? routeValue(routeMatch.params)
      : routeValue;
    return { name, path };
  });
}

function Breadcrumbs({ routes, match, location }) {
  const breadcrumbs = getBreadcrumbs({ routes, match, location });

  return (
    <div>
      Breadcrumb:
      {breadcrumbs.map((breadcrumb, i) =>
        <span key={breadcrumb.path}>
          <Link to={breadcrumb.path}>
            {breadcrumb.name}
          </Link>
          {i < breadcrumbs.length - 1 ? ' / ' : ''}
        </span>
      )}
    </div>
  );
}

export default withRouter(Breadcrumbs);

Breadcrumbs can then be added like

import Breadcrumb from './Breadcrumb'

// routes must contain the route pattern as key, and the route label as value
// label can be either a simple string, or a handler which receives
// the matched params as arguments
const routes = {
  '/': 'Home',
  '/about': 'About',
  '/topics': 'Topics',
  '/topics/:topicId': params => params.topicId
};

...

<Breadcrumb routes={routes} />

Hi @sqren Your approach may be rigth but I think that a real breadcrumbs component should't need to fill the routes object or any config, maybe will take the route names from the URL...

@mgscreativa I agree that it would be better, if the routes didn't have to be duplicated, and could instead come from the original react-router config.
However, it is only for the most simple use-cases, that you can write the breadcrumb completely from the url. Eg: users/1337 should result in the breadcrumb:

Users -> John Doe

@sqren Code seems to be working well with dynamic URLs. I used in my project till now no issue :+1:
Today, I got the new case where I need to show text on breadcrumb links instead of id/anything that is in the url.
Let's consider, Device module i.e device = { id : 0, name : X-Ray', IP: '10.0.0.102' }
So over here, I want device IP address on the breadcrumb link instead of id text.
This can be achieved if I pass directly IP address as part of URL but I do not want to do that. Which kind of weird if I pass url.

Any thought on this?

Is there a "cut and dry ready" example for react-router@~v3 instead of react-router-dom ?

Thanks @sqren for putting me on the right track! I was having trouble with the following use case where the user models need to be fetched and populated into our redux store before it can be displayed:

url: /user/75e6755b-d798-4c83-9dab-5cab4cad157d
breadcrumbs: user > John Doe

Plus I wasn't a fan of the extra route-parser dependency. So I decided to take a slightly more component-based approach:

Breadcrumbs.jsx

import React from 'react';
import PropTypes from 'prop-types';
import { matchPath, withRouter } from 'react-router';
import { NavLink, Route } from 'react-router-dom';

const DEFAULT_MATCH_OPTIONS = { exact: true };

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

  pathname
    .replace(/\/$/, '')
    .split('/')
    .reduce((previous, current) => {
      const pathSection = `${previous}/${current}`;
      const match = routes.find(({ matchOptions, path }) =>
        matchPath(pathSection, { ...(matchOptions || DEFAULT_MATCH_OPTIONS), path }));

      if (match) {
        matches.push({
          component: <Route {...match} />,
          path: pathSection,
        });
      }

      return pathSection;
    });

  return matches;
};

export const PureBreadcrumbs = ({ dividerComponent, location, routes }) => {
  const breadcrumbs = getBreadcrumbs({ location, routes });

  return (
    <div>
      {breadcrumbs.map(({ component, path }, index) => (
        <span key={path}>
          <NavLink to={path}>
            {component}
          </NavLink>
          {!!(index < breadcrumbs.length - 1) && dividerComponent()}
        </span>
      ))}
    </div>
  );
};

PureBreadcrumbs.propTypes = {
  dividerComponent: PropTypes.node,
  location: PropTypes.shape().isRequired,
  routes: PropTypes.arrayOf(PropTypes.shape({
    path: PropTypes.string.isRequired,
    render: PropTypes.func.isRequired,
    matchOptions: PropTypes.shape(),
  })).isRequired,
};

PureBreadcrumbs.defaultProps = {
  dividerComponent: () => '/',
};

export default withRouter(PureBreadcrumbs);

Parent.jsx

// ...
  const PureUserBreadcrumb = ({ name }) => <span>{name}</span>;
  const UserBreadcrumb = compose(
    withRouteParam('userId'),
    withUser,
  )(PureUserBreadcrumb);

  export const routes = [
    { path: '/users', render: () => 'Goals' },
    { path: '/users/:userId', render: props => <UserBreadcrumb {...props} /> },
  ];

  <Breadcumbs routes={routes} />
// ...

^ results in / Users / John Doe

Note: I like to use recompose & HOCs (withRouteParam and withUser), but the point is: in your breadcrumb components you can take react-router params and render whatever you like based on them.

I'm going to clean up and optimize this code, but in the meantime it seems to be working well.

Thoughts?

@icd2k3 Just skimming through it, I like your suggestion a lot more than mine. Especially that you use matchPath from react-router, and that individual breadcrumbs can be react components.
Nice work!

I'll try it out for my use case later this week, and will let you know if I have any feedback.

@sqren just a heads up that I open-sourced my solution here if you're interested!

@icd2k3 I use your solution in my production app.

@icd2k3 Great solution, declarative and simple 馃憤 馃嵑

Take a look at react-breadcrumbs-dynamic. Just declare breadcrumb items as you generally declare any other components and that is all.

Can you return back v3's routes prop and give us the list of routes?

Look at beautiful ant.design's breadcrumb integration with react-router@2 and react-router@3... Now it is broken.

It's very important to pass the router name (Users -> John Smith) as a route's prop for further using it in the breadcrumb.

Will you do it?

@hosembafer there are a number of solutions in this thread and open source modules to do this for you. I don鈥檛 think it鈥檚 something react-router needs to support out-of-the-box because not everyone uses breadcrumbs in their app.

react-router-breadcrumbs-hoc and react-breadcrumbs-dynamic should both work for your case

It's the different things, I can do like you say, but it can't concurrent in with react-router old version's method.

+Now I have a backward compatibility issue.

In my React App architecture in each level of routing, I'm just added the name of the entity to and it's all... now I need to include and implement third-party package to do this. I think this issue not only my. And I don't know why it cleared from v4.

The react-router is not an library for communication between components. It allows or does not allow you to do some things which may be considered as communication possibility. If you want suggests some communication features to react-router, may be better to create separated ticket with details about that.

If you create an application, you select the libraries that you want to use (they are all third-party, except yours). You choose a way to communicate between your application subsystems. You can choose what you like, react-redux, react-through, etc, or write it manually.

If you write a library of components and tools. You can provide components wich allows to build some user interface features (for example breadrumbs and its items). Also you can provide tools how to integrate your components in application and organize communication between parts of application and also suggest to use another ways which possible to use. Users can choose tools for integrating application and communication that they like.

I'm not about communication.

They are not all third-party in my context. I have a standard stack of packages which I use in every project. react, react-router, antd components etc. From which I solve all my needs. But react-router@4 has removed a very useful feature, and now I want to know why, and maybe it is possible to rollback it.

When a router can return path of routing endpoint, it's useful info and can be used in other components, but now, I need to manage my routes state manually for passing into breadcrumb.

In old versions, if I have a router, so automatically I have breadcrumb based on a router, but now router doesn't provide API, and I want to know why backward compatibility is broken. Maybe it is a bug?

React components in application is isolated from each other. When you need to pass some data from one to another you can specify props, use context, or some another way may be a library. These are ways to trasfer data through react tree or communication between components.

Before react-router have to distribute configuration of routes through react tree. It was possible to put some data into that structure and extract some data from it using this as data communication way.

But React Router is a collection of navigational components that compose declaratively with your application - from the intro of its documentatin. Data communications or some state distribution is not declared.

You can choose which solutions to use. You may use some communication way to transfer some data. Or you can use breadcrumbs solutions without data transfer wich makes as much as possible without data transfer but only based on router information which is currently provided.

There two question: Is it possible to do by react-router internals to build configuration structure of all routes and transfer it through react tree? And would developers planing to add this feature with possibility to add user defined data to that structure into future releases? This questions is another topic but not this topic where breadcrumbs is discussed. It may be not visible to developers in huge of closed tickets. If it will be suggested as separated issue then more probably to be noticed and answered. This ticket it only use case and good reference to that ticket and questions.

Thank you for the detailed explanation. I got you.

@shawnmclean if you want the breadcrumbs items as sibling you can do this

const Breadcrumbs = (props) => (
    <div className="breadcrumbs">
        <ul className='container'>
            <Route path='/:path' component={BreadcrumbsItem} />
        </ul>
    </div>
)

const BreadcrumbsItem = ({ ...rest, match }) => (
    <>
        <li className={match.isExact ? 'breadcrumb-active' : undefined}>
            <Link to={match.url || ''}>
                {match.url}
            </Link>
        </li>
        <Route path={`${match.url}/:path`} component={BreadcrumbsItem} />
    </>
)

This will work React 16 onwards as I'm using <>

Was this page helpful?
0 / 5 - 0 ratings

Related issues

hgezim picture hgezim  路  3Comments

ArthurRougier picture ArthurRougier  路  3Comments

wzup picture wzup  路  3Comments

nicolashery picture nicolashery  路  3Comments

misterwilliam picture misterwilliam  路  3Comments