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/router-plugin": "^1.114.30",
"match-sorter": "^8.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"sort-by": "^1.2.0",
"tiny-invariant": "^1.3.3",
"vinxi": "^0.5.3"
@@ -25,18 +25,18 @@
"@tanstack/eslint-plugin-router": "^1.114.29",
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.2.0",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@types/react": "^19.0.12",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.23.0",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.2.0",
"jsdom": "^26.0.0",
"typescript": "^5.8.2",
"typescript-eslint": "^8.28.0",
"vite": "^6.1.0",
"typescript-eslint": "^8.29.0",
"vite": "^6.2.4",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.0.5",
"vitest": "^3.1.1",
"web-vitals": "^4.2.4"
},
"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 { Route as rootRoute } from './routes/__root'
import { Route as IndexImport } from './routes/index'
import { Route as ContactsContractIdImport } from './routes/contacts.$contractId'
import { Route as AboutImport } from './routes/about'
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
const IndexRoute = IndexImport.update({
id: '/',
path: '/',
const AboutRoute = AboutImport.update({
id: '/about',
path: '/about',
getParentRoute: () => rootRoute,
} 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',
path: '/contacts/$contractId',
getParentRoute: () => rootRoute,
getParentRoute: () => SidebarRoute,
} as any)
// Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexImport
'/_sidebar': {
id: '/_sidebar'
path: ''
fullPath: ''
preLoaderRoute: typeof SidebarImport
parentRoute: typeof rootRoute
}
'/contacts/$contractId': {
id: '/contacts/$contractId'
'/about': {
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'
fullPath: '/contacts/$contractId'
preLoaderRoute: typeof ContactsContractIdImport
parentRoute: typeof rootRoute
preLoaderRoute: typeof SidebarContactsContractIdImport
parentRoute: typeof SidebarImport
}
}
}
// 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 {
'/': typeof IndexRoute
'/contacts/$contractId': typeof ContactsContractIdRoute
'': typeof SidebarRouteWithChildren
'/about': typeof AboutRoute
'/': typeof SidebarIndexRoute
'/contacts/$contractId': typeof SidebarContactsContractIdRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/contacts/$contractId': typeof ContactsContractIdRoute
'/about': typeof AboutRoute
'/': typeof SidebarIndexRoute
'/contacts/$contractId': typeof SidebarContactsContractIdRoute
}
export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexRoute
'/contacts/$contractId': typeof ContactsContractIdRoute
'/_sidebar': typeof SidebarRouteWithChildren
'/about': typeof AboutRoute
'/_sidebar/': typeof SidebarIndexRoute
'/_sidebar/contacts/$contractId': typeof SidebarContactsContractIdRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/contacts/$contractId'
fullPaths: '' | '/about' | '/' | '/contacts/$contractId'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/contacts/$contractId'
id: '__root__' | '/' | '/contacts/$contractId'
to: '/about' | '/' | '/contacts/$contractId'
id:
| '__root__'
| '/_sidebar'
| '/about'
| '/_sidebar/'
| '/_sidebar/contacts/$contractId'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
ContactsContractIdRoute: typeof ContactsContractIdRoute
SidebarRoute: typeof SidebarRouteWithChildren
AboutRoute: typeof AboutRoute
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
ContactsContractIdRoute: ContactsContractIdRoute,
SidebarRoute: SidebarRouteWithChildren,
AboutRoute: AboutRoute,
}
export const routeTree = rootRoute
@@ -96,15 +146,27 @@ export const routeTree = rootRoute
"__root__": {
"filePath": "__root.tsx",
"children": [
"/",
"/contacts/$contractId"
"/_sidebar",
"/about"
]
},
"/": {
"filePath": "index.tsx"
"/_sidebar": {
"filePath": "_sidebar.tsx",
"children": [
"/_sidebar/",
"/_sidebar/contacts/$contractId"
]
},
"/contacts/$contractId": {
"filePath": "contacts.$contractId.tsx"
"/about": {
"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 appCss from '@/App.css?url';
import { getContacts } from "@/data";
import type { ReactNode } from "react";
async function fetchContacts() {
const contacts = await getContacts();
return { contacts };
}
export const Route = createRootRoute({
loader: fetchContacts,
head: () => ({
meta: [{
charSet: 'utf-8',
@@ -34,57 +27,9 @@ export const Route = createRootRoute({
});
function RootLayout() {
const { contacts } = Route.useLoaderData();
return (
<RootDocument>
<div id="sidebar">
<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/>
</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 type { ContactRecord } from "@/data.ts";
export const Route = createFileRoute('/contacts/$contractId')({
export const Route = createFileRoute('/_sidebar/contacts/$contractId')({
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>
);
}