T
TanStack•3mo ago
extended-salmon

Breadcrumbs with static data

Not a full project, but I'm really proud of my breadcrumbs implementation:
<div className="hidden md:flex items-center pl-2">
<Breadcrumbs.Root className="my-auto">
{useMatches()
.map((match) => match.staticData.breadcrumb)
.filter(Boolean)
.map((breadcrumb, i, arr) => (
<Fragment key={breadcrumb.to}>
<LinkBreadcrumb {...breadcrumb} activeOptions={{ exact: true }} />
{i < arr.length - 1 && (<Breadcrumbs.Separator />)}
</Fragment>
)
)}
</Breadcrumbs.Root>
</div>
<div className="hidden md:flex items-center pl-2">
<Breadcrumbs.Root className="my-auto">
{useMatches()
.map((match) => match.staticData.breadcrumb)
.filter(Boolean)
.map((breadcrumb, i, arr) => (
<Fragment key={breadcrumb.to}>
<LinkBreadcrumb {...breadcrumb} activeOptions={{ exact: true }} />
{i < arr.length - 1 && (<Breadcrumbs.Separator />)}
</Fragment>
)
)}
</Breadcrumbs.Root>
</div>
8 Replies
complex-teal
complex-teal•3mo ago
it starts with defining an optional breadcrumb o nthe route
declare module "@tanstack/react-router" {
interface Register {
router: ReturnType<typeof createRouter>;
}

interface StaticDataRouteOption {
breadcrumb?: {
children: string;
to: LinkProps["to"]
},
}
}
declare module "@tanstack/react-router" {
interface Register {
router: ReturnType<typeof createRouter>;
}

interface StaticDataRouteOption {
breadcrumb?: {
children: string;
to: LinkProps["to"]
},
}
}
which can be used like incrementally, and then the snippet above is at my _layout.tsx. Combined with the Link data-status, becoems quite easy to make something that looks good with just a few dozen lines of code:
const BreadcrumbsLink: FunctionComponent<ComponentProps<"a">> = ({
className,
...props
}) => (
<a
className={cn(
"text-muted select-none hover:text-text hover:underline",
"data-[status=active]:cursor-default",
"data-[status=active]:text-text",
"data-[status=active]:hover:no-underline",
"data-[status=active]:hover:text-text",
className,
)}
{...props}
/>
);
const BreadcrumbsLink: FunctionComponent<ComponentProps<"a">> = ({
className,
...props
}) => (
<a
className={cn(
"text-muted select-none hover:text-text hover:underline",
"data-[status=active]:cursor-default",
"data-[status=active]:text-text",
"data-[status=active]:hover:no-underline",
"data-[status=active]:hover:text-text",
className,
)}
{...props}
/>
);
No description
absent-sapphire
absent-sapphire•3mo ago
Nice! How do you use it? Ie. on the route level?
complex-teal
complex-teal•3mo ago
yes!
<AppLayout.Header>
<ChairFlightLink className="md:hidden" />
<div className="hidden md:flex items-center pl-2">
<Breadcrumbs.Root className="my-auto">
{useMatches()
.map((match) => match.staticData.breadcrumb)
.filter(Boolean)
.map((breadcrumb, i, arr) => (
<Fragment key={breadcrumb.to}>
<LinkBreadcrumb {...breadcrumb} activeOptions={{ exact: true }} />
{i < arr.length - 1 && (<Breadcrumbs.Separator />)}
</Fragment>
)
)}
</Breadcrumbs.Root>
</div>
<AppLayout.Header>
<ChairFlightLink className="md:hidden" />
<div className="hidden md:flex items-center pl-2">
<Breadcrumbs.Root className="my-auto">
{useMatches()
.map((match) => match.staticData.breadcrumb)
.filter(Boolean)
.map((breadcrumb, i, arr) => (
<Fragment key={breadcrumb.to}>
<LinkBreadcrumb {...breadcrumb} activeOptions={{ exact: true }} />
{i < arr.length - 1 && (<Breadcrumbs.Separator />)}
</Fragment>
)
)}
</Breadcrumbs.Root>
</div>
it's very plug and play, so I will put this snippet in a few pathless layouts. I cant put it in a single one since i have a bit of a design going on 😅
absent-sapphire
absent-sapphire•3mo ago
I mean, how do you set the breadcrumb static data on the route itself?
complex-teal
complex-teal•3mo ago
here you go:
export const Route = createFileRoute({
staleTime: Infinity,
component: LearningObjectivesIndexPage,
validateSearch: zodValidator(
z.object({
page: z.number().optional(),
search: z.string().optional(),
subject: z.string().optional(),
}),
),
loader: async ({ context }) => {
const courses = context.trpc.questionBank.courses;
await context.queryClient.ensureQueryData(
courses.getSubjectsInCourse.queryOptions({ courseId: "ATPL_A" }),
);
},
staticData: {
breadcrumb: {
to: "/question-bank/atpl/learning-objectives",
children: "Learning Objectives",
}
}
});
export const Route = createFileRoute({
staleTime: Infinity,
component: LearningObjectivesIndexPage,
validateSearch: zodValidator(
z.object({
page: z.number().optional(),
search: z.string().optional(),
subject: z.string().optional(),
}),
),
loader: async ({ context }) => {
const courses = context.trpc.questionBank.courses;
await context.queryClient.ensureQueryData(
courses.getSubjectsInCourse.queryOptions({ courseId: "ATPL_A" }),
);
},
staticData: {
breadcrumb: {
to: "/question-bank/atpl/learning-objectives",
children: "Learning Objectives",
}
}
});
absent-sapphire
absent-sapphire•3mo ago
Thanks!
fascinating-indigo
fascinating-indigo•3mo ago
hi @pupo ! this seems pretty good, but how about dynamic crumbs (that may depend on the loader or path params) ?
complex-teal
complex-teal•3mo ago
@notKamui hey! Sadly... no solution. I hit the same problem as well, and i haven't given it a second thought yet. I'm afraid I will go back to "plan A" and use some sort of context to move the information up the ladder

Did you find this page helpful?