Complete "Layout Routes"
This commit is contained in:
14
package.json
14
package.json
@@ -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
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
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
70
src/routes/_sidebar.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
19
src/routes/_sidebar/index.tsx
Normal file
19
src/routes/_sidebar/index.tsx
Normal 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
49
src/routes/about.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router';
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/')({
|
|
||||||
component: App,
|
|
||||||
});
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
return (
|
|
||||||
<div></div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user