Complete "Layout Routes"

This commit is contained in:
2025-03-31 21:18:59 +02:00
parent 1f9c62774a
commit 6d3da9874c
10 changed files with 487 additions and 363 deletions

View File

@@ -14,8 +14,8 @@
"@tanstack/react-start": "^1.114.30", "@tanstack/react-start": "^1.114.30",
"@tanstack/router-plugin": "^1.114.30", "@tanstack/router-plugin": "^1.114.30",
"match-sorter": "^8.0.0", "match-sorter": "^8.0.0",
"react": "^19.0.0", "react": "^19.1.0",
"react-dom": "^19.0.0", "react-dom": "^19.1.0",
"sort-by": "^1.2.0", "sort-by": "^1.2.0",
"tiny-invariant": "^1.3.3", "tiny-invariant": "^1.3.3",
"vinxi": "^0.5.3" "vinxi": "^0.5.3"
@@ -25,18 +25,18 @@
"@tanstack/eslint-plugin-router": "^1.114.29", "@tanstack/eslint-plugin-router": "^1.114.29",
"@testing-library/dom": "^10.4.0", "@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.2.0", "@testing-library/react": "^16.2.0",
"@types/react": "^19.0.8", "@types/react": "^19.0.12",
"@types/react-dom": "^19.0.3", "@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.23.0", "eslint": "^9.23.0",
"eslint-plugin-react": "^7.37.4", "eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"jsdom": "^26.0.0", "jsdom": "^26.0.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"typescript-eslint": "^8.28.0", "typescript-eslint": "^8.29.0",
"vite": "^6.1.0", "vite": "^6.2.4",
"vite-tsconfig-paths": "^5.1.4", "vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.0.5", "vitest": "^3.1.1",
"web-vitals": "^4.2.4" "web-vitals": "^4.2.4"
}, },
"pnpm": { "pnpm": {

491
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -11,79 +11,129 @@
// Import Routes // Import Routes
import { Route as rootRoute } from './routes/__root' import { Route as rootRoute } from './routes/__root'
import { Route as IndexImport } from './routes/index' import { Route as AboutImport } from './routes/about'
import { Route as ContactsContractIdImport } from './routes/contacts.$contractId' import { Route as SidebarImport } from './routes/_sidebar'
import { Route as SidebarIndexImport } from './routes/_sidebar/index'
import { Route as SidebarContactsContractIdImport } from './routes/_sidebar/contacts.$contractId'
// Create/Update Routes // Create/Update Routes
const IndexRoute = IndexImport.update({ const AboutRoute = AboutImport.update({
id: '/', id: '/about',
path: '/', path: '/about',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any) } as any)
const ContactsContractIdRoute = ContactsContractIdImport.update({ const SidebarRoute = SidebarImport.update({
id: '/_sidebar',
getParentRoute: () => rootRoute,
} as any)
const SidebarIndexRoute = SidebarIndexImport.update({
id: '/',
path: '/',
getParentRoute: () => SidebarRoute,
} as any)
const SidebarContactsContractIdRoute = SidebarContactsContractIdImport.update({
id: '/contacts/$contractId', id: '/contacts/$contractId',
path: '/contacts/$contractId', path: '/contacts/$contractId',
getParentRoute: () => rootRoute, getParentRoute: () => SidebarRoute,
} as any) } as any)
// Populate the FileRoutesByPath interface // Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {
interface FileRoutesByPath { interface FileRoutesByPath {
'/': { '/_sidebar': {
id: '/' id: '/_sidebar'
path: '/' path: ''
fullPath: '/' fullPath: ''
preLoaderRoute: typeof IndexImport preLoaderRoute: typeof SidebarImport
parentRoute: typeof rootRoute parentRoute: typeof rootRoute
} }
'/contacts/$contractId': { '/about': {
id: '/contacts/$contractId' id: '/about'
path: '/about'
fullPath: '/about'
preLoaderRoute: typeof AboutImport
parentRoute: typeof rootRoute
}
'/_sidebar/': {
id: '/_sidebar/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof SidebarIndexImport
parentRoute: typeof SidebarImport
}
'/_sidebar/contacts/$contractId': {
id: '/_sidebar/contacts/$contractId'
path: '/contacts/$contractId' path: '/contacts/$contractId'
fullPath: '/contacts/$contractId' fullPath: '/contacts/$contractId'
preLoaderRoute: typeof ContactsContractIdImport preLoaderRoute: typeof SidebarContactsContractIdImport
parentRoute: typeof rootRoute parentRoute: typeof SidebarImport
} }
} }
} }
// Create and export the route tree // Create and export the route tree
interface SidebarRouteChildren {
SidebarIndexRoute: typeof SidebarIndexRoute
SidebarContactsContractIdRoute: typeof SidebarContactsContractIdRoute
}
const SidebarRouteChildren: SidebarRouteChildren = {
SidebarIndexRoute: SidebarIndexRoute,
SidebarContactsContractIdRoute: SidebarContactsContractIdRoute,
}
const SidebarRouteWithChildren =
SidebarRoute._addFileChildren(SidebarRouteChildren)
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '': typeof SidebarRouteWithChildren
'/contacts/$contractId': typeof ContactsContractIdRoute '/about': typeof AboutRoute
'/': typeof SidebarIndexRoute
'/contacts/$contractId': typeof SidebarContactsContractIdRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute '/about': typeof AboutRoute
'/contacts/$contractId': typeof ContactsContractIdRoute '/': typeof SidebarIndexRoute
'/contacts/$contractId': typeof SidebarContactsContractIdRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRoute __root__: typeof rootRoute
'/': typeof IndexRoute '/_sidebar': typeof SidebarRouteWithChildren
'/contacts/$contractId': typeof ContactsContractIdRoute '/about': typeof AboutRoute
'/_sidebar/': typeof SidebarIndexRoute
'/_sidebar/contacts/$contractId': typeof SidebarContactsContractIdRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/contacts/$contractId' fullPaths: '' | '/about' | '/' | '/contacts/$contractId'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: '/' | '/contacts/$contractId' to: '/about' | '/' | '/contacts/$contractId'
id: '__root__' | '/' | '/contacts/$contractId' id:
| '__root__'
| '/_sidebar'
| '/about'
| '/_sidebar/'
| '/_sidebar/contacts/$contractId'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute SidebarRoute: typeof SidebarRouteWithChildren
ContactsContractIdRoute: typeof ContactsContractIdRoute AboutRoute: typeof AboutRoute
} }
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, SidebarRoute: SidebarRouteWithChildren,
ContactsContractIdRoute: ContactsContractIdRoute, AboutRoute: AboutRoute,
} }
export const routeTree = rootRoute export const routeTree = rootRoute
@@ -96,15 +146,27 @@ export const routeTree = rootRoute
"__root__": { "__root__": {
"filePath": "__root.tsx", "filePath": "__root.tsx",
"children": [ "children": [
"/", "/_sidebar",
"/contacts/$contractId" "/about"
] ]
}, },
"/": { "/_sidebar": {
"filePath": "index.tsx" "filePath": "_sidebar.tsx",
"children": [
"/_sidebar/",
"/_sidebar/contacts/$contractId"
]
}, },
"/contacts/$contractId": { "/about": {
"filePath": "contacts.$contractId.tsx" "filePath": "about.tsx"
},
"/_sidebar/": {
"filePath": "_sidebar/index.tsx",
"parent": "/_sidebar"
},
"/_sidebar/contacts/$contractId": {
"filePath": "_sidebar/contacts.$contractId.tsx",
"parent": "/_sidebar"
} }
} }
} }

