1

I have implemented a breadcrumb components that forms from the location.pathname. In that case, if a url is localhost:3000/products/13 , the breadcrumbs would display like products > 13, but I want it to display the name instead of the id, so it should be like products > product_name

In my case I have a table, on clicking a row of a table, instead of appending the name to url, I am pushing a hash value to the url. The resultant page show a breadcrumb containing the hash but I want to display the name instead. What would be the most effective way to do this

Below is my code :

Breadcrumb.js

import {
  Breadcrumbs as MUIBreadcrumbs,
  Link,
  Typography
} from "@material-ui/core";
import { withRouter } from "react-router-dom";
import styled from 'styled-components';
import { fontFamily } from "@material-ui/system";

const Header = styled.p`
  font-size: 14px;
  font-weight: 600;
  color: #211758;
  margin-bottom: 0px !important;
`
const BreadcrumbLink = styled(Link)`
  font-size: 14px;
  font-weight: 600;
  color: #211758;
`

const Breadcrumbs = props => {
  const {
    history,
    location: { pathname }
  } = props;
  const keynames = ['clusters', 'runs']
  const pathnames = pathname.split("/").filter(x => x);
  return (
    <MUIBreadcrumbs separator="›" aria-label="breadcrumb" style = {{fontFamily:'inherit'}}>
      {pathnames.map((name, index) => {
        const routeTo = `/${pathnames.slice(0, index + 1).join("/")}`;
        const isLast = index === pathnames.length - 1;
        return isLast? (
         pathnames.length === 1 ? <Header key={name}>{name.charAt(0).toUpperCase() + name.slice(1)}</Header> :
         <Header key={name}>{name}</Header> 
        ) : (
          <BreadcrumbLink key={name} onClick={() => history.push(routeTo)}>
            {keynames.includes(name)?name.charAt(0).toUpperCase() + name.slice(1):name}
          </BreadcrumbLink>
        );
      })}
    </MUIBreadcrumbs>
  );
};

export default withRouter(Breadcrumbs);

The sample code when a table row is clicked :

  rowClicked = (params) => {
    return (
      history.push({
        pathname: `/clusters/${params.data.hash_value}/runs`
      })
    )
  }

The resultant breadcrumb in next page is : clusters > {params.data.hash_value} > runs (> is the separator in the breadcrumbs)

I want this breadcrumbs instead : clusters > {params.data.name} > runs

How can I achieve this?

desertnaut
  • 57,590
  • 26
  • 140
  • 166
Derrick Omanwa
  • 457
  • 1
  • 5
  • 23

2 Answers2

1

After a while of thinking. I came up with a solution that works for me. I did this in two parts:

  1. create a filter in backend: Since I am using django rest framework for backend, and I need to get the name of a cluster based on a hash value. the appropiate thing to do is to filter out the clusters serializer in the backend that would work when a hash parameter is queried in the api url (localhost:8000/clusters?hash=<hash_value>):

class ClustersView(generics.ListAPIView): serializer_class = ClustersSerializer

def get_queryset(self):
    clusters = Cluster.objects.all()
    hash_val = self.request.query_params.get('hash')
    if hash_val:
        return Cluster.objects.filter(hash_value=hash_val)
    return clusters

This would return a single cluster, and it is this cluster where the name needed to replace the hash in breadcrumbs is available.

  1. Now we edit the breadcrumbs.Here is the code below with additional details.

    const Breadcrumbs = props => { const { history, location: { pathname } } = props;

    const keynames = ['clusters', 'runs']
    //make sure you import useState, useEffect hooks from react.
    
    const [cluster, setCluster] = useState({
        pathnames: pathname.split("/").filter(x => x) //set the value of pathnames here
    })
    
    let pathnames = cluster.pathnames
    
    useEffect(() => {
        if (pathnames[0] === 'clusters' && pathnames.length > 1) {
            const hash = pathnames[1] //get the hash value from pathnames if condition satisified.
            loadData(hash) //call the api call function in useEffect
        }
    }, [])
    
    //define the function
    const loadData = async (value) => {
        const response = await fetch(`localhost:8000/clusters?hash=${value}`)
        const data = await response.json();
        cluster.pathnames[1] = data.results[0].name //replace the `hash` in pathnames with `name` from api
        setCluster({ pathnames: cluster.pathnames }) //update the state
    }
    
    return (
        <MUIBreadcrumbs separator="›" aria-label="breadcrumb" style={{ fontFamily: 'inherit' }}>
            {pathnames.map((name, index) => {
                const routeTo = `/${pathnames.slice(0, index + 1).join("/")}`;
                const isLast = index === pathnames.length - 1;
                return isLast ? (
                    pathnames.length === 1 ? <Header key={name}>{name.charAt(0).toUpperCase() + name.slice(1)}</Header> :
                        <Header key={name}>{name}</Header>
                ) : (
                        <BreadcrumbLink key={name} onClick={() => history.push(routeTo)}>
                            {keynames.includes(name) ? name.charAt(0).toUpperCase() + name.slice(1) : name}
                        </BreadcrumbLink>
                    );
            })}
        </MUIBreadcrumbs>
    );
    

    };

    export default withRouter(Breadcrumbs);

I hope this gets to help someone who encounters the same. Thanks.

Derrick Omanwa
  • 457
  • 1
  • 5
  • 23
0

How many products do you have? If theres not that many, or the products wont be changing, you could just check what the params hash value is in react-router and update the breadcrumb based on that.

So a function or useEffect like, switch (hash_value) case 1: return 'product-one'; case 2: return 'product-two'; etc etc.

Obviously thats not scalable and wont work for more than a few products though.

If you do have more than a few products, presumably your making some sort of database call to fetch the product data? In which case you could return from the backend the breadcrumb trail in an array or string, and instead of splitting the pathname, you could split the array/string from the backend to populate the BreadbrumbLink.

Or if you cant change the back end, you could pass the product_name as query parameters to the URL, so it would contain both the ID and the product_name, then just grab the name the populate the breadcrumb. (eg. URL could look like /product/13?product=product-name)

But if you dont want to change the URL structure, its probably easiest to pass the product name along with the link, then access it as props within the component that displays the product.

benmneb
  • 1,773
  • 1
  • 15
  • 26