2

I'm trying to get Material-UI Breadcrumbs to work with React-Router. There is an example on the MUI site:

import * as React from 'react';
import Box from '@mui/material/Box';
import List from '@mui/material/List';
import Link, { LinkProps } from '@mui/material/Link';
import ListItem, { ListItemProps } from '@mui/material/ListItem';
import Collapse from '@mui/material/Collapse';
import ListItemText from '@mui/material/ListItemText';
import Typography from '@mui/material/Typography';
import ExpandLess from '@mui/icons-material/ExpandLess';
import ExpandMore from '@mui/icons-material/ExpandMore';
import Breadcrumbs from '@mui/material/Breadcrumbs';
import { Link as RouterLink, Route, MemoryRouter } from 'react-router-dom';

interface ListItemLinkProps extends ListItemProps {
  to: string;
  open?: boolean;
}

const breadcrumbNameMap: { [key: string]: string } = {
  '/inbox': 'Inbox',
  '/inbox/important': 'Important',
  '/trash': 'Trash',
  '/spam': 'Spam',
  '/drafts': 'Drafts',
};

function ListItemLink(props: ListItemLinkProps) {
  const { to, open, ...other } = props;
  const primary = breadcrumbNameMap[to];

  let icon = null;
  if (open != null) {
    icon = open ? <ExpandLess /> : <ExpandMore />;
  }

  return (
    <li>
      <ListItem button component={RouterLink as any} to={to} {...other}>
        <ListItemText primary={primary} />
        {icon}
      </ListItem>
    </li>
  );
}

interface LinkRouterProps extends LinkProps {
  to: string;
  replace?: boolean;
}

const LinkRouter = (props: LinkRouterProps) => (
  <Link {...props} component={RouterLink as any} />
);

export default function RouterBreadcrumbs() {
  const [open, setOpen] = React.useState(true);

  const handleClick = () => {
    setOpen((prevOpen) => !prevOpen);
  };

  return (
    <MemoryRouter initialEntries={['/inbox']} initialIndex={0}>
      <Box sx={{ display: 'flex', flexDirection: 'column', width: 360 }}>
        <Route>
          {({ location }) => {
            const pathnames = location.pathname.split('/').filter((x) => x);

            return (
              <Breadcrumbs aria-label="breadcrumb">
                <LinkRouter underline="hover" color="inherit" to="/">
                  Home
                </LinkRouter>
                {pathnames.map((value, index) => {
                  const last = index === pathnames.length - 1;
                  const to = `/${pathnames.slice(0, index + 1).join('/')}`;

                  return last ? (
                    <Typography color="text.primary" key={to}>
                      {breadcrumbNameMap[to]}
                    </Typography>
                  ) : (
                    <LinkRouter underline="hover" color="inherit" to={to} key={to}>
                      {breadcrumbNameMap[to]}
                    </LinkRouter>
                  );
                })}
              </Breadcrumbs>
            );
          }}
        </Route>
        <Box
          sx={{
            bgcolor: 'background.paper',
            mt: 1,
          }}
          component="nav"
          aria-label="mailbox folders"
        >
          <List>
            <ListItemLink to="/inbox" open={open} onClick={handleClick} />
            <Collapse component="li" in={open} timeout="auto" unmountOnExit>
              <List disablePadding>
                <ListItemLink sx={{ pl: 4 }} to="/inbox/important" />
              </List>
            </Collapse>
            <ListItemLink to="/trash" />
            <ListItemLink to="/spam" />
          </List>
        </Box>
      </Box>
    </MemoryRouter>
  );
}

But this line:

{({ location }) => {

produces error

Type '({ location }: { location: any; }) => Element' is not assignable to type 'ReactNode'

and this line:

        {pathnames.map((value, index) => {

produces error:

'value' is declared but its value is never read.ts(6133) Parameter 'value' implicitly has an 'any' type.

and:

Parameter 'index' implicitly has an 'any' type.

I've investigated similar questions, but either they have the same problem or don't work either. The problems seem to be attributed to out-of-date code.

Can anyone provide a modern solution that works?

2
  • Well, value isn't used so that second issue makes sense. What version of React-Router-DOM are you trying to use? It looks like an older v5 version based on usage. Can you edit to include a complete minimal reproducible example and specify your dependencies' versions. See also if you can create a running CodeSandbox demo that reproduces the issue that readers can inspect live. Commented Feb 24 at 20:52
  • "react-router-dom": "^7.1.3", I'll try to figure out CodeSandbox Commented Feb 24 at 22:25

1 Answer 1

1

If you are using React-Router 6 or newer then your code is missing a Routes component wrapping any/all Route components, and Route components can only have React.Fragment and other Route components as children. When I copy your code into a running sandbox and get all the dependencies set I see the expected errors about the invalid components usage.

You should refactor the anonymous function into a standalone React component.

Create a new component, i.e. BreadcrumbsComponent, and use the useLocation hook to access the current location value.

const BreadcrumbsComponent = () => {
  const { pathname } = useLocation();

  const pathnames = pathname.split("/").filter((segment) => segment);

  return (
    <Breadcrumbs aria-label="breadcrumb">
      <LinkRouter underline="hover" color="inherit" to="/">
        Home
      </LinkRouter>
      {pathnames.map((_value, index) => {
        const last = index === pathnames.length - 1;
        const to = `/${pathnames.slice(0, index + 1).join("/")}`;

        return last ? (
          <Typography color="text.primary" key={to}>
            {breadcrumbNameMap[to]}
          </Typography>
        ) : (
          <LinkRouter underline="hover" color="inherit" to={to} key={to}>
            {breadcrumbNameMap[to]}
          </LinkRouter>
        );
      })}
    </Breadcrumbs>
  );
};

You could render BreadcrumbsComponent on a pathless route like <Route element=<BreadcrumbsComponent />} /> but this is pointless, you can simply render BreadcrumbsComponent directly within the MemoryRouter.

function RouterBreadcrumbs() {
  const [open, setOpen] = React.useState(true);

  const handleClick = () => {
    setOpen((prevOpen) => !prevOpen);
  };

  return (
    <MemoryRouter initialEntries={["/inbox"]} initialIndex={0}>
      <Box sx={{ display: "flex", flexDirection: "column", width: 360 }}>
        <BreadcrumbsComponent />
        <Box
          sx={{
            bgcolor: "background.paper",
            mt: 1,
          }}
          component="nav"
          aria-label="mailbox folders"
        >
          <List>
            <ListItemLink to="/inbox" open={open} onClick={handleClick} />
            <Collapse component="li" in={open} timeout="auto" unmountOnExit>
              <List disablePadding>
                <ListItemLink sx={{ pl: 4 }} to="/inbox/important" />
              </List>
            </Collapse>
            <ListItemLink to="/trash" />
            <ListItemLink to="/spam" />
          </List>
        </Box>
      </Box>
    </MemoryRouter>
  );
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks Drew! It is working like the example now. There is still a long way to go for me. It doesn't do exactly what I want. Suppose there are two entries under Inbox, Mary and Bob. When I click on Inbox, I want to navigate to an Inbox page where the list now shows Mary and Bob, but not trash and spam and the breadcrumbs shows Inbox > Mary when I click on her. This web site is a wonderful example of what I want: dph.illinois.gov/topics-services/diseases-and-conditions/…. I'll try to figure it out on sandbox.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.