Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Breadcrumbs Example in V4 Documentation #4556

Closed
msaron opened this issue Feb 19, 2017 · 31 comments
Closed

Breadcrumbs Example in V4 Documentation #4556

msaron opened this issue Feb 19, 2017 · 31 comments

Comments

@msaron
Copy link

msaron commented Feb 19, 2017

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

@timdorr
Copy link
Member

timdorr commented Feb 20, 2017

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

@timdorr timdorr closed this as completed Feb 20, 2017
@garmjs
Copy link

garmjs commented Apr 7, 2017

@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>
)

@shawnmclean
Copy link
Contributor

@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.

@shawnmclean
Copy link
Contributor

shawnmclean commented Apr 9, 2017

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>
  )
}

@garmjs
Copy link

garmjs commented Apr 9, 2017

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

@slava-viktorov
Copy link

@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>
);

@mgscreativa
Copy link

mgscreativa commented Jun 9, 2017

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?

@slava-viktorov
Copy link

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.

@mgscreativa
Copy link

mgscreativa commented Jun 9, 2017

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?

@slava-viktorov
Copy link

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

@mgscreativa
Copy 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);

@sorenlouv
Copy link

sorenlouv commented Jul 23, 2017

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} />

@mgscreativa
Copy link

mgscreativa commented Jul 28, 2017

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...

@sorenlouv
Copy link

@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

@ni3galave
Copy link

@sqren Code seems to be working well with dynamic URLs. I used in my project till now no issue 👍
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?

@patvdleer
Copy link

patvdleer commented Oct 9, 2017

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

@icd2k3
Copy link

icd2k3 commented Oct 25, 2017

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?

@sorenlouv
Copy link

sorenlouv commented Oct 26, 2017

@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.

@icd2k3
Copy link

icd2k3 commented Nov 1, 2017

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

@liberium
Copy link

liberium commented Nov 2, 2017

@icd2k3 I use your solution in my production app.

@export-mike
Copy link

@icd2k3 Great solution, declarative and simple 👍 🍺

@oklas
Copy link

oklas commented Mar 1, 2018

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

@hosembafer
Copy link

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?

@icd2k3
Copy link

icd2k3 commented Mar 29, 2018

@hosembafer there are a number of solutions in this thread and open source modules to do this for you. I don’t think it’s 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

@hosembafer
Copy link

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.

@oklas
Copy link

oklas commented Mar 30, 2018

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.

@hosembafer
Copy link

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.

@hosembafer
Copy link

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?

@oklas
Copy link

oklas commented Mar 31, 2018

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.

@hosembafer
Copy link

Thank you for the detailed explanation. I got you.

@itaditya
Copy link

itaditya commented May 8, 2018

@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 <>

@lock lock bot locked as resolved and limited conversation to collaborators Jul 7, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests