React breadcrumbs
I recently needed a simple breadcrumbs implementation in a React app, and thought I’d use https://github.com/NiklasMencke/nextjs-breadcrumbs‘s source code as a starting point for my own implementation. I thought I’d share the source code here in case anyone might benefit from it,.
But first, at little context. In the app’s URL, we might see something like this: http://<url>/stores/3/products/58. Note that the numbers are database ID’s for the stores and products. For my breadcrumbs, I wanted the names instead if database ID’s, so I needed the breadcrumb function to translate store and product ID’s into actual names.
With that in mind, let’s get to the code. Here’s the main business logic:
import React, { useEffect, useState } from 'react';
import { Link, useRouter } from "blitz"
import { Text, HStack } from "@chakra-ui/react"
import { ChevronRightIcon } from "@chakra-ui/icons"
import { getProductById, getStoreById, getTickerById } from "app/core/libs/dbUtils"
type BreadcrumbElement = {
breadcrumb: string
href: string
}
export const Breadcrumbs = () => {
const router = useRouter();
const initialData: BreadcrumbElement[] = []
const [breadcrumbs, setBreadcrumbs] = useState(initialData);
useEffect(() => {
const generateBreadcrumbs = async () => {
const pathTokens = router.asPath.split('/');
pathTokens.shift();
getBreadcrumElementsByPathTokens(pathTokens).then((breadcrumbElements: BreadcrumbElement[]) => {
setBreadcrumbs(breadcrumbElements)
}).catch(error => {
console.error(`Could not get data: ${JSON.stringify(error)}`)
})
}
if (router.isReady) {
generateBreadcrumbs()
}
}, [router]);
if (!breadcrumbs) {
return null;
}
return (
<nav aria-label="breadcrumbs">
<HStack>
<Text key="home" fontStyle="italic" fontSize="xs">
<a href="/">HOME</a>
</Text>
{breadcrumbs.map((breadcrumbElement: BreadcrumbElement, index: number) => {
const Separator = () => {
if (index < breadcrumbs.length) {
return <ChevronRightIcon />
}
else return null
}
if (breadcrumbElement.breadcrumb === undefined) return <>(not found)</>
return (
<HStack key={index} >
<Separator />
<Text key={index} fontStyle="italic" fontSize="xs">
<Link href={breadcrumbElement.href}>
<a>
{convertBreadcrumb(breadcrumbElement.breadcrumb)}
</a>
</Link>
</Text>
</HStack>
);
})}
</HStack>
</nav>
);
};
And here’s the library functions referenced in the above code:
const convertBreadcrumb = (string: string) => {
if (!string) return "N/A"
return string
.replace(/-/g, ' ')
.replace(/oe/g, 'ö')
.replace(/ae/g, 'ä')
.replace(/ue/g, 'ü')
.toUpperCase();
};
// https://javascript.plainenglish.io/how-to-use-async-function-in-react-hook-useeffect-typescript-js-6204a788a435
const generateBreadcrumbEntryByPath = async (pathElement: string, previousPathElement): Promise<string> => {
if (previousPathElement === "products") {
return await getProductById(Number(pathElement))
}
else if (previousPathElement === "stores") {
const store = await getStoreById(Number(pathElement))
return store.name
}
else return pathElement
}
const getBreadcrumbElement = async (pathTokens: string[], inputPathToken: string, index: number): Promise<BreadcrumbElement> => {
const generateHref = (linkPath: string[], i: number) => {
return '/' + linkPath.slice(0, i + 1).join('/')
}
let newPathElement = inputPathToken
if (index > 0) {
const previousBreadcrumb = pathTokens[index - 1]
try {
newPathElement = await generateBreadcrumbEntryByPath(inputPathToken, previousBreadcrumb)
}
catch (error) {
console.error(`Error from generateBreadcrumbEntryByPath: ${JSON.stringify(error)}`)
const breadcrumbElement: BreadcrumbElement = { breadcrumb: "", href: "" };
return breadcrumbElement
}
}
const breadcrumbElement: BreadcrumbElement = { breadcrumb: newPathElement, href: generateHref(pathTokens, index) };
return breadcrumbElement
}
const getBreadcrumElementsByPathTokens = async (pathTokens: string[]): Promise<BreadcrumbElement[]> => {
return Promise.all(pathTokens.map(async (pathToken, index) => {
const breadcrumbElement: BreadcrumbElement = await getBreadcrumbElement(pathTokens, pathToken, index)
return breadcrumbElement
}))
}