View File

@@ -1,16 +1,9 @@
import { Outlet, createRootRoute, Link, HeadContent, Scripts } from '@tanstack/react-router'; import { Outlet, createRootRoute, HeadContent, Scripts } from '@tanstack/react-router';
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'; import { TanStackRouterDevtools } from '@tanstack/react-router-devtools';
import appCss from '@/App.css?url'; import appCss from '@/App.css?url';
import { getContacts } from "@/data";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
async function fetchContacts() {
const contacts = await getContacts();
return { contacts };
}
export const Route = createRootRoute({ export const Route = createRootRoute({
loader: fetchContacts,
head: () => ({ head: () => ({
meta: [{ meta: [{
charSet: 'utf-8', charSet: 'utf-8',
@@ -34,57 +27,9 @@ export const Route = createRootRoute({
}); });
function RootLayout() { function RootLayout() {
const { contacts } = Route.useLoaderData();
return ( return (
<RootDocument> <RootDocument>
<div id="sidebar"> <Outlet/>
<h1>React Router Contacts</h1>
<div>
<form id="search-form" role="search">
<input
aria-label="Search contacts"
id="q"
name="q"
placeholder="Search"
type="search"
/>
<div aria-hidden hidden={true} id="search-spinner"/>
</form>
<form method="post">
<button type="submit">New</button>
</form>
</div>
<nav>
{contacts.length ? (
<ul>
{contacts.map((contact) => (
<li key={contact.id}>
<Link to="/contacts/$contractId" params={{ contractId: contact.id }}>
{contact.first || contact.last ? (
<>
{contact.first} {contact.last}
</>
) : (
<i>No Name</i>
)}
{contact.favorite ? (
<span></span>
) : null}
</Link>
</li>
))}
</ul>
) : (
<p>
<i>No contacts</i>
</p>
)}
</nav>
</div>
<div id={'detail'}>
<Outlet/>
</div>
<TanStackRouterDevtools/> <TanStackRouterDevtools/>
</RootDocument> </RootDocument>
); );

70
src/routes/_sidebar.tsx Normal file
View File

@@ -0,0 +1,70 @@
import { createFileRoute, Link, Outlet } from '@tanstack/react-router';
import { getContacts } from "@/data";
async function fetchContacts() {
const contacts = await getContacts();
return { contacts };
}
export const Route = createFileRoute('/_sidebar')({
component: Sidebar,
loader: fetchContacts,
});
function Sidebar() {
const { contacts } = Route.useLoaderData();
return (
<>
<div id="sidebar">
<h1>
<Link to="/about">TanStack Start Contacts</Link>
</h1>
<div>
<form id="search-form" role="search">
<input
aria-label="Search contacts"
id="q"
name="q"
placeholder="Search"
type="search"
/>
<div aria-hidden hidden={true} id="search-spinner"/>
</form>
<form method="post">
<button type="submit">New</button>
</form>
</div>
<nav>
{contacts.length ? (
<ul>
{contacts.map((contact) => (
<li key={contact.id}>
<Link to="/contacts/$contractId" params={{ contractId: contact.id }}>
{contact.first || contact.last ? (
<>
{contact.first} {contact.last}
</>
) : (
<i>No Name</i>
)}
{contact.favorite ? (
<span></span>
) : null}
</Link>
</li>
))}
</ul>
) : (
<p>
<i>No contacts</i>
</p>
)}
</nav>
</div>
<div id={'detail'}>
<Outlet/>
</div>
</>
);
}

View File

@@ -1,7 +1,7 @@
import { createFileRoute } from '@tanstack/react-router'; import { createFileRoute } from '@tanstack/react-router';
import type { ContactRecord } from "@/data.ts"; import type { ContactRecord } from "@/data.ts";
export const Route = createFileRoute('/contacts/$contractId')({ export const Route = createFileRoute('/_sidebar/contacts/$contractId')({
component: Contact, component: Contact,
}); });

View File

@@ -0,0 +1,19 @@
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/_sidebar/')({
component: App,
});
function App() {
return (
<p id="index-page">
This is a demo for TanStack/Start and Router.
<br/>
Check out{" "}
<a href="https://tanstack.com/">
the docs at tanstack.com
</a>
.
</p>
);
}

49
src/routes/about.tsx Normal file
View File

@@ -0,0 +1,49 @@
import { createFileRoute, Link } from '@tanstack/react-router';
export const Route = createFileRoute('/about')({
component: RouteComponent,
});
function RouteComponent() {
return (
<div id="about">
<Link to="/"> Go to demo</Link>
<h1>About React Router Contacts</h1>
<div>
<p>
This is a demo application showing off some of the
powerful features of React Router, including
dynamic routing, nested routes, loaders, actions,
and more.
</p>
<h2>Features</h2>
<p>
Explore the demo to see how React Router handles:
</p>
<ul>
<li>
Data loading and mutations with loaders and
actions
</li>
<li>
Nested routing with parent/child relationships
</li>
<li>URL-based routing with dynamic segments</li>
<li>Pending and optimistic UI</li>
</ul>
<h2>Learn More</h2>
<p>
Check out the official documentation at{" "}
<a href="https://reactrouter.com">
reactrouter.com
</a>{" "}
to learn more about building great web
applications with React Router.
</p>
</div>
</div>
);
}

View File

@@ -1,11 +0,0 @@
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/')({
component: App,
});
function App() {
return (
<div></div>
);
}