Simple Breadcrumbs in React with Reach-Router [Tutorial]

Written by josejaviasilis | Published 2020/08/06
Tech Story Tags: react | react-router | javascript | single-page-web-applications | ux | ui | tutorial | web-development | web-monetization

TLDR React 16.13 (could be lower), TypeScript, and Reach-Router 1.3.x.x. I'll be using CSS-In-JS (Not required) and React 16.13. No hooks are needed, but components are functional. You can use any CSS-in-JS approach to display breadcrumbs in React. Re-Reach Router allows us to have as many Routers as we'd like. By specifying it with the main navigation, we'll be calling it 'Reach-router'via the TL;DR App

One of the things that you end up developing in one point or in the other is a breadcrumbs navigation system. I've seen some posts across the web touting how to achieve it in React and Reach Router by providing complex looping mechanisms. In this post, I show you a simpler, non loop way that displays breadcrumbs in Reach-Router.
I'll be using CSS-In-JS (Not required), React 16.13 (could be lower), TypeScript, and Reach-Router 1.3.x. No hooks are needed, but components are functional.
Here's all the code that you'll need.

type BreadrumbsProps = {
    url: string;
    text: string;
    className?: string;
  };
  
  // This is material-ui. You can use any CSS-In-JS approach. 
  const useStyles = makeStyles(() => ({
    root: {
      display: 'block',
    },
    right: {
      float: 'right',
    },
    left: {
      float: 'left',
    },
    clear: {
      clear: 'both',
    },
    separator: {
      padding: '6px',
    },
  }));
  
  export const AppBreadcrumbs: React.FC<RouteComponentProps> = memo((props) => {
    const styles = useStyles();
    return (
      <>
        <Router primary={false}>
          <Bread path="/" url={'/'} text={'Home'} className={styles.left}>
            <Bread
              path={"/evaluation"}
              url={"/evaluation"}
              text={'Evaluation'}
            >
              <Bread
                path="instruments"
                url={'/evaluation/instruments'}
                text={'Instruments'}
              >
                <Bread
                  path=":classId/:period/:instrument/*"
                  url={'../'}
                  text={'Period'}
                ></Bread>
              </Bread>
              <Bread
                path="indicators"
                url={'/evaluation/indicators'}
                text={'Indicators'}
              ></Bread>
            </Bread>
            <Bread
              path={"/planning" + '/*'}
              url={"/planning"}
              text={'Indicators'}
            ></Bread>
            <Bread
              path={"/attendance" + '/*'}
              url={"/attendance"}
              text={'Asistencia'}
            ></Bread>
            <Bread
              path={"/scores" + '/*'}
              url={"/scores"}
              text={'Calificaciones'}
            ></Bread>
            <Bread
              path={"/content" + '/*'}
              url={"/content"}
              text={'Contenido de Clases'}
            ></Bread>
          </Bread>
        </Router>
        <div className={styles.clear} />
      </>
    );
  });
  
  export default AppBreadcrumbs;
  
  const Bread: React.FC<RouteComponentProps & BreadrumbsProps> = memo((props) => {
    const styles = useStyles();
    const shouldRenderCrumb = !props.location?.pathname.endsWith(
      props.path || '',
    );
    return (
      <div className={props.className}>
        <LinkButton size="small" variant="text" to={props.url}>
          {props.text}
        </LinkButton>
        {shouldRenderCrumb && React.Children.count(props.children) > 0 && (
          <div className={cls(styles.right)}>
            <div className={cls(styles.left, styles.separator)}>{'>'}</div>
  
            <div className={styles.left}>{props.children}</div>
          </div>
        )}
      </div>
    );
  });
  
You define all of the crumbs inside the
AppBreadcrumbs
component as if it were normal routes.
Here's an example (Sorry it's in Spanish - It's an actual application that we've been working in)
Let's break it down:
Reach-Router allows us to have as many Routers as we'd like. By specifying it with
<Router primary={false}> ... </Router>
we instruct Reach-router not to use the router for main navigation, this allows us to display the inner content of the router.
We also need to create an additional component, which we will be calling it
<Bread/>
because we need to:
  1. Format its content. Reach-router creates all of the inner routes as children wrapped in
    <div>
    s. We need to do some CSS to make them all float next to each other.
  2. Prevent Reach-Router from complaining. Any direct children inside the
    <Router></Router>
    which doesn't have a path attribute will cause Reach-Router to throw an error.
Designing the <Bread> component
  const Bread: React.FC<RouteComponentProps & BreadrumbsProps> = memo((props) => {
    const styles = useStyles();
    const shouldRenderCrumb = !props.location?.pathname.endsWith(
      props.path || '',
    );
    return (
      <div className={props.className}>
        <LinkButton size="small" variant="text" to={props.url}>
          {props.text}
        </LinkButton>
        {shouldRenderCrumb && React.Children.count(props.children) > 0 && (
          <div className={cls(styles.right)}>
            <div className={cls(styles.left, styles.separator)}>{'>'}</div>
  
            <div className={styles.left}>{props.children}</div>
          </div>
        )}
      </div>
    );
  });
Its CSS
  const useStyles = makeStyles(() => ({
    root: {
      display: 'block',
    },
    right: {
      float: 'right',
    },
    left: {
      float: 'left',
    },
    clear: {
      clear: 'both',
    },
    separator: {
      padding: '6px',
    },
  }));
Note that the
<LinkButton>
it's just a component that wraps it in an
<a> 
and
<button>
tags.
Nothing out of the extraordinary happens here. We overcome the limitation of the children in Reach Router by passing its text as props and rendering it into the component.
We also dynamically render the crumb separator or marking (In this case it's just an amersand
>
) by checking whether it doesn't have more children in it, and if the path is successfully matched.
Clearing div
We add a
<div>
with a
clear:both
css property at the very end to prevent elements from stepping next to it.
Some notes in the route
  1. Only use
    /*
    on the route's path if there's no more children, and you'd like for the route to be displayed in all of its descendants (See
    /content
    and
    /scores
    .)
  2. Do not add
    /* 
    at the end of the route's path if you have children. (see the
     /evaluations
    route)
That's it!
Follow me on Instagram, TikTok, Snapchat, Twitter, Facebook @josejaviasilis.

Written by josejaviasilis | Excellent and getting better 🔥🔥🔥! Focused on building tech startups to shape the world and solve big problems 🚀.
Published by HackerNoon on 2020/08/06