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/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
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 { 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
<Outlet/>
|
||||
<TanStackRouterDevtools/>
|
||||
</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 type { ContactRecord } from "@/data.ts";
|
||||
|
||||
export const Route = createFileRoute('/contacts/$contractId')({
|
||||
export const Route = createFileRoute('/_sidebar/contacts/$contractId')({
|
||||
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