Skip to content

Commit

Permalink
feat: add route object support (icd2k3#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nodios committed Sep 25, 2022
1 parent a51f9e6 commit 0a9e21d
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 12 deletions.
70 changes: 65 additions & 5 deletions src/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import '@testing-library/jest-dom';
import React from 'react';
import { render, screen } from '@testing-library/react';
import { MemoryRouter as Router, useLocation } from 'react-router-dom';
import useBreadcrumbs, { getBreadcrumbs, createRoutesFromChildren, BreadcrumbsRoute, Route, Options, BreadcrumbComponentProps } from './index';
import useBreadcrumbs, { getBreadcrumbs, createRoutesFromChildren, BreadcrumbsRoute, Route, Options, BreadcrumbComponentProps, BreadcrumbsRouteObjectType } from './index';

// imports to test compiled builds
import useBreadcrumbsCompiledES, {
Expand All @@ -29,13 +29,15 @@ const components = {
useBreadcrumbs: useBreadcrumbsHook,
options,
routes,
routeObjects,
...forwardedProps
}: {
useBreadcrumbs: (r?: BreadcrumbsRoute[], o?: Options) => []
useBreadcrumbs: (r?: BreadcrumbsRoute[], ro?: BreadcrumbsRouteObjectType, o?: Options) => []
options?: Options
routes?: BreadcrumbsRoute[]
routes?: BreadcrumbsRoute[],
routeObjects?: BreadcrumbsRouteObjectType,
}) => {
const breadcrumbs = useBreadcrumbsHook(routes, options);
const breadcrumbs = useBreadcrumbsHook(routes, routeObjects, options);
const location = useLocation();

return (
Expand Down Expand Up @@ -127,11 +129,12 @@ const getMethod = () => {
};

const renderer = (
{ options, pathname, routes, state, props }:
{ options, pathname, routes, routeObjects, state, props }:
{
options?: Options
pathname: string
routes?: BreadcrumbsRoute<string>[]
routeObjects?: BreadcrumbsRouteObjectType
state?: { isLocationTest: boolean }
props?: { [x: string]: unknown }
},
Expand All @@ -147,6 +150,7 @@ const renderer = (
useBreadcrumbs={useBreadcrumbsHook}
options={options}
routes={routes}
routeObjects={routeObjects}
{...(props || {})}
/>
</Router>,
Expand Down Expand Up @@ -592,4 +596,60 @@ describe('use-react-router-breadcrumbs', () => {
expect(getByTextContent('Home / Sandwiches / Tuna')).toBeTruthy();
});
});

describe('Using route objects', () => {
it('Detect route object routes', () => {
const routes = [
{ path: 'a/*' },
];

renderer({
pathname: '/a/b',
routes,
routeObjects: {
a: [
{
path: 'b',
},
],
},
});

expect(getByTextContent('Home / A / B')).toBeTruthy();
});

it('Use custom breadcrumb from route object', () => {
const routes: BreadcrumbsRoute[] = [
{ path: 'a/*', breadcrumb: 'Route A' },
];

renderer({
pathname: '/a/b',
routes,
routeObjects: {
a: [
{
path: 'b',
breadcrumb: 'Route B',
},
],
},
});

expect(getByTextContent('Home / Route A / Route B')).toBeTruthy();
});

it('No route object passed', () => {
const routes = [
{ path: 'a/*' },
];

renderer({
pathname: '/a/b',
routes,
});

expect(getByTextContent('Home / A / B')).toBeTruthy();
});
});
});
28 changes: 21 additions & 7 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ interface PathPattern<Path extends string = string> {
export interface Options {
disableDefaults?: boolean;
excludePaths?: string[];
defaultFormatter?: (str: string) => string
defaultFormatter?: (str: string) => string;
}

export interface BreadcrumbMatch<ParamKey extends string = string> {
Expand Down Expand Up @@ -75,6 +75,8 @@ export interface BreadcrumbComponentProps<ParamKey extends string = string> {
export type BreadcrumbComponentType<ParamKey extends string = string> =
React.ComponentType<BreadcrumbComponentProps<ParamKey>>;

export type BreadcrumbsRouteObjectType = { [path: string]: BreadcrumbsRoute[] };

export interface BreadcrumbsRoute<ParamKey extends string = string>
extends RouteObject {
children?: BreadcrumbsRoute[];
Expand Down Expand Up @@ -143,6 +145,7 @@ function compareIndexes(a: number[], b: number[]): number {

function flattenRoutes(
routes: BreadcrumbsRoute[],
routeObjects: BreadcrumbsRouteObjectType = {},
branches: BreadcrumbsRouteBranch[] = [],
parentsMeta: BreadcrumbsRouteMeta[] = [],
parentPath = '',
Expand Down Expand Up @@ -173,14 +176,22 @@ function flattenRoutes(
meta.relativePath = meta.relativePath.slice(parentPath.length);
}

const path = joinPaths([parentPath, meta.relativePath]);
let path = joinPaths([parentPath, meta.relativePath]);
const routesMeta = parentsMeta.concat(meta);
const potentialRouteObject = meta.relativePath.endsWith('/*');

if (route.children && route.children.length > 0) {
if (route.index) {
throw new Error('useBreadcrumbs: Index route cannot have child routes');
}
flattenRoutes(route.children, branches, routesMeta, path);
flattenRoutes(route.children, routeObjects, branches, routesMeta, path);
} else if (potentialRouteObject) {
const pathElement = meta.relativePath.slice(0, -2); // remove "/*"" from the end
if (routeObjects[pathElement]) {
const routeObjectRoutes = routeObjects[pathElement];
path = joinPaths([parentPath, pathElement]);
flattenRoutes(routeObjectRoutes, routeObjects, branches, routesMeta, path);
}
}

branches.push({
Expand Down Expand Up @@ -361,7 +372,7 @@ const getBreadcrumbMatch = ({
// which we support. The route config object may not have a `breadcrumb` param specified.
// If this is the case, we should provide a default via `humanize`.
breadcrumb: userProvidedBreadcrumb
|| (defaultFormatter ? defaultFormatter(currentSection) : humanize(currentSection)),
|| (defaultFormatter ? defaultFormatter(currentSection) : humanize(currentSection)),
match: { ...match, route },
location,
props,
Expand Down Expand Up @@ -397,16 +408,18 @@ const getBreadcrumbMatch = ({
*/
export const getBreadcrumbs = ({
routes,
routeObjects,
location,
options = {},
}: {
routes: BreadcrumbsRoute[];
location: Location;
routeObjects?: BreadcrumbsRouteObjectType
options?: Options;
}): BreadcrumbData[] => {
const { pathname } = location;

const branches = rankRouteBranches(flattenRoutes(routes));
const branches = rankRouteBranches(flattenRoutes(routes, routeObjects));
const breadcrumbs: BreadcrumbData[] = [];
pathname
.split('?')[0]
Expand Down Expand Up @@ -453,9 +466,11 @@ export const getBreadcrumbs = ({
*/
const useReactRouterBreadcrumbs = (
routes?: BreadcrumbsRoute[],
routeObjects?: BreadcrumbsRouteObjectType,
options?: Options,
): BreadcrumbData[] => getBreadcrumbs({
routes: routes || [],
routeObjects,
location: useLocation(),
options,
});
Expand Down Expand Up @@ -502,8 +517,7 @@ export function createRoutesFromChildren(

invariant(
element.type === Route,
`[${
typeof element.type === 'string' ? element.type : element.type.name
`[${typeof element.type === 'string' ? element.type : element.type.name
}] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>`,
);

Expand Down

0 comments on commit 0a9e21d

Please sign in to comment.