Links that route
Each link navigates client-side; its href is the URL the router shows.
import { Outlet, RouterProvider, createMemoryHistory, createRootRoute, createRoute, createRouter } from '@tanstack/react-router';
import { useMemo } from 'react';
import type { ReactNode } from 'react';
import { useLocale } from 'react-aria-components';
import { Link } from '@arbol-ai/ui';
import { RouterUIProvider } from '@arbol-ai/ui/router';
const LINK_LABELS: Record<string, string> = {
'/': 'Overview',
'/campaigns': 'Campaigns',
'/contacts': 'Contacts',
'/settings': 'Settings',
};
interface MemoryRouterExampleProps {
initialPath: string;
pages: Record<string, () => ReactNode>;
}
const MemoryRouterExample = ({ initialPath, pages }: MemoryRouterExampleProps) => {
const router = useMemo(() => createExampleRouter(initialPath, pages), [initialPath, pages]);
return <RouterProvider router={router} />;
};
const createExampleRouter = (initialPath: string, pages: Record<string, () => ReactNode>) => {
const paths = Object.keys(pages);
const rootRoute = createRootRoute({ component: () => <ExampleRoot paths={paths} /> });
const routes = Object.entries(pages).map(([path, component]) =>
createRoute({ component, getParentRoute: () => rootRoute, path })
);
return createRouter({
history: createMemoryHistory({ initialEntries: [initialPath] }),
routeTree: rootRoute.addChildren(routes),
});
};
const ExampleRoot = ({ paths }: { paths: string[] }) => {
const { locale } = useLocale();
return (
<RouterUIProvider locale={locale}>
<div className="rounded-card grid gap-4 border p-4">
<nav aria-label="Example sections" className="flex gap-4 text-sm">
{paths.map((path) => (
<Link href={path} key={path}>
{LINK_LABELS[path]}
</Link>
))}
</nav>
<Outlet />
</div>
</RouterUIProvider>
);
};
const OverviewPage = () => <p className="text-sm">Today: 18 appointments and 3 conversations waiting.</p>;
const ContactsPage = () => <p className="text-sm">1,284 contacts, 46 added this week.</p>;
const CampaignsPage = () => <p className="text-sm">2 campaigns running, 1 scheduled for Monday.</p>;
const OVERVIEW_PAGES = { '/': OverviewPage, '/contacts': ContactsPage, '/campaigns': CampaignsPage };
<MemoryRouterExample initialPath="/" pages={OVERVIEW_PAGES} />Unsaved changes
Change the clinic’s name, then go to Contacts: the page asks first. Save, and it lets you go.
import { Outlet, RouterProvider, createMemoryHistory, createRootRoute, createRoute, createRouter } from '@tanstack/react-router';
import { useMemo, useState } from 'react';
import type { ReactNode } from 'react';
import { useLocale } from 'react-aria-components';
import { Button, Link, TextField, UnsavedChangesDialog } from '@arbol-ai/ui';
import { RouterUIProvider, useUnsavedChangesBlocker } from '@arbol-ai/ui/router';
const LINK_LABELS: Record<string, string> = {
'/': 'Overview',
'/campaigns': 'Campaigns',
'/contacts': 'Contacts',
'/settings': 'Settings',
};
interface MemoryRouterExampleProps {
initialPath: string;
pages: Record<string, () => ReactNode>;
}
const MemoryRouterExample = ({ initialPath, pages }: MemoryRouterExampleProps) => {
const router = useMemo(() => createExampleRouter(initialPath, pages), [initialPath, pages]);
return <RouterProvider router={router} />;
};
const createExampleRouter = (initialPath: string, pages: Record<string, () => ReactNode>) => {
const paths = Object.keys(pages);
const rootRoute = createRootRoute({ component: () => <ExampleRoot paths={paths} /> });
const routes = Object.entries(pages).map(([path, component]) =>
createRoute({ component, getParentRoute: () => rootRoute, path })
);
return createRouter({
history: createMemoryHistory({ initialEntries: [initialPath] }),
routeTree: rootRoute.addChildren(routes),
});
};
const ExampleRoot = ({ paths }: { paths: string[] }) => {
const { locale } = useLocale();
return (
<RouterUIProvider locale={locale}>
<div className="rounded-card grid gap-4 border p-4">
<nav aria-label="Example sections" className="flex gap-4 text-sm">
{paths.map((path) => (
<Link href={path} key={path}>
{LINK_LABELS[path]}
</Link>
))}
</nav>
<Outlet />
</div>
</RouterUIProvider>
);
};
const SettingsPage = () => {
const [saved, setSaved] = useState('Clínica Norte');
const [name, setName] = useState(saved);
const blocker = useUnsavedChangesBlocker(name !== saved);
return (
<form
className="grid max-w-sm gap-3"
onSubmit={(event) => {
event.preventDefault();
setSaved(name);
}}
>
<TextField label="Clinic name" onChange={setName} value={name} />
<Button className="justify-self-start" type="submit">
Save
</Button>
<UnsavedChangesDialog {...blocker} />
</form>
);
};
const ContactsPage = () => <p className="text-sm">1,284 contacts, 46 added this week.</p>;
const SETTINGS_PAGES = { '/settings': SettingsPage, '/contacts': ContactsPage };
<MemoryRouterExample initialPath="/settings" pages={SETTINGS_PAGES} />Source
The examples above are src/router/router.examples.tsx, which the library’s tests render too.