From 215c4c98119f40f1afbe69efd45aa163edfa17eb Mon Sep 17 00:00:00 2001 From: Birk Skyum <birk.skyum@pm.me> Date: Sun, 30 Mar 2025 22:55:43 +0200 Subject: [PATCH 1/9] add solid/kitchen-sink-solid-query --- .../.gitignore | 5 + .../.vscode/settings.json | 11 + .../README.md | 6 + .../index.html | 12 + .../package.json | 30 + .../postcss.config.mjs | 6 + .../src/components/InvoiceFields.tsx | 33 + .../src/components/Spinner.tsx | 19 + .../src/hooks/useSessionStorage.tsx | 13 + .../src/main.tsx | 151 +++ .../src/routeTree.gen.ts | 723 +++++++++++ .../src/routes/__root.tsx | 80 ++ .../src/routes/_auth.profile.tsx | 17 + .../src/routes/_auth.tsx | 26 + .../src/routes/_pathlessLayout.route-a.tsx | 9 + .../src/routes/_pathlessLayout.route-b.tsx | 9 + .../src/routes/_pathlessLayout.tsx | 15 + .../src/routes/dashboard.index.tsx | 23 + .../routes/dashboard.invoices.$invoiceId.tsx | 125 ++ .../src/routes/dashboard.invoices.index.tsx | 58 + .../src/routes/dashboard.invoices.route.tsx | 55 + .../src/routes/dashboard.route.tsx | 38 + .../src/routes/dashboard.users.index.tsx | 29 + .../src/routes/dashboard.users.route.tsx | 152 +++ .../src/routes/dashboard.users.user.tsx | 31 + .../expensive/-components/Expensive.tsx | 8 + .../src/routes/expensive/index.tsx | 6 + .../src/routes/foo/bar.tsx | 7 + .../src/routes/index.tsx | 36 + .../src/routes/login.tsx | 68 + .../src/styles.css | 13 + .../src/utils/auth.tsx | 19 + .../src/utils/mockTodos.ts | 174 +++ .../src/utils/queryOptions.ts | 53 + .../src/utils/utils.tsx | 33 + .../tailwind.config.mjs | 4 + .../tsconfig.dev.json | 10 + .../tsconfig.json | 10 + .../vite.config.js | 11 + .../solid/kitchen-sink-solid-query/.gitignore | 5 + .../.vscode/settings.json | 11 + .../solid/kitchen-sink-solid-query/README.md | 6 + .../solid/kitchen-sink-solid-query/index.html | 12 + .../kitchen-sink-solid-query/package.json | 29 + .../postcss.config.mjs | 6 + .../src/Expensive.tsx | 8 + .../kitchen-sink-solid-query/src/main.tsx | 1118 +++++++++++++++++ .../kitchen-sink-solid-query/src/mockTodos.ts | 174 +++ .../kitchen-sink-solid-query/src/styles.css | 13 + .../kitchen-sink-solid-query/src/utils.tsx | 33 + .../tailwind.config.mjs | 4 + .../tsconfig.dev.json | 10 + .../kitchen-sink-solid-query/tsconfig.json | 13 + .../kitchen-sink-solid-query/vite.config.js | 7 + pnpm-lock.yaml | 105 ++ 55 files changed, 3682 insertions(+) create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/.gitignore create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/.vscode/settings.json create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/README.md create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/index.html create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/package.json create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/postcss.config.mjs create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/components/InvoiceFields.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/components/Spinner.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/hooks/useSessionStorage.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/main.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routeTree.gen.ts create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/__root.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.profile.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-a.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-b.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.index.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.index.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.route.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.route.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.index.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.route.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.user.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/expensive/-components/Expensive.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/expensive/index.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/foo/bar.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/index.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/routes/login.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/styles.css create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/utils/auth.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/utils/mockTodos.ts create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/utils/queryOptions.ts create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/src/utils/utils.tsx create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/tailwind.config.mjs create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/tsconfig.dev.json create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/tsconfig.json create mode 100644 examples/solid/kitchen-sink-solid-query-file-based/vite.config.js create mode 100644 examples/solid/kitchen-sink-solid-query/.gitignore create mode 100644 examples/solid/kitchen-sink-solid-query/.vscode/settings.json create mode 100644 examples/solid/kitchen-sink-solid-query/README.md create mode 100644 examples/solid/kitchen-sink-solid-query/index.html create mode 100644 examples/solid/kitchen-sink-solid-query/package.json create mode 100644 examples/solid/kitchen-sink-solid-query/postcss.config.mjs create mode 100644 examples/solid/kitchen-sink-solid-query/src/Expensive.tsx create mode 100644 examples/solid/kitchen-sink-solid-query/src/main.tsx create mode 100644 examples/solid/kitchen-sink-solid-query/src/mockTodos.ts create mode 100644 examples/solid/kitchen-sink-solid-query/src/styles.css create mode 100644 examples/solid/kitchen-sink-solid-query/src/utils.tsx create mode 100644 examples/solid/kitchen-sink-solid-query/tailwind.config.mjs create mode 100644 examples/solid/kitchen-sink-solid-query/tsconfig.dev.json create mode 100644 examples/solid/kitchen-sink-solid-query/tsconfig.json create mode 100644 examples/solid/kitchen-sink-solid-query/vite.config.js diff --git a/examples/solid/kitchen-sink-solid-query-file-based/.gitignore b/examples/solid/kitchen-sink-solid-query-file-based/.gitignore new file mode 100644 index 0000000000..d451ff16c1 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/.gitignore @@ -0,0 +1,5 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local diff --git a/examples/solid/kitchen-sink-solid-query-file-based/.vscode/settings.json b/examples/solid/kitchen-sink-solid-query-file-based/.vscode/settings.json new file mode 100644 index 0000000000..00b5278e58 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + }, + "files.readonlyInclude": { + "**/routeTree.gen.ts": true + } +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/README.md b/examples/solid/kitchen-sink-solid-query-file-based/README.md new file mode 100644 index 0000000000..115199d292 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm start` or `yarn start` diff --git a/examples/solid/kitchen-sink-solid-query-file-based/index.html b/examples/solid/kitchen-sink-solid-query-file-based/index.html new file mode 100644 index 0000000000..9b6335c0ac --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/index.html @@ -0,0 +1,12 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Vite App</title> + </head> + <body> + <div id="app"></div> + <script type="module" src="/src/main.tsx"></script> + </body> +</html> diff --git a/examples/solid/kitchen-sink-solid-query-file-based/package.json b/examples/solid/kitchen-sink-solid-query-file-based/package.json new file mode 100644 index 0000000000..abb6175bf2 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/package.json @@ -0,0 +1,30 @@ +{ + "name": "tanstack-router-solid-example-kitchen-sink-solid-query-file-based", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port 3000", + "build": "vite build", + "serve": "vite preview", + "start": "vite" + }, + "dependencies": { + "@tanstack/solid-query": "^5.71.0", + "@tanstack/solid-query-devtools": "^5.71.0", + "@tanstack/solid-router": "^1.114.29", + "@tanstack/solid-router-devtools": "^1.114.29", + "@tanstack/router-plugin": "^1.114.29", + "immer": "^10.1.1", + "solid-js": "^1.9.5", + "redaxios": "^0.5.1", + "postcss": "^8.5.1", + "autoprefixer": "^10.4.20", + "tailwindcss": "^3.4.17", + "zod": "^3.24.2" + }, + "devDependencies": { + "vite-plugin-solid": "^2.11.6", + "typescript": "^5.7.2", + "vite": "^6.1.0" + } +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/postcss.config.mjs b/examples/solid/kitchen-sink-solid-query-file-based/postcss.config.mjs new file mode 100644 index 0000000000..2e7af2b7f1 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/postcss.config.mjs @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/components/InvoiceFields.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/components/InvoiceFields.tsx new file mode 100644 index 0000000000..96a5299c8f --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/components/InvoiceFields.tsx @@ -0,0 +1,33 @@ +import type { Invoice } from '../utils/mockTodos' + +export function InvoiceFields({ + invoice, + disabled, +}: { + invoice?: Invoice + disabled?: boolean +}) { + return ( + <div class="space-y-2"> + <h2 class="font-bold text-lg"> + <input + name="title" + value={invoice?.title ?? ''} + placeholder="Invoice Title" + class="border border-opacity-50 rounded p-2 w-full" + disabled={disabled} + /> + </h2> + <div> + <textarea + name="body" + value={invoice?.body ?? ''} + rows={6} + placeholder="Invoice Body..." + class="border border-opacity-50 p-2 rounded w-full" + disabled={disabled} + /> + </div> + </div> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/components/Spinner.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/components/Spinner.tsx new file mode 100644 index 0000000000..d1e7e34d97 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/components/Spinner.tsx @@ -0,0 +1,19 @@ +export function Spinner({ + show, + wait, +}: { + show?: boolean + wait?: `delay-${number}` +}) { + return ( + <div + class={`inline-block animate-spin px-3 transition ${ + (show ?? true) + ? `opacity-1 duration-500 ${wait ?? 'delay-300'}` + : 'duration-500 opacity-0 delay-0' + }`} + > + ⍥ + </div> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/hooks/useSessionStorage.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/hooks/useSessionStorage.tsx new file mode 100644 index 0000000000..710fd6797c --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/hooks/useSessionStorage.tsx @@ -0,0 +1,13 @@ +import * as Solid from 'solid-js' + +export function useSessionStorage<T>(key: string, initialValue: T) { + + const stored = sessionStorage.getItem(key) + const [state, setState] = Solid.createSignal<T>(stored ? JSON.parse(stored) : initialValue) + + Solid.createEffect(() => { + sessionStorage.setItem(key, JSON.stringify(state())) + }, [state()]) + + return [state, setState] +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/main.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/main.tsx new file mode 100644 index 0000000000..276870d3ba --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/main.tsx @@ -0,0 +1,151 @@ +import {render} from 'solid-js/web' +import { + ErrorComponent, + RouterProvider, + createRouter, +} from '@tanstack/solid-router' +import { QueryClient, QueryClientProvider } from '@tanstack/solid-query' +import { auth } from './utils/auth' +import { Spinner } from './components/Spinner' +import { routeTree } from './routeTree.gen' +import { useSessionStorage } from './hooks/useSessionStorage' +import './styles.css' + +// + +export const queryClient = new QueryClient() + +const router = createRouter({ + routeTree, + defaultPendingComponent: () => ( + <div class={`p-2 text-2xl`}> + <Spinner /> + </div> + ), + defaultErrorComponent: ({ error }) => <ErrorComponent error={error} />, + context: { + auth: undefined!, // We'll inject this when we render + queryClient: queryClient, // Type assertion to fix the type mismatch + }, + defaultPreload: 'intent', + // Since we're using Solid Query, we don't want loader calls to ever be stale + // This will ensure that the loader is always called when the route is preloaded or visited + defaultPreloadStaleTime: 0, + scrollRestoration: true, +}) + +declare module '@tanstack/solid-router' { + interface Register { + router: typeof router + } +} + +function App() { + // This stuff is just to tweak our sandbox setup in real-time + const [loaderDelay, setLoaderDelay] = useSessionStorage('loaderDelay', 500) + const [pendingMs, setPendingMs] = useSessionStorage('pendingMs', 1000) + const [pendingMinMs, setPendingMinMs] = useSessionStorage('pendingMinMs', 500) + + return ( + <> + <div class="text-xs fixed w-52 shadow-md shadow-black/20 rounded bottom-2 left-2 bg-white dark:bg-gray-800 bg-opacity-75 border-b flex flex-col gap-1 flex-wrap items-left divide-y"> + <div class="p-2 space-y-2"> + <div class="flex gap-2"> + <button + class="bg-blue-500 text-white rounded p-1 px-2" + onClick={() => { + setLoaderDelay(150) + }} + > + Fast + </button> + <button + class="bg-blue-500 text-white rounded p-1 px-2" + onClick={() => { + setLoaderDelay(500) + }} + > + Fast 3G + </button> + <button + class="bg-blue-500 text-white rounded p-1 px-2" + onClick={() => { + setLoaderDelay(2000) + }} + > + Slow 3G + </button> + </div> + <div> + <div>Loader Delay: {loaderDelay()}ms</div> + <input + type="range" + min="0" + max="5000" + step="100" + value={loaderDelay()} + onChange={(e) => setLoaderDelay(e.target.valueAsNumber)} + class="w-full" + /> + </div> + </div> + <div class="p-2 space-y-2"> + <div class="flex gap-2"> + <button + class="bg-blue-500 text-white rounded p-1 px-2" + onClick={() => { + setPendingMs(1000) + setPendingMinMs(500) + }} + > + Reset to Default + </button> + </div> + <div> + <div>defaultPendingMs: {pendingMs()}ms</div> + <input + type="range" + min="0" + max="5000" + step="100" + value={pendingMs()} + onChange={(e) => setPendingMs(e.target.valueAsNumber)} + class="w-full" + /> + </div> + <div> + <div>defaultPendingMinMs: {pendingMinMs()}ms</div> + <input + type="range" + min="0" + max="5000" + step="100" + value={pendingMinMs()} + onChange={(e) => setPendingMinMs(e.target.valueAsNumber)} + class="w-full" + /> + </div> + </div> + </div> + <RouterProvider + router={router} + defaultPreload="intent" + defaultPendingMs={pendingMs()} + defaultPendingMinMs={pendingMinMs()} + context={{ + auth, + }} + /> + </> + ) +} + +const rootElement = document.getElementById('app')! +if (!rootElement.innerHTML) { + render(()=> + <QueryClientProvider client={queryClient}> + <App /> + </QueryClientProvider>, + rootElement + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routeTree.gen.ts b/examples/solid/kitchen-sink-solid-query-file-based/src/routeTree.gen.ts new file mode 100644 index 0000000000..729ef3f63f --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routeTree.gen.ts @@ -0,0 +1,723 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +// Import Routes + +import type { FileRoutesByPath, CreateFileRoute } from '@tanstack/solid-router' +import { Route as rootRoute } from './routes/__root' +import { Route as LoginRouteImport } from './routes/login' +import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' +import { Route as AuthRouteImport } from './routes/_auth' +import { Route as DashboardRouteRouteImport } from './routes/dashboard.route' +import { Route as IndexRouteImport } from './routes/index' +import { Route as ExpensiveIndexRouteImport } from './routes/expensive/index' +import { Route as DashboardIndexRouteImport } from './routes/dashboard.index' +import { Route as FooBarRouteImport } from './routes/foo/bar' +import { Route as PathlessLayoutRouteBRouteImport } from './routes/_pathlessLayout.route-b' +import { Route as PathlessLayoutRouteARouteImport } from './routes/_pathlessLayout.route-a' +import { Route as AuthProfileRouteImport } from './routes/_auth.profile' +import { Route as DashboardUsersRouteRouteImport } from './routes/dashboard.users.route' +import { Route as DashboardInvoicesRouteRouteImport } from './routes/dashboard.invoices.route' +import { Route as DashboardUsersIndexRouteImport } from './routes/dashboard.users.index' +import { Route as DashboardInvoicesIndexRouteImport } from './routes/dashboard.invoices.index' +import { Route as DashboardUsersUserRouteImport } from './routes/dashboard.users.user' +import { Route as DashboardInvoicesInvoiceIdRouteImport } from './routes/dashboard.invoices.$invoiceId' + +// Create/Update Routes + +const LoginRoute = LoginRouteImport.update({ + id: '/login', + path: '/login', + getParentRoute: () => rootRoute, +} as any) + +const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ + id: '/_pathlessLayout', + getParentRoute: () => rootRoute, +} as any) + +const AuthRoute = AuthRouteImport.update({ + id: '/_auth', + getParentRoute: () => rootRoute, +} as any) + +const DashboardRouteRoute = DashboardRouteRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => rootRoute, +} as any) + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRoute, +} as any) + +const ExpensiveIndexRoute = ExpensiveIndexRouteImport.update({ + id: '/expensive/', + path: '/expensive/', + getParentRoute: () => rootRoute, +} as any) + +const DashboardIndexRoute = DashboardIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => DashboardRouteRoute, +} as any) + +const FooBarRoute = FooBarRouteImport.update({ + id: '/foo/bar', + path: '/foo/bar', + getParentRoute: () => rootRoute, +} as any) + +const PathlessLayoutRouteBRoute = PathlessLayoutRouteBRouteImport.update({ + id: '/route-b', + path: '/route-b', + getParentRoute: () => PathlessLayoutRoute, +} as any) + +const PathlessLayoutRouteARoute = PathlessLayoutRouteARouteImport.update({ + id: '/route-a', + path: '/route-a', + getParentRoute: () => PathlessLayoutRoute, +} as any) + +const AuthProfileRoute = AuthProfileRouteImport.update({ + id: '/profile', + path: '/profile', + getParentRoute: () => AuthRoute, +} as any) + +const DashboardUsersRouteRoute = DashboardUsersRouteRouteImport.update({ + id: '/users', + path: '/users', + getParentRoute: () => DashboardRouteRoute, +} as any) + +const DashboardInvoicesRouteRoute = DashboardInvoicesRouteRouteImport.update({ + id: '/invoices', + path: '/invoices', + getParentRoute: () => DashboardRouteRoute, +} as any) + +const DashboardUsersIndexRoute = DashboardUsersIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => DashboardUsersRouteRoute, +} as any) + +const DashboardInvoicesIndexRoute = DashboardInvoicesIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => DashboardInvoicesRouteRoute, +} as any) + +const DashboardUsersUserRoute = DashboardUsersUserRouteImport.update({ + id: '/user', + path: '/user', + getParentRoute: () => DashboardUsersRouteRoute, +} as any) + +const DashboardInvoicesInvoiceIdRoute = + DashboardInvoicesInvoiceIdRouteImport.update({ + id: '/$invoiceId', + path: '/$invoiceId', + getParentRoute: () => DashboardInvoicesRouteRoute, + } as any) + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/solid-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRoute + } + '/dashboard': { + id: '/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof DashboardRouteRouteImport + parentRoute: typeof rootRoute + } + '/_auth': { + id: '/_auth' + path: '' + fullPath: '' + preLoaderRoute: typeof AuthRouteImport + parentRoute: typeof rootRoute + } + '/_pathlessLayout': { + id: '/_pathlessLayout' + path: '' + fullPath: '' + preLoaderRoute: typeof PathlessLayoutRouteImport + parentRoute: typeof rootRoute + } + '/login': { + id: '/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginRouteImport + parentRoute: typeof rootRoute + } + '/dashboard/invoices': { + id: '/dashboard/invoices' + path: '/invoices' + fullPath: '/dashboard/invoices' + preLoaderRoute: typeof DashboardInvoicesRouteRouteImport + parentRoute: typeof DashboardRouteRouteImport + } + '/dashboard/users': { + id: '/dashboard/users' + path: '/users' + fullPath: '/dashboard/users' + preLoaderRoute: typeof DashboardUsersRouteRouteImport + parentRoute: typeof DashboardRouteRouteImport + } + '/_auth/profile': { + id: '/_auth/profile' + path: '/profile' + fullPath: '/profile' + preLoaderRoute: typeof AuthProfileRouteImport + parentRoute: typeof AuthRouteImport + } + '/_pathlessLayout/route-a': { + id: '/_pathlessLayout/route-a' + path: '/route-a' + fullPath: '/route-a' + preLoaderRoute: typeof PathlessLayoutRouteARouteImport + parentRoute: typeof PathlessLayoutRouteImport + } + '/_pathlessLayout/route-b': { + id: '/_pathlessLayout/route-b' + path: '/route-b' + fullPath: '/route-b' + preLoaderRoute: typeof PathlessLayoutRouteBRouteImport + parentRoute: typeof PathlessLayoutRouteImport + } + '/foo/bar': { + id: '/foo/bar' + path: '/foo/bar' + fullPath: '/foo/bar' + preLoaderRoute: typeof FooBarRouteImport + parentRoute: typeof rootRoute + } + '/dashboard/': { + id: '/dashboard/' + path: '/' + fullPath: '/dashboard/' + preLoaderRoute: typeof DashboardIndexRouteImport + parentRoute: typeof DashboardRouteRouteImport + } + '/expensive/': { + id: '/expensive/' + path: '/expensive' + fullPath: '/expensive' + preLoaderRoute: typeof ExpensiveIndexRouteImport + parentRoute: typeof rootRoute + } + '/dashboard/invoices/$invoiceId': { + id: '/dashboard/invoices/$invoiceId' + path: '/$invoiceId' + fullPath: '/dashboard/invoices/$invoiceId' + preLoaderRoute: typeof DashboardInvoicesInvoiceIdRouteImport + parentRoute: typeof DashboardInvoicesRouteRouteImport + } + '/dashboard/users/user': { + id: '/dashboard/users/user' + path: '/user' + fullPath: '/dashboard/users/user' + preLoaderRoute: typeof DashboardUsersUserRouteImport + parentRoute: typeof DashboardUsersRouteRouteImport + } + '/dashboard/invoices/': { + id: '/dashboard/invoices/' + path: '/' + fullPath: '/dashboard/invoices/' + preLoaderRoute: typeof DashboardInvoicesIndexRouteImport + parentRoute: typeof DashboardInvoicesRouteRouteImport + } + '/dashboard/users/': { + id: '/dashboard/users/' + path: '/' + fullPath: '/dashboard/users/' + preLoaderRoute: typeof DashboardUsersIndexRouteImport + parentRoute: typeof DashboardUsersRouteRouteImport + } + } +} + +// Add type-safety to the createFileRoute function across the route tree + +declare module './routes/index' { + const createFileRoute: CreateFileRoute< + '/', + FileRoutesByPath['/']['parentRoute'], + FileRoutesByPath['/']['id'], + FileRoutesByPath['/']['path'], + FileRoutesByPath['/']['fullPath'] + > +} +declare module './routes/dashboard.route' { + const createFileRoute: CreateFileRoute< + '/dashboard', + FileRoutesByPath['/dashboard']['parentRoute'], + FileRoutesByPath['/dashboard']['id'], + FileRoutesByPath['/dashboard']['path'], + FileRoutesByPath['/dashboard']['fullPath'] + > +} +declare module './routes/_auth' { + const createFileRoute: CreateFileRoute< + '/_auth', + FileRoutesByPath['/_auth']['parentRoute'], + FileRoutesByPath['/_auth']['id'], + FileRoutesByPath['/_auth']['path'], + FileRoutesByPath['/_auth']['fullPath'] + > +} +declare module './routes/_pathlessLayout' { + const createFileRoute: CreateFileRoute< + '/_pathlessLayout', + FileRoutesByPath['/_pathlessLayout']['parentRoute'], + FileRoutesByPath['/_pathlessLayout']['id'], + FileRoutesByPath['/_pathlessLayout']['path'], + FileRoutesByPath['/_pathlessLayout']['fullPath'] + > +} +declare module './routes/login' { + const createFileRoute: CreateFileRoute< + '/login', + FileRoutesByPath['/login']['parentRoute'], + FileRoutesByPath['/login']['id'], + FileRoutesByPath['/login']['path'], + FileRoutesByPath['/login']['fullPath'] + > +} +declare module './routes/dashboard.invoices.route' { + const createFileRoute: CreateFileRoute< + '/dashboard/invoices', + FileRoutesByPath['/dashboard/invoices']['parentRoute'], + FileRoutesByPath['/dashboard/invoices']['id'], + FileRoutesByPath['/dashboard/invoices']['path'], + FileRoutesByPath['/dashboard/invoices']['fullPath'] + > +} +declare module './routes/dashboard.users.route' { + const createFileRoute: CreateFileRoute< + '/dashboard/users', + FileRoutesByPath['/dashboard/users']['parentRoute'], + FileRoutesByPath['/dashboard/users']['id'], + FileRoutesByPath['/dashboard/users']['path'], + FileRoutesByPath['/dashboard/users']['fullPath'] + > +} +declare module './routes/_auth.profile' { + const createFileRoute: CreateFileRoute< + '/_auth/profile', + FileRoutesByPath['/_auth/profile']['parentRoute'], + FileRoutesByPath['/_auth/profile']['id'], + FileRoutesByPath['/_auth/profile']['path'], + FileRoutesByPath['/_auth/profile']['fullPath'] + > +} +declare module './routes/_pathlessLayout.route-a' { + const createFileRoute: CreateFileRoute< + '/_pathlessLayout/route-a', + FileRoutesByPath['/_pathlessLayout/route-a']['parentRoute'], + FileRoutesByPath['/_pathlessLayout/route-a']['id'], + FileRoutesByPath['/_pathlessLayout/route-a']['path'], + FileRoutesByPath['/_pathlessLayout/route-a']['fullPath'] + > +} +declare module './routes/_pathlessLayout.route-b' { + const createFileRoute: CreateFileRoute< + '/_pathlessLayout/route-b', + FileRoutesByPath['/_pathlessLayout/route-b']['parentRoute'], + FileRoutesByPath['/_pathlessLayout/route-b']['id'], + FileRoutesByPath['/_pathlessLayout/route-b']['path'], + FileRoutesByPath['/_pathlessLayout/route-b']['fullPath'] + > +} +declare module './routes/foo/bar' { + const createFileRoute: CreateFileRoute< + '/foo/bar', + FileRoutesByPath['/foo/bar']['parentRoute'], + FileRoutesByPath['/foo/bar']['id'], + FileRoutesByPath['/foo/bar']['path'], + FileRoutesByPath['/foo/bar']['fullPath'] + > +} +declare module './routes/dashboard.index' { + const createFileRoute: CreateFileRoute< + '/dashboard/', + FileRoutesByPath['/dashboard/']['parentRoute'], + FileRoutesByPath['/dashboard/']['id'], + FileRoutesByPath['/dashboard/']['path'], + FileRoutesByPath['/dashboard/']['fullPath'] + > +} +declare module './routes/expensive/index' { + const createFileRoute: CreateFileRoute< + '/expensive/', + FileRoutesByPath['/expensive/']['parentRoute'], + FileRoutesByPath['/expensive/']['id'], + FileRoutesByPath['/expensive/']['path'], + FileRoutesByPath['/expensive/']['fullPath'] + > +} +declare module './routes/dashboard.invoices.$invoiceId' { + const createFileRoute: CreateFileRoute< + '/dashboard/invoices/$invoiceId', + FileRoutesByPath['/dashboard/invoices/$invoiceId']['parentRoute'], + FileRoutesByPath['/dashboard/invoices/$invoiceId']['id'], + FileRoutesByPath['/dashboard/invoices/$invoiceId']['path'], + FileRoutesByPath['/dashboard/invoices/$invoiceId']['fullPath'] + > +} +declare module './routes/dashboard.users.user' { + const createFileRoute: CreateFileRoute< + '/dashboard/users/user', + FileRoutesByPath['/dashboard/users/user']['parentRoute'], + FileRoutesByPath['/dashboard/users/user']['id'], + FileRoutesByPath['/dashboard/users/user']['path'], + FileRoutesByPath['/dashboard/users/user']['fullPath'] + > +} +declare module './routes/dashboard.invoices.index' { + const createFileRoute: CreateFileRoute< + '/dashboard/invoices/', + FileRoutesByPath['/dashboard/invoices/']['parentRoute'], + FileRoutesByPath['/dashboard/invoices/']['id'], + FileRoutesByPath['/dashboard/invoices/']['path'], + FileRoutesByPath['/dashboard/invoices/']['fullPath'] + > +} +declare module './routes/dashboard.users.index' { + const createFileRoute: CreateFileRoute< + '/dashboard/users/', + FileRoutesByPath['/dashboard/users/']['parentRoute'], + FileRoutesByPath['/dashboard/users/']['id'], + FileRoutesByPath['/dashboard/users/']['path'], + FileRoutesByPath['/dashboard/users/']['fullPath'] + > +} + +// Create and export the route tree + +interface DashboardInvoicesRouteRouteChildren { + DashboardInvoicesInvoiceIdRoute: typeof DashboardInvoicesInvoiceIdRoute + DashboardInvoicesIndexRoute: typeof DashboardInvoicesIndexRoute +} + +const DashboardInvoicesRouteRouteChildren: DashboardInvoicesRouteRouteChildren = + { + DashboardInvoicesInvoiceIdRoute: DashboardInvoicesInvoiceIdRoute, + DashboardInvoicesIndexRoute: DashboardInvoicesIndexRoute, + } + +const DashboardInvoicesRouteRouteWithChildren = + DashboardInvoicesRouteRoute._addFileChildren( + DashboardInvoicesRouteRouteChildren, + ) + +interface DashboardUsersRouteRouteChildren { + DashboardUsersUserRoute: typeof DashboardUsersUserRoute + DashboardUsersIndexRoute: typeof DashboardUsersIndexRoute +} + +const DashboardUsersRouteRouteChildren: DashboardUsersRouteRouteChildren = { + DashboardUsersUserRoute: DashboardUsersUserRoute, + DashboardUsersIndexRoute: DashboardUsersIndexRoute, +} + +const DashboardUsersRouteRouteWithChildren = + DashboardUsersRouteRoute._addFileChildren(DashboardUsersRouteRouteChildren) + +interface DashboardRouteRouteChildren { + DashboardInvoicesRouteRoute: typeof DashboardInvoicesRouteRouteWithChildren + DashboardUsersRouteRoute: typeof DashboardUsersRouteRouteWithChildren + DashboardIndexRoute: typeof DashboardIndexRoute +} + +const DashboardRouteRouteChildren: DashboardRouteRouteChildren = { + DashboardInvoicesRouteRoute: DashboardInvoicesRouteRouteWithChildren, + DashboardUsersRouteRoute: DashboardUsersRouteRouteWithChildren, + DashboardIndexRoute: DashboardIndexRoute, +} + +const DashboardRouteRouteWithChildren = DashboardRouteRoute._addFileChildren( + DashboardRouteRouteChildren, +) + +interface AuthRouteChildren { + AuthProfileRoute: typeof AuthProfileRoute +} + +const AuthRouteChildren: AuthRouteChildren = { + AuthProfileRoute: AuthProfileRoute, +} + +const AuthRouteWithChildren = AuthRoute._addFileChildren(AuthRouteChildren) + +interface PathlessLayoutRouteChildren { + PathlessLayoutRouteARoute: typeof PathlessLayoutRouteARoute + PathlessLayoutRouteBRoute: typeof PathlessLayoutRouteBRoute +} + +const PathlessLayoutRouteChildren: PathlessLayoutRouteChildren = { + PathlessLayoutRouteARoute: PathlessLayoutRouteARoute, + PathlessLayoutRouteBRoute: PathlessLayoutRouteBRoute, +} + +const PathlessLayoutRouteWithChildren = PathlessLayoutRoute._addFileChildren( + PathlessLayoutRouteChildren, +) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/dashboard': typeof DashboardRouteRouteWithChildren + '': typeof PathlessLayoutRouteWithChildren + '/login': typeof LoginRoute + '/dashboard/invoices': typeof DashboardInvoicesRouteRouteWithChildren + '/dashboard/users': typeof DashboardUsersRouteRouteWithChildren + '/profile': typeof AuthProfileRoute + '/route-a': typeof PathlessLayoutRouteARoute + '/route-b': typeof PathlessLayoutRouteBRoute + '/foo/bar': typeof FooBarRoute + '/dashboard/': typeof DashboardIndexRoute + '/expensive': typeof ExpensiveIndexRoute + '/dashboard/invoices/$invoiceId': typeof DashboardInvoicesInvoiceIdRoute + '/dashboard/users/user': typeof DashboardUsersUserRoute + '/dashboard/invoices/': typeof DashboardInvoicesIndexRoute + '/dashboard/users/': typeof DashboardUsersIndexRoute +} + +export interface FileRoutesByTo { + '/': typeof IndexRoute + '': typeof PathlessLayoutRouteWithChildren + '/login': typeof LoginRoute + '/profile': typeof AuthProfileRoute + '/route-a': typeof PathlessLayoutRouteARoute + '/route-b': typeof PathlessLayoutRouteBRoute + '/foo/bar': typeof FooBarRoute + '/dashboard': typeof DashboardIndexRoute + '/expensive': typeof ExpensiveIndexRoute + '/dashboard/invoices/$invoiceId': typeof DashboardInvoicesInvoiceIdRoute + '/dashboard/users/user': typeof DashboardUsersUserRoute + '/dashboard/invoices': typeof DashboardInvoicesIndexRoute + '/dashboard/users': typeof DashboardUsersIndexRoute +} + +export interface FileRoutesById { + __root__: typeof rootRoute + '/': typeof IndexRoute + '/dashboard': typeof DashboardRouteRouteWithChildren + '/_auth': typeof AuthRouteWithChildren + '/_pathlessLayout': typeof PathlessLayoutRouteWithChildren + '/login': typeof LoginRoute + '/dashboard/invoices': typeof DashboardInvoicesRouteRouteWithChildren + '/dashboard/users': typeof DashboardUsersRouteRouteWithChildren + '/_auth/profile': typeof AuthProfileRoute + '/_pathlessLayout/route-a': typeof PathlessLayoutRouteARoute + '/_pathlessLayout/route-b': typeof PathlessLayoutRouteBRoute + '/foo/bar': typeof FooBarRoute + '/dashboard/': typeof DashboardIndexRoute + '/expensive/': typeof ExpensiveIndexRoute + '/dashboard/invoices/$invoiceId': typeof DashboardInvoicesInvoiceIdRoute + '/dashboard/users/user': typeof DashboardUsersUserRoute + '/dashboard/invoices/': typeof DashboardInvoicesIndexRoute + '/dashboard/users/': typeof DashboardUsersIndexRoute +} + +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/dashboard' + | '' + | '/login' + | '/dashboard/invoices' + | '/dashboard/users' + | '/profile' + | '/route-a' + | '/route-b' + | '/foo/bar' + | '/dashboard/' + | '/expensive' + | '/dashboard/invoices/$invoiceId' + | '/dashboard/users/user' + | '/dashboard/invoices/' + | '/dashboard/users/' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '' + | '/login' + | '/profile' + | '/route-a' + | '/route-b' + | '/foo/bar' + | '/dashboard' + | '/expensive' + | '/dashboard/invoices/$invoiceId' + | '/dashboard/users/user' + | '/dashboard/invoices' + | '/dashboard/users' + id: + | '__root__' + | '/' + | '/dashboard' + | '/_auth' + | '/_pathlessLayout' + | '/login' + | '/dashboard/invoices' + | '/dashboard/users' + | '/_auth/profile' + | '/_pathlessLayout/route-a' + | '/_pathlessLayout/route-b' + | '/foo/bar' + | '/dashboard/' + | '/expensive/' + | '/dashboard/invoices/$invoiceId' + | '/dashboard/users/user' + | '/dashboard/invoices/' + | '/dashboard/users/' + fileRoutesById: FileRoutesById +} + +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + DashboardRouteRoute: typeof DashboardRouteRouteWithChildren + AuthRoute: typeof AuthRouteWithChildren + PathlessLayoutRoute: typeof PathlessLayoutRouteWithChildren + LoginRoute: typeof LoginRoute + FooBarRoute: typeof FooBarRoute + ExpensiveIndexRoute: typeof ExpensiveIndexRoute +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + DashboardRouteRoute: DashboardRouteRouteWithChildren, + AuthRoute: AuthRouteWithChildren, + PathlessLayoutRoute: PathlessLayoutRouteWithChildren, + LoginRoute: LoginRoute, + FooBarRoute: FooBarRoute, + ExpensiveIndexRoute: ExpensiveIndexRoute, +} + +export const routeTree = rootRoute + ._addFileChildren(rootRouteChildren) + ._addFileTypes<FileRouteTypes>() + +/* ROUTE_MANIFEST_START +{ + "routes": { + "__root__": { + "filePath": "__root.tsx", + "children": [ + "/", + "/dashboard", + "/_auth", + "/_pathlessLayout", + "/login", + "/foo/bar", + "/expensive/" + ] + }, + "/": { + "filePath": "index.tsx" + }, + "/dashboard": { + "filePath": "dashboard.route.tsx", + "children": [ + "/dashboard/invoices", + "/dashboard/users", + "/dashboard/" + ] + }, + "/_auth": { + "filePath": "_auth.tsx", + "children": [ + "/_auth/profile" + ] + }, + "/_pathlessLayout": { + "filePath": "_pathlessLayout.tsx", + "children": [ + "/_pathlessLayout/route-a", + "/_pathlessLayout/route-b" + ] + }, + "/login": { + "filePath": "login.tsx" + }, + "/dashboard/invoices": { + "filePath": "dashboard.invoices.route.tsx", + "parent": "/dashboard", + "children": [ + "/dashboard/invoices/$invoiceId", + "/dashboard/invoices/" + ] + }, + "/dashboard/users": { + "filePath": "dashboard.users.route.tsx", + "parent": "/dashboard", + "children": [ + "/dashboard/users/user", + "/dashboard/users/" + ] + }, + "/_auth/profile": { + "filePath": "_auth.profile.tsx", + "parent": "/_auth" + }, + "/_pathlessLayout/route-a": { + "filePath": "_pathlessLayout.route-a.tsx", + "parent": "/_pathlessLayout" + }, + "/_pathlessLayout/route-b": { + "filePath": "_pathlessLayout.route-b.tsx", + "parent": "/_pathlessLayout" + }, + "/foo/bar": { + "filePath": "foo/bar.tsx" + }, + "/dashboard/": { + "filePath": "dashboard.index.tsx", + "parent": "/dashboard" + }, + "/expensive/": { + "filePath": "expensive/index.tsx" + }, + "/dashboard/invoices/$invoiceId": { + "filePath": "dashboard.invoices.$invoiceId.tsx", + "parent": "/dashboard/invoices" + }, + "/dashboard/users/user": { + "filePath": "dashboard.users.user.tsx", + "parent": "/dashboard/users" + }, + "/dashboard/invoices/": { + "filePath": "dashboard.invoices.index.tsx", + "parent": "/dashboard/invoices" + }, + "/dashboard/users/": { + "filePath": "dashboard.users.index.tsx", + "parent": "/dashboard/users" + } + } +} +ROUTE_MANIFEST_END */ diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/__root.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/__root.tsx new file mode 100644 index 0000000000..4752063de3 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/__root.tsx @@ -0,0 +1,80 @@ +import { + Link, + Outlet, + createRootRouteWithContext, + useRouterState, +} from '@tanstack/solid-router' +import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools' +import { SolidQueryDevtools } from '@tanstack/solid-query-devtools' +import { Spinner } from '../components/Spinner' +import type { QueryClient } from '@tanstack/solid-query' +import type { Auth } from '../utils/auth' + +function RouterSpinner() { + const isLoading = useRouterState({ select: (s) => s.status === 'pending' }) + return <Spinner show={isLoading()} /> +} + +export const Route = createRootRouteWithContext<{ + auth: Auth + queryClient: QueryClient +}>()({ + component: RootComponent, +}) + +function RootComponent() { + return ( + <> + <div class={`min-h-screen flex flex-col`}> + <div class={`flex items-center border-b gap-2`}> + <h1 class={`text-3xl p-2`}>Kitchen Sink</h1> + {/* Show a global spinner when the router is transitioning */} + <div class={`text-3xl`}> + <RouterSpinner /> + </div> + </div> + <div class={`flex-1 flex`}> + <div class={`divide-y w-56`}> + {( + [ + ['/', 'Home'], + ['/dashboard', 'Dashboard'], + ['/expensive', 'Expensive'], + ['/route-a', 'Pathless Layout A'], + ['/route-b', 'Pathless Layout B'], + ['/profile', 'Profile'], + ['/login', 'Login'], + ] as const + ).map(([to, label]) => { + return ( + <div> + <Link + to={to} + activeOptions={ + { + // If the route points to the root of it's parent, + // make sure it's only active if it's exact + // exact: to === '.', + } + } + preload="intent" + class={`block py-2 px-3 text-blue-700`} + // Make "active" links bold + activeProps={{ class: `font-bold` }} + > + {label} + </Link> + </div> + ) + })} + </div> + <div class={`flex-1 border-l`}> + <Outlet /> + </div> + </div> + </div> + <SolidQueryDevtools buttonPosition="top-right" /> + <TanStackRouterDevtools position="bottom-right" /> + </> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.profile.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.profile.tsx new file mode 100644 index 0000000000..60bafd1de7 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.profile.tsx @@ -0,0 +1,17 @@ + + +export const Route = createFileRoute({ + component: ProfileComponent, +}) + +function ProfileComponent() { + const routeContext = Route.useRouteContext() + + return ( + <div class="p-2 space-y-2"> + <div> + Username:<strong>{routeContext().username}</strong> + </div> + </div> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.tsx new file mode 100644 index 0000000000..8e95c11edb --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.tsx @@ -0,0 +1,26 @@ +import { redirect } from '@tanstack/solid-router' +import { auth } from '../utils/auth' + +export const Route = createFileRoute({ + // Before loading, authenticate the user via our auth context + // This will also happen during prefetching (e.g. hovering over links, etc) + beforeLoad: ({ context, location }) => { + // If the user is logged out, redirect them to the login page + if (context.auth.status === 'loggedOut') { + throw redirect({ + to: '/login', + search: { + // Use the current location to power a redirect after login + // (Do not use `router.state.resolvedLocation` as it can + // potentially lag behind the actual current location) + redirect: location.href, + }, + }) + } + + // Otherwise, return the user in context + return { + username: auth.username, + } + }, +}) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-a.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-a.tsx new file mode 100644 index 0000000000..1d1c583fa0 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-a.tsx @@ -0,0 +1,9 @@ + + +export const Route = createFileRoute({ + component: LayoutAComponent, +}) + +function LayoutAComponent() { + return <div>I'm A!</div> +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-b.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-b.tsx new file mode 100644 index 0000000000..3f3e60b070 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-b.tsx @@ -0,0 +1,9 @@ + + +export const Route = createFileRoute({ + component: LayoutBComponent, +}) + +function LayoutBComponent() { + return <div>I'm B!</div> +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.tsx new file mode 100644 index 0000000000..1ae71fc856 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.tsx @@ -0,0 +1,15 @@ +import { Outlet } from '@tanstack/solid-router' + +export const Route = createFileRoute({ + component: LayoutComponent, +}) + +function LayoutComponent() { + return ( + <div> + <div>Layout</div> + <hr /> + <Outlet /> + </div> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.index.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.index.tsx new file mode 100644 index 0000000000..2260d95bbc --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.index.tsx @@ -0,0 +1,23 @@ + +import { createQuery } from '@tanstack/solid-query' +import { invoicesQueryOptions } from '../utils/queryOptions' + +export const Route = createFileRoute({ + loader: (opts) => + opts.context.queryClient.ensureQueryData(invoicesQueryOptions()), + component: DashboardIndexComponent, +}) + +function DashboardIndexComponent() { + const invoicesQuery = createQuery(()=>invoicesQueryOptions()) + const invoices = invoicesQuery.data + + return ( + <div class="p-2"> + <div class="p-2"> + Welcome to the dashboard! You have{' '} + <strong>{invoices?.length} total invoices</strong>. + </div> + </div> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx new file mode 100644 index 0000000000..d8a07649fd --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx @@ -0,0 +1,125 @@ +import * as Solid from 'solid-js' +import { Link, useNavigate } from '@tanstack/solid-router' +import { createQuery } from '@tanstack/solid-query' +import { z } from 'zod' +import { InvoiceFields } from '../components/InvoiceFields' +import { + invoiceQueryOptions, + useUpdateInvoiceMutation, +} from '../utils/queryOptions' + +export const Route = createFileRoute({ + params: { + parse: (params) => ({ + invoiceId: z.number().int().parse(Number(params.invoiceId)), + }), + stringify: ({ invoiceId }) => ({ invoiceId: `${invoiceId}` }), + }, + validateSearch: (search) => + z + .object({ + showNotes: z.boolean().optional(), + notes: z.string().optional(), + }) + .parse(search), + loader: (opts) => + opts.context.queryClient.ensureQueryData( + invoiceQueryOptions(opts.params.invoiceId), + ), + component: InvoiceComponent, +}) + +function InvoiceComponent() { + const search = Route.useSearch() + const params = Route.useParams() + const navigate = useNavigate({ from: Route.fullPath }) + const invoiceQuery = createQuery(()=>invoiceQueryOptions(params().invoiceId)) + const invoice = invoiceQuery.data + const updateInvoiceMutation = useUpdateInvoiceMutation(params().invoiceId) + const [notes, setNotes] = Solid.createSignal(search().notes ?? '') + + Solid.createEffect(() => { + navigate({ + search: (old) => ({ + ...old, + notes: notes() ? notes() : undefined, + }), + replace: true, + params: true, + }) + }, [notes]) + + return ( + <form + onSubmit={(event) => { + event.preventDefault() + event.stopPropagation() + const formData = new FormData(event.target as HTMLFormElement) + updateInvoiceMutation.mutate({ + id: invoice!.id, + title: formData.get('title') as string, + body: formData.get('body') as string, + }) + }} + class="p-2 space-y-2" + > + <InvoiceFields + invoice={invoice!} + disabled={updateInvoiceMutation.status === 'pending'} + /> + <div> + <Link + from={Route.fullPath} + params={true} + search={(old) => ({ + ...old, + showNotes: old.showNotes ? undefined : true, + })} + class="text-blue-700" + > + {search().showNotes ? 'Close Notes' : 'Show Notes'}{' '} + </Link> + {search().showNotes ? ( + <> + <div> + <div class="h-2" /> + <textarea + value={notes()} + onChange={(e) => { + setNotes(e.target.value) + }} + rows={5} + class="shadow w-full p-2 rounded" + placeholder="Write some notes here..." + /> + <div class="italic text-xs"> + Notes are stored in the URL. Try copying the URL into a new tab! + </div> + </div> + </> + ) : null} + </div> + <div> + <button + class="bg-blue-500 rounded p-2 uppercase text-white font-black disabled:opacity-50" + disabled={updateInvoiceMutation.status === 'pending'} + > + Save + </button> + </div> + {updateInvoiceMutation.variables?.id === invoice!.id ? ( + <div> + {updateInvoiceMutation.status === 'success' ? ( + <div class="inline-block px-2 py-1 rounded bg-green-500 text-white animate-bounce [animation-iteration-count:2.5] [animation-duration:.3s]"> + Saved! + </div> + ) : updateInvoiceMutation.status === 'error' ? ( + <div class="inline-block px-2 py-1 rounded bg-red-500 text-white animate-bounce [animation-iteration-count:2.5] [animation-duration:.3s]"> + Failed to save. + </div> + ) : null} + </div> + ) : null} + </form> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.index.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.index.tsx new file mode 100644 index 0000000000..f5faa8022a --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.index.tsx @@ -0,0 +1,58 @@ + +import { InvoiceFields } from '../components/InvoiceFields' +import { Spinner } from '../components/Spinner' +import { useCreateInvoiceMutation } from '../utils/queryOptions' +import type { Invoice } from '../utils/mockTodos' + +export const Route = createFileRoute({ + component: InvoicesIndexComponent, +}) + +function InvoicesIndexComponent() { + const createInvoiceMutation = useCreateInvoiceMutation() + + return ( + <> + <div class="p-2"> + <form + onSubmit={(event) => { + event.preventDefault() + event.stopPropagation() + const formData = new FormData(event.target as HTMLFormElement) + createInvoiceMutation.mutate({ + title: formData.get('title') as string, + body: formData.get('body') as string, + }) + }} + class="space-y-2" + > + <div>Create a new Invoice:</div> + <InvoiceFields invoice={{} as Invoice} /> + <div> + <button + class="bg-blue-500 rounded p-2 uppercase text-white font-black disabled:opacity-50" + disabled={createInvoiceMutation.status === 'pending'} + > + {createInvoiceMutation.status === 'pending' ? ( + <> + Creating <Spinner /> + </> + ) : ( + 'Create' + )} + </button> + </div> + {createInvoiceMutation.status === 'success' ? ( + <div class="inline-block px-2 py-1 rounded bg-green-500 text-white animate-bounce [animation-iteration-count:2.5] [animation-duration:.3s]"> + Created! + </div> + ) : createInvoiceMutation.status === 'error' ? ( + <div class="inline-block px-2 py-1 rounded bg-red-500 text-white animate-bounce [animation-iteration-count:2.5] [animation-duration:.3s]"> + Failed to create. + </div> + ) : null} + </form> + </div> + </> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.route.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.route.tsx new file mode 100644 index 0000000000..72797ba863 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.route.tsx @@ -0,0 +1,55 @@ +import { Link, + MatchRoute, + Outlet } from '@tanstack/solid-router' +import { createQuery } from '@tanstack/solid-query' +import { Spinner } from '../components/Spinner' +import { invoicesQueryOptions } from '../utils/queryOptions' + +export const Route = createFileRoute({ + loader: (opts) => + opts.context.queryClient.ensureQueryData(invoicesQueryOptions()), + component: InvoicesComponent, +}) + +function InvoicesComponent() { + const invoicesQuery = createQuery(()=>invoicesQueryOptions()) + const invoices = invoicesQuery.data + + return ( + <div class="flex-1 flex"> + <div class="divide-y w-48"> + {invoices?.map((invoice) => { + return ( + <div> + <Link + to="/dashboard/invoices/$invoiceId" + params={{ + invoiceId: invoice.id, + }} + preload="intent" + class="block py-2 px-3 text-blue-700" + activeProps={{ class: `font-bold` }} + > + <pre class="text-sm"> + #{invoice.id} - {invoice.title.slice(0, 10)}{' '} + <MatchRoute + to="/dashboard/invoices/$invoiceId" + params={{ + invoiceId: invoice.id, + }} + pending + > + {(match) => <Spinner show={!!match} wait="delay-50" />} + </MatchRoute> + </pre> + </Link> + </div> + ) + })} + </div> + <div class="flex-1 border-l"> + <Outlet /> + </div> + </div> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.route.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.route.tsx new file mode 100644 index 0000000000..42a54751de --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.route.tsx @@ -0,0 +1,38 @@ +import { Link, Outlet } from '@tanstack/solid-router' + +export const Route = createFileRoute({ + component: DashboardComponent, +}) + +function DashboardComponent() { + return ( + <> + <div class="flex items-center border-b"> + <h2 class="text-xl p-2">Dashboard</h2> + </div> + <div class="flex flex-wrap divide-x"> + {( + [ + ['/dashboard', 'Summary', true], + ['/dashboard/invoices', 'Invoices'], + ['/dashboard/users', 'Users'], + ] as const + ).map(([to, label, exact]) => { + return ( + <Link + + to={to} + activeOptions={{ exact }} + activeProps={{ class: `font-bold` }} + class="p-2" + > + {label} + </Link> + ) + })} + </div> + <hr /> + <Outlet /> + </> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.index.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.index.tsx new file mode 100644 index 0000000000..68d2555648 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.index.tsx @@ -0,0 +1,29 @@ + + +export const Route = createFileRoute({ + component: UsersIndexComponent, +}) + +function UsersIndexComponent() { + return ( + <div class="p-2 space-y-2"> + <p> + Normally, setting default search parameters would either need to be done + manually in every link to a page, or as a side-effect (not a great + experience). + </p> + <p> + Instead, we can use <strong>search filters</strong> to provide defaults + or even persist search params for links to routes (and child routes). + </p> + <p> + A good example of this is the sorting and filtering of the users list. + In a traditional router, both would be lost while navigating around + individual users or even changing each sort/filter option unless each + state was manually passed from the current route into each new link we + created (that's a lot of tedious and error-prone work). With TanStack + router and search filters, they are persisted with little effort. + </p> + </div> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.route.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.route.tsx new file mode 100644 index 0000000000..198b6bf935 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.route.tsx @@ -0,0 +1,152 @@ +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ +import * as Solid from 'solid-js' +import { Link, + MatchRoute, + Outlet, + retainSearchParams, + useNavigate, } from '@tanstack/solid-router' +import { createQuery } from '@tanstack/solid-query' +import { z } from 'zod' +import { Spinner } from '../components/Spinner' +import { usersQueryOptions } from '../utils/queryOptions' + +type UsersViewSortBy = 'name' | 'id' | 'email' + +export const Route = createFileRoute({ + validateSearch: z.object({ + usersView: z + .object({ + sortBy: z.enum(['name', 'id', 'email']).optional(), + filterBy: z.string().optional(), + }) + .optional(), + }).parse, + search: { + // Retain the usersView search param while navigating + // within or to this route (or it's children!) + middlewares: [retainSearchParams(['usersView'])], + }, + loader: (opts) => + opts.context.queryClient.ensureQueryData(usersQueryOptions(opts.deps)), + component: UsersComponent, +}) + +function UsersComponent() { + const navigate = useNavigate({ from: Route.fullPath }) + const search = Route.useSearch() + const usersQuery = createQuery(()=>usersQueryOptions(Route.useLoaderDeps())) + const users = usersQuery.data + const sortBy = search().usersView?.sortBy ?? 'name' + const filterBy = search().usersView?.filterBy + + const [filterDraft, setFilterDraft] = Solid.createSignal(filterBy ?? '') + + Solid.createEffect(() => { + setFilterDraft(filterBy ?? '') + }, [filterBy]) + + const sortedUsers = Solid.createMemo(() => { + if (!users) return [] + + return !sortBy + ? users + : [...users].sort((a, b) => { + return a[sortBy] > b[sortBy] ? 1 : -1 + }) + }, [users, sortBy]) + + const filteredUsers = Solid.createMemo(() => { + if (!filterBy) return sortedUsers() + + return sortedUsers().filter((user) => + user.name.toLowerCase().includes(filterBy.toLowerCase()), + ) + }, [sortedUsers, filterBy]) + + const setSortBy = (sortBy: UsersViewSortBy) => + navigate({ + search: (old) => { + return { + ...old, + usersView: { + ...(old?.usersView ?? {}), + sortBy, + }, + } + }, + replace: true, + }) + + Solid.createEffect(() => { + navigate({ + search: (old) => { + return { + ...old, + usersView: { + ...old?.usersView, + filterBy: filterDraft() || undefined, + }, + } + }, + replace: true, + }) + }, [filterDraft]) + + return ( + <div class="flex-1 flex"> + <div class="divide-y"> + <div class="py-2 px-3 flex gap-2 items-center bg-gray-100 dark:bg-gray-800"> + <div>Sort By:</div> + <select + value={sortBy} + onChange={(e) => setSortBy(e.target.value as UsersViewSortBy)} + class="flex-1 border p-1 px-2 rounded" + > + {['name', 'id', 'email'].map((d) => { + return <option value={d} children={d} /> + })} + </select> + </div> + <div class="py-2 px-3 flex gap-2 items-center bg-gray-100 dark:bg-gray-800"> + <div>Filter By:</div> + <input + value={filterDraft()} + onChange={(e) => setFilterDraft(e.target.value)} + placeholder="Search Names..." + class="min-w-0 flex-1 border p-1 px-2 rounded" + /> + </div> + {filteredUsers()?.map((user) => { + return ( + <div> + <Link + to="/dashboard/users/user" + search={{ + userId: user.id, + }} + class="block py-2 px-3 text-blue-700" + activeProps={{ class: `font-bold` }} + > + <pre class="text-sm"> + {user.name}{' '} + <MatchRoute + to="/dashboard/users/user" + search={{ + userId: user.id, + }} + pending + > + {(match) => <Spinner show={!!match} wait="delay-50" />} + </MatchRoute> + </pre> + </Link> + </div> + ) + })} + </div> + <div class="flex-initial border-l"> + <Outlet /> + </div> + </div> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.user.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.user.tsx new file mode 100644 index 0000000000..b019571639 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.user.tsx @@ -0,0 +1,31 @@ + +import { createQuery } from '@tanstack/solid-query' +import { z } from 'zod' +import { userQueryOptions } from '../utils/queryOptions' + +export const Route = createFileRoute({ + validateSearch: z.object({ + userId: z.number(), + }), + loaderDeps: ({ search: { userId } }) => ({ userId }), + loader: (opts) => + opts.context.queryClient.ensureQueryData( + userQueryOptions(opts.deps.userId), + ), + component: UserComponent, +}) + +function UserComponent() { + const search = Route.useSearch() + const userQuery = createQuery(()=>userQueryOptions(search().userId)) + const user = userQuery.data + + return ( + <> + <h4 class="p-2 font-bold">{user?.name}</h4> + <pre class="text-sm whitespace-pre-wrap"> + {JSON.stringify(user, null, 2)} + </pre> + </> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/expensive/-components/Expensive.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/expensive/-components/Expensive.tsx new file mode 100644 index 0000000000..fd29d131b6 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/expensive/-components/Expensive.tsx @@ -0,0 +1,8 @@ +export default function Expensive() { + return ( + <div class={`p-2`}> + I am an "expensive" component... which really just means that I was + code-split 😉 + </div> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/expensive/index.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/expensive/index.tsx new file mode 100644 index 0000000000..6ee020c9c4 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/expensive/index.tsx @@ -0,0 +1,6 @@ + +import Expensive from './-components/Expensive' + +export const Route = createFileRoute({ + component: Expensive, +}) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/foo/bar.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/foo/bar.tsx new file mode 100644 index 0000000000..0a2d9a6ccd --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/foo/bar.tsx @@ -0,0 +1,7 @@ + +import { z } from 'zod' + +export const Route = createFileRoute({ + component: () => <div>{JSON.stringify(Route.useSearch())}</div>, + validateSearch: z.object({ asdf: z.string() }), +}) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/index.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/index.tsx new file mode 100644 index 0000000000..a5b44c1bb6 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/index.tsx @@ -0,0 +1,36 @@ +import { Link } from '@tanstack/solid-router' + +export const Route = createFileRoute({ + component: IndexComponent, +}) + +function IndexComponent() { + return ( + <div class={`p-2`}> + <div class={`text-lg`}>Welcome Home!</div> + <hr class={`my-2`} /> + <Link + to="/dashboard/invoices/$invoiceId" + params={{ + invoiceId: 3, + }} + class={`py-1 px-2 text-xs bg-blue-500 text-white rounded-full`} + > + 1 New Invoice + </Link> + <hr class={`my-2`} /> + <div class={`max-w-xl`}> + As you navigate around take note of the UX. It should feel + suspense-like, where routes are only rendered once all of their data and + elements are ready. + <hr class={`my-2`} /> + To exaggerate async effects, play with the artificial request delay + slider in the bottom-left corner. + <hr class={`my-2`} /> + The last 2 sliders determine if link-hover preloading is enabled (and + how long those preloads stick around) and also whether to cache rendered + route data (and for how long). Both of these default to 0 (or off). + </div> + </div> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/login.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/login.tsx new file mode 100644 index 0000000000..346ef780fe --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/login.tsx @@ -0,0 +1,68 @@ +import * as Solid from 'solid-js' +import { useRouter } from '@tanstack/solid-router' +import { z } from 'zod' + +export const Route = createFileRoute({ + validateSearch: z.object({ + redirect: z.string().optional(), + }), + component: LoginComponent, +}) + +function LoginComponent() { + const router = useRouter() + const routeContext: Solid.Accessor<{ auth: any; status: any }> = Route.useRouteContext({ + select: ({ auth }:{auth:any}) => ({ auth, status: auth.status }), + }) + const search = Route.useSearch() + const [username, setUsername] = Solid.createSignal('') + + const onSubmit = (e: SubmitEvent) => { + e.preventDefault() + routeContext().auth.login(username()) + router.invalidate() + } + + // Ah, the subtle nuances of client side auth. 🙄 + Solid.createRenderEffect(() => { + if (routeContext().status === 'loggedIn' && search().redirect) { + router.history.push(search().redirect || '') + } + }, [routeContext().status, search().redirect]) + + return routeContext().status === 'loggedIn' ? ( + <div> + Logged in as <strong>{routeContext().auth.username}</strong> + <div class="h-2" /> + <button + onClick={() => { + routeContext().auth.logout() + router.invalidate() + }} + class="text-sm bg-blue-500 text-white border inline-block py-1 px-2 rounded" + > + Log out + </button> + <div class="h-2" /> + </div> + ) : ( + <div class="p-2"> + <div>You must log in!</div> + <div class="h-2" /> + <form onSubmit={onSubmit} class="flex gap-2"> + <input + value={username()} + onChange={(e) => setUsername(e.target.value)} + placeholder="Username" + class="border p-1 px-2 rounded" + /> + <button + type="submit" + class="text-sm bg-blue-500 text-white border inline-block py-1 px-2 rounded" + > + Login + </button> + </form> + </div> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/styles.css b/examples/solid/kitchen-sink-solid-query-file-based/src/styles.css new file mode 100644 index 0000000000..0b8e317099 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/styles.css @@ -0,0 +1,13 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +html { + color-scheme: light dark; +} +* { + @apply border-gray-200 dark:border-gray-800; +} +body { + @apply bg-gray-50 text-gray-950 dark:bg-gray-900 dark:text-gray-200; +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/utils/auth.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/utils/auth.tsx new file mode 100644 index 0000000000..6c3d4f1638 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/utils/auth.tsx @@ -0,0 +1,19 @@ +export const auth: Auth = { + status: 'loggedOut', + username: undefined, + login: (username: string) => { + auth.status = 'loggedIn' + auth.username = username + }, + logout: () => { + auth.status = 'loggedOut' + auth.username = undefined + }, +} + +export type Auth = { + login: (username: string) => void + logout: () => void + status: 'loggedOut' | 'loggedIn' + username?: string +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/utils/mockTodos.ts b/examples/solid/kitchen-sink-solid-query-file-based/src/utils/mockTodos.ts new file mode 100644 index 0000000000..475d0fa88f --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/utils/mockTodos.ts @@ -0,0 +1,174 @@ +import axios from 'redaxios' +import { produce } from 'immer' +import { actionDelayFn, loaderDelayFn, shuffle } from './utils' + +type PickAsRequired<TValue, TKey extends keyof TValue> = Omit<TValue, TKey> & + Required<Pick<TValue, TKey>> + +export type Invoice = { + id: number + title: string + body: string +} + +export interface User { + id: number + name: string + username: string + email: string + address: Address + phone: string + website: string + company: Company +} + +export interface Address { + street: string + suite: string + city: string + zipcode: string + geo: Geo +} + +export interface Geo { + lat: string + lng: string +} + +export interface Company { + name: string + catchPhrase: string + bs: string +} + +let invoices: Array<Invoice> = null! +let users: Array<User> = null! + +let invoicesPromise: Promise<void> | undefined = undefined +let usersPromise: Promise<void> | undefined = undefined + +const ensureInvoices = async () => { + if (!invoicesPromise) { + invoicesPromise = Promise.resolve().then(async () => { + const { data } = await axios.get( + 'https://jsonplaceholder.typicode.com/posts', + ) + invoices = data.slice(0, 10) + }) + } + + await invoicesPromise +} + +const ensureUsers = async () => { + if (!usersPromise) { + usersPromise = Promise.resolve().then(async () => { + const { data } = await axios.get( + 'https://jsonplaceholder.typicode.com/users', + ) + users = data.slice(0, 10) + }) + } + + await usersPromise +} + +export async function fetchInvoices() { + return loaderDelayFn(() => ensureInvoices().then(() => invoices)) +} + +export async function fetchInvoiceById(id: number) { + return loaderDelayFn(() => + ensureInvoices().then(() => { + const invoice = invoices.find((d) => d.id === id) + if (!invoice) { + throw new Error('Invoice not found') + } + return invoice + }), + ) +} + +export async function postInvoice(partialInvoice: Partial<Invoice>) { + return actionDelayFn(() => { + if (partialInvoice.title?.includes('error')) { + console.error('error') + throw new Error('Ouch!') + } + const invoice = { + id: invoices.length + 1, + title: + partialInvoice.title ?? `New Invoice ${String(Date.now()).slice(0, 5)}`, + body: + partialInvoice.body ?? + shuffle( + `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. + Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. + `.split(' '), + ).join(' '), + } + invoices = [...invoices, invoice] + return invoice + }) +} + +export async function patchInvoice({ + id, + ...updatedInvoice +}: PickAsRequired<Partial<Invoice>, 'id'>) { + return actionDelayFn(() => { + invoices = produce(invoices, (draft) => { + const invoice = draft.find((d) => d.id === id) + if (!invoice) { + throw new Error('Invoice not found.') + } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (updatedInvoice.title?.toLocaleLowerCase()?.includes('error')) { + throw new Error('Ouch!') + } + Object.assign(invoice, updatedInvoice) + }) + + return invoices.find((d) => d.id === id) + }) +} + +export type UsersSortBy = 'name' | 'id' | 'email' + +export async function fetchUsers({ + filterBy, + sortBy, +}: { filterBy?: string; sortBy?: UsersSortBy } = {}) { + return loaderDelayFn(() => + ensureUsers().then(() => { + let usersDraft = users + + if (filterBy) { + usersDraft = usersDraft.filter((d) => + d.name.toLowerCase().includes(filterBy.toLowerCase()), + ) + } + + if (sortBy) { + usersDraft = [...usersDraft].sort((a, b) => { + return a[sortBy] > b[sortBy] ? 1 : -1 + }) + } + + return usersDraft + }), + ) +} + +export async function fetchUserById(id: number) { + return loaderDelayFn(() => + ensureUsers().then(() => users.find((d) => d.id === id)), + ) +} + +export async function fetchRandomNumber() { + return loaderDelayFn(() => { + return Math.random() + }) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/utils/queryOptions.ts b/examples/solid/kitchen-sink-solid-query-file-based/src/utils/queryOptions.ts new file mode 100644 index 0000000000..8955524132 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/utils/queryOptions.ts @@ -0,0 +1,53 @@ +import { createMutation, queryOptions } from '@tanstack/solid-query' +import { queryClient } from '../main' +import { + fetchInvoiceById, + fetchInvoices, + fetchUserById, + fetchUsers, + patchInvoice, + postInvoice, +} from './mockTodos' + + +export const invoicesQueryOptions = () => + queryOptions({ + queryKey: ['invoices'], + queryFn: () => fetchInvoices(), + }) + +export const invoiceQueryOptions = (invoiceId: number) => + queryOptions({ + queryKey: ['invoices', invoiceId], + queryFn: () => fetchInvoiceById(invoiceId), + }) + +export const usersQueryOptions = (opts: { + filterBy?: string + sortBy?: 'name' | 'id' | 'email' +}) => + queryOptions({ + queryKey: ['users', opts], + queryFn: () => fetchUsers(opts), + }) + +export const userQueryOptions = (userId: number) => + queryOptions({ + queryKey: ['users', userId], + queryFn: () => fetchUserById(userId), + }) + +export const useCreateInvoiceMutation = () => { + return createMutation(() => ({ + mutationFn: postInvoice, + onSuccess: () => queryClient.invalidateQueries(), + })) +} + +export const useUpdateInvoiceMutation = (invoiceId: number) => { + return createMutation(() => ({ + mutationFn: patchInvoice, + onSuccess: () => queryClient.invalidateQueries(), + gcTime: 1000 * 10, + })) +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/utils/utils.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/utils/utils.tsx new file mode 100644 index 0000000000..6435657b12 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/utils/utils.tsx @@ -0,0 +1,33 @@ +export async function loaderDelayFn<T>( + fn: (...args: Array<any>) => Promise<T> | T, +) { + const delay = Number(sessionStorage.getItem('loaderDelay') ?? 0) + const delayPromise = new Promise((r) => setTimeout(r, delay)) + + await delayPromise + const res = await fn() + + return res +} + +export async function actionDelayFn<T>( + fn: (...args: Array<any>) => Promise<T> | T, +) { + const delay = Number(sessionStorage.getItem('actionDelay') ?? 0) + await new Promise((r) => setTimeout(r, delay)) + return fn() +} + +export function shuffle<T>(arr: Array<T>): Array<T> { + let i = arr.length + if (i == 0) return arr + const copy = [...arr] + while (--i) { + const j = Math.floor(Math.random() * (i + 1)) + const a = copy[i] + const b = copy[j] + copy[i] = b! + copy[j] = a! + } + return copy +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/tailwind.config.mjs b/examples/solid/kitchen-sink-solid-query-file-based/tailwind.config.mjs new file mode 100644 index 0000000000..4986094b9d --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/tailwind.config.mjs @@ -0,0 +1,4 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src/**/*.{js,jsx,ts,tsx}', './index.html'], +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/tsconfig.dev.json b/examples/solid/kitchen-sink-solid-query-file-based/tsconfig.dev.json new file mode 100644 index 0000000000..285a09b0dc --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/tsconfig.dev.json @@ -0,0 +1,10 @@ +{ + "composite": true, + "extends": "../../../tsconfig.base.json", + + "files": ["src/main.tsx"], + "include": [ + "src" + // "__tests__/**/*.test.*" + ] +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/tsconfig.json b/examples/solid/kitchen-sink-solid-query-file-based/tsconfig.json new file mode 100644 index 0000000000..6e3ba6f110 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "skipLibCheck": true + } +} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/vite.config.js b/examples/solid/kitchen-sink-solid-query-file-based/vite.config.js new file mode 100644 index 0000000000..4af89e7520 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query-file-based/vite.config.js @@ -0,0 +1,11 @@ +import { defineConfig } from 'vite' +import solid from 'vite-plugin-solid' +import { TanStackRouterVite } from '@tanstack/router-plugin/vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + TanStackRouterVite({ target: 'solid', autoCodeSplitting: true }), + solid(), + ], +}) diff --git a/examples/solid/kitchen-sink-solid-query/.gitignore b/examples/solid/kitchen-sink-solid-query/.gitignore new file mode 100644 index 0000000000..d451ff16c1 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query/.gitignore @@ -0,0 +1,5 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local diff --git a/examples/solid/kitchen-sink-solid-query/.vscode/settings.json b/examples/solid/kitchen-sink-solid-query/.vscode/settings.json new file mode 100644 index 0000000000..00b5278e58 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + }, + "files.readonlyInclude": { + "**/routeTree.gen.ts": true + } +} diff --git a/examples/solid/kitchen-sink-solid-query/README.md b/examples/solid/kitchen-sink-solid-query/README.md new file mode 100644 index 0000000000..115199d292 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm start` or `yarn start` diff --git a/examples/solid/kitchen-sink-solid-query/index.html b/examples/solid/kitchen-sink-solid-query/index.html new file mode 100644 index 0000000000..9b6335c0ac --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query/index.html @@ -0,0 +1,12 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Vite App</title> + </head> + <body> + <div id="app"></div> + <script type="module" src="/src/main.tsx"></script> + </body> +</html> diff --git a/examples/solid/kitchen-sink-solid-query/package.json b/examples/solid/kitchen-sink-solid-query/package.json new file mode 100644 index 0000000000..868e13d162 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query/package.json @@ -0,0 +1,29 @@ +{ + "name": "tanstack-router-solid-example-kitchen-sink-solid-query", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port 3000", + "build": "vite build && tsc --noEmit", + "serve": "vite preview", + "start": "vite" + }, + "dependencies": { + "@tanstack/solid-query": "^5.71.0", + "@tanstack/solid-query-devtools": "^5.71.0", + "@tanstack/solid-router": "^1.114.29", + "@tanstack/solid-router-devtools": "^1.114.29", + "immer": "^10.1.1", + "solid-js": "^1.9.5", + "redaxios": "^0.5.1", + "postcss": "^8.5.1", + "autoprefixer": "^10.4.20", + "tailwindcss": "^3.4.17", + "zod": "^3.24.2" + }, + "devDependencies": { + "vite-plugin-solid": "^2.11.6", + "typescript": "^5.7.2", + "vite": "^6.1.0" + } +} diff --git a/examples/solid/kitchen-sink-solid-query/postcss.config.mjs b/examples/solid/kitchen-sink-solid-query/postcss.config.mjs new file mode 100644 index 0000000000..2e7af2b7f1 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query/postcss.config.mjs @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/examples/solid/kitchen-sink-solid-query/src/Expensive.tsx b/examples/solid/kitchen-sink-solid-query/src/Expensive.tsx new file mode 100644 index 0000000000..fd29d131b6 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query/src/Expensive.tsx @@ -0,0 +1,8 @@ +export default function Expensive() { + return ( + <div class={`p-2`}> + I am an "expensive" component... which really just means that I was + code-split 😉 + </div> + ) +} diff --git a/examples/solid/kitchen-sink-solid-query/src/main.tsx b/examples/solid/kitchen-sink-solid-query/src/main.tsx new file mode 100644 index 0000000000..0df7d9263e --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query/src/main.tsx @@ -0,0 +1,1118 @@ +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ +import * as Solid from 'solid-js' +import { render } from 'solid-js/web' +import { + ErrorComponent, + Link, + MatchRoute, + Outlet, + RouterProvider, + createRootRouteWithContext, + createRoute, + createRouter, + lazyRouteComponent, + redirect, + retainSearchParams, + useNavigate, + useRouter, + useRouterState, + useSearch, +} from '@tanstack/solid-router' +import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools' +import { + QueryClient, + QueryClientProvider, + createMutation, + createQuery, + queryOptions, +} from '@tanstack/solid-query' +import { SolidQueryDevtools } from '@tanstack/solid-query-devtools' +import { z } from 'zod' +import { + fetchInvoiceById, + fetchInvoices, + fetchUserById, + fetchUsers, + patchInvoice, + postInvoice, +} from './mockTodos' +import type { Invoice } from './mockTodos' +import './styles.css' + +// + +type UsersViewSortBy = 'name' | 'id' | 'email' + +const invoicesQueryOptions = () => + queryOptions({ + queryKey: ['invoices'], + queryFn: () => fetchInvoices(), + }) + +const invoiceQueryOptions = (invoiceId: number) => + queryOptions({ + queryKey: ['invoices', invoiceId], + queryFn: () => fetchInvoiceById(invoiceId), + }) + +const usersQueryOptions = ({ + filterBy, + sortBy, +}: { + filterBy?: string + sortBy?: UsersViewSortBy +}) => + queryOptions({ + queryKey: ['users', { filterBy, sortBy }], + queryFn: () => + fetchUsers({ + filterBy, + sortBy, + }), + }) + +const userQueryOptions = (userId: number) => + queryOptions({ + queryKey: ['users', userId], + queryFn: async () => { + const user = await fetchUserById(userId) + if (!user) { + throw new Error('User not found.') + } + + return user + }, + }) + +const useCreateInvoiceMutation = () => { + return createMutation(() => ({ + mutationFn: postInvoice, + onSuccess: () => queryClient.invalidateQueries(), + })) +} + +const useUpdateInvoiceMutation = (invoiceId: number) => { + return createMutation(() => ({ + mutationFn: patchInvoice, + onSuccess: () => queryClient.invalidateQueries(), + gcTime: 1000 * 10, + })) +} + +function RouterSpinner() { + const isLoading = useRouterState({ select: (s) => s.status === 'pending' }) + return <Spinner show={isLoading()} /> +} + +// Routes + +// Build our routes. We could do this in our component, too. +const rootRoute = createRootRouteWithContext<{ + auth: Auth + queryClient: QueryClient +}>()({ + component: RootComponent, +}) + +function RootComponent() { + return ( + <> + <div class={`min-h-screen flex flex-col`}> + <div class={`flex items-center border-b gap-2`}> + <h1 class={`text-3xl p-2`}>Kitchen Sink</h1> + {/* Show a global spinner when the router is transitioning */} + <div class={`text-3xl`}> + <RouterSpinner /> + </div> + </div> + <div class={`flex-1 flex`}> + <div class={`divide-y w-56`}> + {( + [ + ['/', 'Home'], + ['/dashboard', 'Dashboard'], + ['/expensive', 'Expensive'], + ['/route-a', 'Pathless Layout A'], + ['/route-b', 'Pathless Layout B'], + ['/profile', 'Profile'], + ['/login', 'Login'], + ] as const + ).map(([to, label]) => { + return ( + <div> + <Link + to={to} + activeOptions={ + { + // If the route points to the root of it's parent, + // make sure it's only active if it's exact + // exact: to === '.', + } + } + preload="intent" + class={`block py-2 px-3 text-blue-700`} + // Make "active" links bold + activeProps={{ class: `font-bold` }} + > + {label} + </Link> + </div> + ) + })} + </div> + <div class={`flex-1 border-l`}> + {/* Render our first route match */} + <Outlet /> + </div> + </div> + </div> + <TanStackRouterDevtools position="bottom-right" /> + <SolidQueryDevtools buttonPosition="top-right" /> + </> + ) +} + +const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: IndexComponent, +}) + +function IndexComponent() { + return ( + <div class={`p-2`}> + <div class={`text-lg`}>Welcome Home!</div> + <hr class={`my-2`} /> + <Link + to={invoiceRoute.to} + params={{ + invoiceId: 3, + }} + class={`py-1 px-2 text-xs bg-blue-500 text-white rounded-full`} + > + 1 New Invoice + </Link> + <hr class={`my-2`} /> + <div class={`max-w-xl`}> + As you navigate around take note of the UX. It should feel + suspense-like, where routes are only rendered once all of their data and + elements are ready. + <hr class={`my-2`} /> + To exaggerate async effects, play with the artificial request delay + slider in the bottom-left corner. + <hr class={`my-2`} /> + The last 2 sliders determine if link-hover preloading is enabled (and + how long those preloads stick around) and also whether to cache rendered + route data (and for how long). Both of these default to 0 (or off). + </div> + </div> + ) +} + +const dashboardLayoutRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'dashboard', + component: DashboardLayoutComponent, +}) + +function DashboardLayoutComponent() { + return ( + <> + <div class="flex items-center border-b"> + <h2 class="text-xl p-2">Dashboard</h2> + </div> + <div class="flex flex-wrap divide-x"> + {( + [ + ['/dashboard', 'Summary', true], + ['/dashboard/invoices', 'Invoices'], + ['/dashboard/users', 'Users'], + ] as const + ).map(([to, label, exact]) => { + return ( + <Link + + to={to} + activeOptions={{ exact }} + activeProps={{ class: `font-bold` }} + class="p-2" + > + {label} + </Link> + ) + })} + </div> + <hr /> + <Outlet /> + </> + ) +} + +const dashboardIndexRoute = createRoute({ + getParentRoute: () => dashboardLayoutRoute, + path: '/', + loader: (opts) => + opts.context.queryClient.ensureQueryData(invoicesQueryOptions()), + component: DashboardIndexComponent, +}) + +function DashboardIndexComponent() { + const invoicesQuery = createQuery(() => invoicesQueryOptions()) + const invoices = invoicesQuery.data + + return ( + <div class="p-2"> + <div class="p-2"> + Welcome to the dashboard! You have{' '} + <strong>{invoices?.length || 0} total invoices</strong>. + </div> + </div> + ) +} + +const invoicesLayoutRoute = createRoute({ + getParentRoute: () => dashboardLayoutRoute, + path: 'invoices', + loader: (opts) => + opts.context.queryClient.ensureQueryData(invoicesQueryOptions()), + component: InvoicesLayoutComponent, +}) + +function InvoicesLayoutComponent() { + const invoicesQuery = createQuery(() => invoicesQueryOptions()) + const invoices = invoicesQuery.data || [] + // const updateInvoiceMutation = useUpdateInvoiceMutation() + // const createInvoiceMutation = useCreateInvoiceMutation() + + return ( + <div class="flex-1 flex"> + {/* {routerTransitionIsPending ? 'pending' : 'null'} */} + <div class="divide-y w-48"> + {invoices.map((invoice: Invoice) => { + // const updateSubmission = updateInvoiceMutation.submissions.find( + // (d) => d.variables?.id === invoice.id, + // ) + + // if (updateSubmission) { + // invoice = { + // ...invoice, + // ...updateSubmission.variables, + // } + // } + + return ( + <div> + <Link + to="/dashboard/invoices/$invoiceId" + params={{ + invoiceId: invoice.id, + }} + preload="intent" + class="block py-2 px-3 text-blue-700" + activeProps={{ class: `font-bold` }} + > + <pre class="text-sm"> + #{invoice.id} - {invoice.title.slice(0, 10)}{' '} + {/* {updateSubmission ? ( + <Spinner /> + ) : ( */} + <MatchRoute + to={invoiceRoute.to} + params={{ + invoiceId: invoice.id, + }} + pending + > + {(match) => <Spinner show={!!match} wait="delay-50" />} + </MatchRoute> + {/* )} */} + </pre> + </Link> + </div> + ) + })} + {/* {createSubmissions.map((action) => ( + <div key={action.submittedAt}> + <a href="#" class="block py-2 px-3 text-blue-700"> + <pre class="text-sm"> + #<Spinner /> - {action.variables.title?.slice(0, 10)} + </pre> + </a> + </div> + ))} */} + </div> + <div class="flex-1 border-l"> + <Outlet /> + </div> + </div> + ) +} + +const invoicesIndexRoute = createRoute({ + getParentRoute: () => invoicesLayoutRoute, + path: '/', + component: InvoicesIndexComponent, +}) + +function InvoicesIndexComponent() { + const createInvoiceMutation = useCreateInvoiceMutation() + + return ( + <> + <div class="p-2"> + <form + onSubmit={(event) => { + event.preventDefault() + event.stopPropagation() + const form = event.target as HTMLFormElement + const formData = new FormData(form) + createInvoiceMutation.mutate({ + title: formData.get('title') as string, + body: formData.get('body') as string, + } as any) + }} + class="space-y-2" + > + <div>Create a new Invoice:</div> + <InvoiceFields invoice={{} as Invoice} /> + <div> + <button + class="bg-blue-500 rounded p-2 uppercase text-white font-black disabled:opacity-50" + disabled={createInvoiceMutation.isPending} + > + {createInvoiceMutation.isPending ? ( + <> + Creating <Spinner /> + </> + ) : ( + 'Create' + )} + </button> + </div> + {createInvoiceMutation.isSuccess ? ( + <div class="inline-block px-2 py-1 rounded bg-green-500 text-white animate-bounce [animation-iteration-count:2.5] [animation-duration:.3s]"> + Created! + </div> + ) : createInvoiceMutation.isError ? ( + <div class="inline-block px-2 py-1 rounded bg-red-500 text-white animate-bounce [animation-iteration-count:2.5] [animation-duration:.3s]"> + Failed to create. + </div> + ) : null} + </form> + </div> + </> + ) +} + +const invoiceRoute = createRoute({ + getParentRoute: () => invoicesLayoutRoute, + path: '$invoiceId', + params: { + parse: (params) => ({ + invoiceId: z.number().int().parse(Number(params.invoiceId)), + }), + stringify: ({ invoiceId }) => ({ invoiceId: `${invoiceId}` }), + }, + validateSearch: (search) => + z + .object({ + showNotes: z.boolean().optional(), + notes: z.string().optional(), + }) + .parse(search), + loader: (opts) => + opts.context.queryClient.ensureQueryData( + invoiceQueryOptions(opts.params.invoiceId), + ), + component: InvoiceComponent, +}) + +function InvoiceComponent() { + const params = invoiceRoute.useParams() + const search = invoiceRoute.useSearch() + const navigate = useNavigate({ from: invoiceRoute.fullPath }) + const invoiceQuery = createQuery(() => invoiceQueryOptions(params().invoiceId)) + const invoice = invoiceQuery.data || {} as Invoice + const updateInvoiceMutation = useUpdateInvoiceMutation(params().invoiceId) + const [notes, setNotes] = Solid.createSignal(search().notes ?? '') + + Solid.createEffect(() => { + navigate({ + search: (old) => ({ + ...old, + notes: notes() ? notes() : undefined, + }), + replace: true, + params: true, + }) + }) + + return ( + <form + onSubmit={(event) => { + event.preventDefault() + event.stopPropagation() + const form = event.target as HTMLFormElement + const formData = new FormData(form) + updateInvoiceMutation.mutate({ + id: invoice.id, + title: formData.get('title') as string, + body: formData.get('body') as string, + } as any) + }} + class="p-2 space-y-2" + > + <InvoiceFields + invoice={invoice} + disabled={updateInvoiceMutation.isPending} + /> + <div> + <Link + search={(old) => ({ + ...old, + showNotes: old.showNotes ? undefined : true, + })} + class="text-blue-700" + from={invoiceRoute.fullPath} + params={true} + > + {search().showNotes ? 'Close Notes' : 'Show Notes'}{' '} + </Link> + {search().showNotes ? ( + <> + <div> + <div class="h-2" /> + <textarea + value={notes()} + onInput={(e) => { + setNotes(e.target.value) + }} + rows={5} + class="shadow w-full p-2 rounded" + placeholder="Write some notes here..." + /> + <div class="italic text-xs"> + Notes are stored in the URL. Try copying the URL into a new tab! + </div> + </div> + </> + ) : null} + </div> + <div> + <button + class="bg-blue-500 rounded p-2 uppercase text-white font-black disabled:opacity-50" + disabled={updateInvoiceMutation.isPending} + > + Save + </button> + </div> + {updateInvoiceMutation.data?.id === invoice.id ? ( + <div> + {updateInvoiceMutation.isSuccess ? ( + <div class="inline-block px-2 py-1 rounded bg-green-500 text-white animate-bounce [animation-iteration-count:2.5] [animation-duration:.3s]"> + Saved! + </div> + ) : updateInvoiceMutation.isError ? ( + <div class="inline-block px-2 py-1 rounded bg-red-500 text-white animate-bounce [animation-iteration-count:2.5] [animation-duration:.3s]"> + Failed to save. + </div> + ) : null} + </div> + ) : null} + </form> + ) +} + +const usersLayoutRoute = createRoute({ + getParentRoute: () => dashboardLayoutRoute, + path: 'users', + validateSearch: z.object({ + usersView: z + .object({ + sortBy: z.enum(['name', 'id', 'email']).optional(), + filterBy: z.string().optional(), + }) + .optional(), + }).parse, + search: { + // Retain the usersView search param while navigating + // within or to this route (or it's children!) + middlewares: [retainSearchParams(['usersView'])], + }, + loaderDeps: ({ search }) => ({ + filterBy: search.usersView?.filterBy, + sortBy: search.usersView?.sortBy, + }), + loader: (opts) => + opts.context.queryClient.ensureQueryData(usersQueryOptions(opts.deps)), + component: UsersComponent, +}) + +function UsersComponent() { + const navigate = useNavigate({ from: usersLayoutRoute.fullPath }) + const search = usersLayoutRoute.useSearch() + const usersQuery = createQuery(() => usersQueryOptions(usersLayoutRoute.useLoaderDeps())) + const users = usersQuery.data || [] + const sortBy = search().usersView?.sortBy ?? 'name' + const filterBy = search().usersView?.filterBy + + const [filterDraft, setFilterDraft] = Solid.createSignal(filterBy ?? '') + + Solid.createEffect(() => { + setFilterDraft(filterBy ?? '') + }) + + const sortedUsers = Solid.createMemo(() => { + if (!users) return [] + + return !sortBy + ? users + : [...users].sort((a, b) => { + return a[sortBy] > b[sortBy] ? 1 : -1 + }) + }) + + const filteredUsers = Solid.createMemo(() => { + if (!filterBy) return sortedUsers() + + return sortedUsers().filter((user: any) => + user.name.toLowerCase().includes(filterBy.toLowerCase()), + ) + }) + + const setSortBy = (sortBy: UsersViewSortBy) => + navigate({ + search: (old) => { + return { + ...old, + usersView: { + ...(old.usersView ?? {}), + sortBy, + }, + } + }, + replace: true, + }) + + Solid.createEffect(() => { + navigate({ + search: (old) => { + return { + ...old, + usersView: { + ...old.usersView, + filterBy: filterDraft() || undefined, + }, + } + }, + replace: true, + }) + }) + + return ( + <div class="flex-1 flex"> + <div class="divide-y"> + <div class="py-2 px-3 flex gap-2 items-center bg-gray-100 dark:bg-gray-800"> + <div>Sort By:</div> + <select + value={sortBy} + onChange={(e) => setSortBy(e.target.value as UsersViewSortBy)} + class="flex-1 border p-1 px-2 rounded" + > + {['name', 'id', 'email'].map((d) => { + return <option value={d} children={d} /> + })} + </select> + </div> + <div class="py-2 px-3 flex gap-2 items-center bg-gray-100 dark:bg-gray-800"> + <div>Filter By:</div> + <input + value={filterDraft()} + onInput={(e) => setFilterDraft(e.target.value)} + placeholder="Search Names..." + class="min-w-0 flex-1 border p-1 px-2 rounded" + /> + </div> + {filteredUsers().map((user: any) => { + return ( + <div> + <Link + to="/dashboard/users/user" + search={{ + userId: user.id, + }} + class="block py-2 px-3 text-blue-700" + activeProps={{ class: `font-bold` }} + > + <pre class="text-sm"> + {user.name}{' '} + <MatchRoute + to={userRoute.to} + search={{ + userId: user.id, + }} + pending + > + {(match) => <Spinner show={!!match} wait="delay-50" />} + </MatchRoute> + </pre> + </Link> + </div> + ) + })} + </div> + <div class="flex-initial border-l"> + <Outlet /> + </div> + </div> + ) +} + +const usersIndexRoute = createRoute({ + getParentRoute: () => usersLayoutRoute, + path: '/', + component: UsersIndexComponent, +}) + +function UsersIndexComponent() { + return ( + <div class="p-2 space-y-2"> + <p> + Normally, setting default search parameters would either need to be done + manually in every link to a page, or as a side-effect (not a great + experience). + </p> + <p> + Instead, we can use <strong>search filters</strong> to provide defaults + or even persist search params for links to routes (and child routes). + </p> + <p> + A good example of this is the sorting and filtering of the users list. + In a traditional router, both would be lost while navigating around + individual users or even changing each sort/filter option unless each + state was manually passed from the current route into each new link we + created (that's a lot of tedious and error-prone work). With TanStack + router and search filters, they are persisted with little effort. + </p> + </div> + ) +} + +const userRoute = createRoute({ + getParentRoute: () => usersLayoutRoute, + path: 'user', + validateSearch: z.object({ + userId: z.number(), + }), + loaderDeps: ({ search }) => ({ + userId: search.userId, + }), + loader: (opts) => + opts.context.queryClient.ensureQueryData( + userQueryOptions(opts.deps.userId), + ), + component: UserComponent, +}) + +function UserComponent() { + const search = userRoute.useSearch() + const userQuery = createQuery(() => userQueryOptions(search().userId)) + const user = userQuery.data + + return ( + <> + <h4 class="p-2 font-bold">{user?.name}</h4> + <pre class="text-sm whitespace-pre-wrap"> + {JSON.stringify(user, null, 2)} + </pre> + </> + ) +} + +const expensiveRoute = createRoute({ + getParentRoute: () => rootRoute, + // Your elements can be asynchronous, which means you can code-split! + path: 'expensive', + component: lazyRouteComponent(() => import('./Expensive')), +}) + +const authPathlessLayoutRoute = createRoute({ + getParentRoute: () => rootRoute, + id: 'auth', + // Before loading, authenticate the user via our auth context + // This will also happen during prefetching (e.g. hovering over links, etc) + beforeLoad: ({ context, location }) => { + // If the user is logged out, redirect them to the login page + if (context.auth.status === 'loggedOut') { + throw redirect({ + to: loginRoute.to, + search: { + // Use the current location to power a redirect after login + // (Do not use `router.state.resolvedLocation` as it can + // potentially lag behind the actual current location) + redirect: location.href, + }, + }) + } + + // Otherwise, return the user in context + return { + username: auth.username, + } + }, +}) + +const profileRoute = createRoute({ + getParentRoute: () => authPathlessLayoutRoute, + path: 'profile', + component: ProfileComponent, +}) + +function ProfileComponent() { + const routeContext = profileRoute.useRouteContext() + + return ( + <div class="p-2 space-y-2"> + <div> + Username:<strong>{routeContext().username}</strong> + </div> + </div> + ) +} + +const loginRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'login', + validateSearch: z.object({ + redirect: z.string().optional(), + }), +}).update({ + component: LoginComponent, +}) + +function LoginComponent() { + const router = useRouter() + const routeContext = loginRoute.useRouteContext({ + select: ({ auth }) => ({ auth, status: auth.status }), + }) + const search = useSearch({ from: loginRoute.fullPath }) + const [username, setUsername] = Solid.createSignal('') + const status = Solid.createMemo(() => routeContext()?.status) + + const onSubmit = (e: Event) => { + e.preventDefault() + routeContext()?.auth.login(username()) + router.invalidate() + } + + // Ah, the subtle nuances of client side auth. 🙄 + Solid.createEffect(() => { + if (status() === 'loggedIn' && search().redirect) { + router.history.push(search().redirect!) + } + }) + + return status() === 'loggedIn' ? ( + <div> + Logged in as <strong>{auth.username}</strong> + <div class="h-2" /> + <button + onClick={() => auth.logout()} + class="text-sm bg-blue-500 text-white border inline-block py-1 px-2 rounded" + > + Log out + </button> + <div class="h-2" /> + </div> + ) : ( + <div class="p-2"> + <div>You must log in!</div> + <div class="h-2" /> + <form onSubmit={onSubmit} class="flex gap-2"> + <input + value={username()} + onInput={(e) => setUsername(e.currentTarget.value)} + placeholder="Username" + class="border p-1 px-2 rounded" + /> + <button + class="text-sm bg-blue-500 text-white border inline-block py-1 px-2 rounded" + > + Login + </button> + </form> + </div> + ) +} + +const pathlessLayoutRoute = createRoute({ + getParentRoute: () => rootRoute, + id: 'pathlessLayout', + component: PathlessLayoutComponent, +}) + +function PathlessLayoutComponent() { + return ( + <div> + <div>Pathless Layout</div> + <hr /> + <Outlet /> + </div> + ) +} + +const pathlessLayoutARoute = createRoute({ + getParentRoute: () => pathlessLayoutRoute, + path: 'route-a', + component: PathlessLayoutAComponent, +}) + +function PathlessLayoutAComponent() { + return ( + <div> + <div>I'm A</div> + </div> + ) +} + +const pathlessLayoutBRoute = createRoute({ + getParentRoute: () => pathlessLayoutRoute, + path: 'route-b', + component: PathlessLayoutBComponent, +}) + +function PathlessLayoutBComponent() { + return ( + <div> + <div>i"m B</div> + </div> + ) +} + +const routeTree = rootRoute.addChildren([ + indexRoute, + dashboardLayoutRoute.addChildren([ + dashboardIndexRoute, + invoicesLayoutRoute.addChildren([invoicesIndexRoute, invoiceRoute]), + usersLayoutRoute.addChildren([usersIndexRoute, userRoute]), + ]), + expensiveRoute, + authPathlessLayoutRoute.addChildren([profileRoute]), + loginRoute, + pathlessLayoutRoute.addChildren([pathlessLayoutARoute, pathlessLayoutBRoute]), +]) + +const queryClient = new QueryClient() + +const router = createRouter({ + routeTree, + defaultPendingComponent: () => ( + <div class={`p-2 text-2xl`}> + <Spinner /> + </div> + ), + defaultErrorComponent: ({ error }) => <ErrorComponent error={error} />, + context: { + auth: undefined!, // We'll inject this when we render + queryClient, + }, + defaultPreload: 'intent', + // Since we're using React Query, we don't want loader calls to ever be stale + // This will ensure that the loader is always called when the route is preloaded or visited + defaultPreloadStaleTime: 0, + scrollRestoration: true, +}) + +declare module '@tanstack/solid-router' { + interface Register { + router: typeof router + } +} + +const auth: Auth = { + status: 'loggedOut', + username: undefined, + login: (username: string) => { + auth.status = 'loggedIn' + auth.username = username + }, + logout: () => { + auth.status = 'loggedOut' + auth.username = undefined + }, +} + +function App() { + // This stuff is just to tweak our sandbox setup in real-time + const [loaderDelay, setLoaderDelay] = useSessionStorage('loaderDelay', 500) + const [pendingMs, setPendingMs] = useSessionStorage('pendingMs', 1000) + const [pendingMinMs, setPendingMinMs] = useSessionStorage('pendingMinMs', 500) + + return ( + <> + <div class="text-xs fixed w-52 shadow-md shadow-black/20 rounded bottom-2 left-2 bg-white dark:bg-gray-800 bg-opacity-75 border-b flex flex-col gap-1 flex-wrap items-left divide-y"> + <div class="p-2 space-y-2"> + <div class="flex gap-2"> + <button + class="bg-blue-500 text-white rounded p-1 px-2" + onClick={() => { + setLoaderDelay(150) + }} + > + Fast + </button> + <button + class="bg-blue-500 text-white rounded p-1 px-2" + onClick={() => { + setLoaderDelay(500) + }} + > + Fast 3G + </button> + <button + class="bg-blue-500 text-white rounded p-1 px-2" + onClick={() => { + setLoaderDelay(2000) + }} + > + Slow 3G + </button> + </div> + <div> + <div>Loader Delay: {loaderDelay()}ms</div> + <input + type="range" + min="0" + max="5000" + step="100" + value={loaderDelay()} + onInput={(e) => setLoaderDelay(e.target.valueAsNumber)} + class="w-full" + /> + </div> + </div> + <div class="p-2 space-y-2"> + <div class="flex gap-2"> + <button + class="bg-blue-500 text-white rounded p-1 px-2" + onClick={() => { + setPendingMs(1000) + setPendingMinMs(500) + }} + > + Reset to Default + </button> + </div> + <div> + <div>defaultPendingMs: {pendingMs()}ms</div> + <input + type="range" + min="0" + max="5000" + step="100" + value={pendingMs()} + onInput={(e) => setPendingMs(e.target.valueAsNumber)} + class="w-full" + /> + </div> + <div> + <div>defaultPendingMinMs: {pendingMinMs()}ms</div> + <input + type="range" + min="0" + max="5000" + step="100" + value={pendingMinMs()} + onInput={(e) => setPendingMinMs(e.target.valueAsNumber)} + class="w-full" + /> + </div> + </div> + </div> + <QueryClientProvider client={queryClient}> + <RouterProvider + router={router} + defaultPreload="intent" + defaultPendingMs={pendingMs()} + defaultPendingMinMs={pendingMinMs()} + context={{ + auth, + }} + /> + </QueryClientProvider> + </> + ) +} + +function InvoiceFields({ + invoice, + disabled, +}: { + invoice: Invoice + disabled?: boolean +}) { + return ( + <div class="space-y-2"> + <h2 class="font-bold text-lg"> + <input + name="title" + value={invoice.title || ''} + placeholder="Invoice Title" + class="border border-opacity-50 rounded p-2 w-full" + disabled={disabled} + /> + </h2> + <div> + <textarea + name="body" + value={invoice.body || ''} + rows={6} + placeholder="Invoice Body..." + class="border border-opacity-50 p-2 rounded w-full" + disabled={disabled} + /> + </div> + </div> + ) +} + +type Auth = { + login: (username: string) => void + logout: () => void + status: 'loggedOut' | 'loggedIn' + username?: string +} + +function Spinner({ show, wait }: { show?: boolean; wait?: `delay-${number}` }) { + return ( + <div + class={`inline-block animate-spin px-3 transition ${ + (show ?? true) + ? `opacity-1 duration-500 ${wait ?? 'delay-300'}` + : 'duration-500 opacity-0 delay-0' + }`} + > + ⍥ + </div> + ) +} + +function useSessionStorage<T>(key: string, initialValue: T) { + const [state, setState] = Solid.createSignal<T>( + typeof sessionStorage !== 'undefined' && + sessionStorage.getItem(key) !== null + ? JSON.parse(sessionStorage.getItem(key)!) + : initialValue + ) + + Solid.createEffect(() => { + sessionStorage.setItem(key, JSON.stringify(state())) + }) + + return [state, setState] as const +} + +const rootElement = document.getElementById('app')! +if (!rootElement.innerHTML) { + render(() => <App />, rootElement) +} diff --git a/examples/solid/kitchen-sink-solid-query/src/mockTodos.ts b/examples/solid/kitchen-sink-solid-query/src/mockTodos.ts new file mode 100644 index 0000000000..475d0fa88f --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query/src/mockTodos.ts @@ -0,0 +1,174 @@ +import axios from 'redaxios' +import { produce } from 'immer' +import { actionDelayFn, loaderDelayFn, shuffle } from './utils' + +type PickAsRequired<TValue, TKey extends keyof TValue> = Omit<TValue, TKey> & + Required<Pick<TValue, TKey>> + +export type Invoice = { + id: number + title: string + body: string +} + +export interface User { + id: number + name: string + username: string + email: string + address: Address + phone: string + website: string + company: Company +} + +export interface Address { + street: string + suite: string + city: string + zipcode: string + geo: Geo +} + +export interface Geo { + lat: string + lng: string +} + +export interface Company { + name: string + catchPhrase: string + bs: string +} + +let invoices: Array<Invoice> = null! +let users: Array<User> = null! + +let invoicesPromise: Promise<void> | undefined = undefined +let usersPromise: Promise<void> | undefined = undefined + +const ensureInvoices = async () => { + if (!invoicesPromise) { + invoicesPromise = Promise.resolve().then(async () => { + const { data } = await axios.get( + 'https://jsonplaceholder.typicode.com/posts', + ) + invoices = data.slice(0, 10) + }) + } + + await invoicesPromise +} + +const ensureUsers = async () => { + if (!usersPromise) { + usersPromise = Promise.resolve().then(async () => { + const { data } = await axios.get( + 'https://jsonplaceholder.typicode.com/users', + ) + users = data.slice(0, 10) + }) + } + + await usersPromise +} + +export async function fetchInvoices() { + return loaderDelayFn(() => ensureInvoices().then(() => invoices)) +} + +export async function fetchInvoiceById(id: number) { + return loaderDelayFn(() => + ensureInvoices().then(() => { + const invoice = invoices.find((d) => d.id === id) + if (!invoice) { + throw new Error('Invoice not found') + } + return invoice + }), + ) +} + +export async function postInvoice(partialInvoice: Partial<Invoice>) { + return actionDelayFn(() => { + if (partialInvoice.title?.includes('error')) { + console.error('error') + throw new Error('Ouch!') + } + const invoice = { + id: invoices.length + 1, + title: + partialInvoice.title ?? `New Invoice ${String(Date.now()).slice(0, 5)}`, + body: + partialInvoice.body ?? + shuffle( + `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. + Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. + `.split(' '), + ).join(' '), + } + invoices = [...invoices, invoice] + return invoice + }) +} + +export async function patchInvoice({ + id, + ...updatedInvoice +}: PickAsRequired<Partial<Invoice>, 'id'>) { + return actionDelayFn(() => { + invoices = produce(invoices, (draft) => { + const invoice = draft.find((d) => d.id === id) + if (!invoice) { + throw new Error('Invoice not found.') + } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (updatedInvoice.title?.toLocaleLowerCase()?.includes('error')) { + throw new Error('Ouch!') + } + Object.assign(invoice, updatedInvoice) + }) + + return invoices.find((d) => d.id === id) + }) +} + +export type UsersSortBy = 'name' | 'id' | 'email' + +export async function fetchUsers({ + filterBy, + sortBy, +}: { filterBy?: string; sortBy?: UsersSortBy } = {}) { + return loaderDelayFn(() => + ensureUsers().then(() => { + let usersDraft = users + + if (filterBy) { + usersDraft = usersDraft.filter((d) => + d.name.toLowerCase().includes(filterBy.toLowerCase()), + ) + } + + if (sortBy) { + usersDraft = [...usersDraft].sort((a, b) => { + return a[sortBy] > b[sortBy] ? 1 : -1 + }) + } + + return usersDraft + }), + ) +} + +export async function fetchUserById(id: number) { + return loaderDelayFn(() => + ensureUsers().then(() => users.find((d) => d.id === id)), + ) +} + +export async function fetchRandomNumber() { + return loaderDelayFn(() => { + return Math.random() + }) +} diff --git a/examples/solid/kitchen-sink-solid-query/src/styles.css b/examples/solid/kitchen-sink-solid-query/src/styles.css new file mode 100644 index 0000000000..0b8e317099 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query/src/styles.css @@ -0,0 +1,13 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +html { + color-scheme: light dark; +} +* { + @apply border-gray-200 dark:border-gray-800; +} +body { + @apply bg-gray-50 text-gray-950 dark:bg-gray-900 dark:text-gray-200; +} diff --git a/examples/solid/kitchen-sink-solid-query/src/utils.tsx b/examples/solid/kitchen-sink-solid-query/src/utils.tsx new file mode 100644 index 0000000000..6435657b12 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query/src/utils.tsx @@ -0,0 +1,33 @@ +export async function loaderDelayFn<T>( + fn: (...args: Array<any>) => Promise<T> | T, +) { + const delay = Number(sessionStorage.getItem('loaderDelay') ?? 0) + const delayPromise = new Promise((r) => setTimeout(r, delay)) + + await delayPromise + const res = await fn() + + return res +} + +export async function actionDelayFn<T>( + fn: (...args: Array<any>) => Promise<T> | T, +) { + const delay = Number(sessionStorage.getItem('actionDelay') ?? 0) + await new Promise((r) => setTimeout(r, delay)) + return fn() +} + +export function shuffle<T>(arr: Array<T>): Array<T> { + let i = arr.length + if (i == 0) return arr + const copy = [...arr] + while (--i) { + const j = Math.floor(Math.random() * (i + 1)) + const a = copy[i] + const b = copy[j] + copy[i] = b! + copy[j] = a! + } + return copy +} diff --git a/examples/solid/kitchen-sink-solid-query/tailwind.config.mjs b/examples/solid/kitchen-sink-solid-query/tailwind.config.mjs new file mode 100644 index 0000000000..4986094b9d --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query/tailwind.config.mjs @@ -0,0 +1,4 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src/**/*.{js,jsx,ts,tsx}', './index.html'], +} diff --git a/examples/solid/kitchen-sink-solid-query/tsconfig.dev.json b/examples/solid/kitchen-sink-solid-query/tsconfig.dev.json new file mode 100644 index 0000000000..285a09b0dc --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query/tsconfig.dev.json @@ -0,0 +1,10 @@ +{ + "composite": true, + "extends": "../../../tsconfig.base.json", + + "files": ["src/main.tsx"], + "include": [ + "src" + // "__tests__/**/*.test.*" + ] +} diff --git a/examples/solid/kitchen-sink-solid-query/tsconfig.json b/examples/solid/kitchen-sink-solid-query/tsconfig.json new file mode 100644 index 0000000000..a972899814 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "target": "ESNext", + "moduleResolution": "Bundler", + "module": "ESNext", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "skipLibCheck": true + } +} diff --git a/examples/solid/kitchen-sink-solid-query/vite.config.js b/examples/solid/kitchen-sink-solid-query/vite.config.js new file mode 100644 index 0000000000..05041cc6d8 --- /dev/null +++ b/examples/solid/kitchen-sink-solid-query/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import solid from 'vite-plugin-solid' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [solid()], +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b958b69ec2..fe907c74db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5474,6 +5474,101 @@ importers: specifier: ^2.11.2 version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@6.1.4(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0)) + examples/solid/kitchen-sink-solid-query: + dependencies: + '@tanstack/solid-query': + specifier: ^5.71.0 + version: 5.71.0(solid-js@1.9.5) + '@tanstack/solid-query-devtools': + specifier: ^5.71.0 + version: 5.71.0(@tanstack/solid-query@5.71.0(solid-js@1.9.5))(solid-js@1.9.5) + '@tanstack/solid-router': + specifier: ^1.114.29 + version: link:../../../packages/solid-router + '@tanstack/solid-router-devtools': + specifier: workspace:^ + version: link:../../../packages/solid-router-devtools + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.5.3) + immer: + specifier: ^10.1.1 + version: 10.1.1 + postcss: + specifier: ^8.5.1 + version: 8.5.3 + redaxios: + specifier: ^0.5.1 + version: 0.5.1 + solid-js: + specifier: ^1.9.5 + version: 1.9.5 + tailwindcss: + specifier: ^3.4.17 + version: 3.4.17 + zod: + specifier: ^3.24.2 + version: 3.24.2 + devDependencies: + typescript: + specifier: ^5.7.2 + version: 5.8.2 + vite: + specifier: 6.1.2 + version: 6.1.2(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0) + vite-plugin-solid: + specifier: ^2.11.6 + version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@6.1.2(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0)) + + examples/solid/kitchen-sink-solid-query-file-based: + dependencies: + '@tanstack/router-plugin': + specifier: workspace:* + version: link:../../../packages/router-plugin + '@tanstack/solid-query': + specifier: ^5.71.0 + version: 5.71.0(solid-js@1.9.5) + '@tanstack/solid-query-devtools': + specifier: ^5.71.0 + version: 5.71.0(@tanstack/solid-query@5.71.0(solid-js@1.9.5))(solid-js@1.9.5) + '@tanstack/solid-router': + specifier: ^1.114.29 + version: link:../../../packages/solid-router + '@tanstack/solid-router-devtools': + specifier: workspace:^ + version: link:../../../packages/solid-router-devtools + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.5.3) + immer: + specifier: ^10.1.1 + version: 10.1.1 + postcss: + specifier: ^8.5.1 + version: 8.5.3 + redaxios: + specifier: ^0.5.1 + version: 0.5.1 + solid-js: + specifier: ^1.9.5 + version: 1.9.5 + tailwindcss: + specifier: ^3.4.17 + version: 3.4.17 + zod: + specifier: ^3.24.2 + version: 3.24.2 + devDependencies: + typescript: + specifier: ^5.7.2 + version: 5.8.2 + vite: + specifier: 6.1.2 + version: 6.1.2(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0) + vite-plugin-solid: + specifier: ^2.11.6 + version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@6.1.2(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0)) + examples/solid/quickstart-file-based: dependencies: '@tanstack/solid-router': @@ -9966,6 +10061,11 @@ packages: peerDependencies: solid-js: ^1.6.0 + '@tanstack/solid-query@5.71.0': + resolution: {integrity: sha512-Gz3MyZtOda9ppPnoPcaohEqwj3ovlU1Mg1IuOr0BSIe4EzkRW8Wpn47VqMi2I2wtryNStPZ2tIU/N98w8IfO/Q==} + peerDependencies: + solid-js: ^1.6.0 + '@tanstack/solid-store@0.7.0': resolution: {integrity: sha512-uDQYkUuH3MppitiduZLTEcItkTr8vEJ33jzp2rH2VvlNRMGbuU54GQcqf3dLIlTbZ1/Z2TtIBtBjjl+N/OhwRg==} peerDependencies: @@ -18153,6 +18253,11 @@ snapshots: '@tanstack/query-core': 5.71.5 solid-js: 1.9.5 + '@tanstack/solid-query@5.71.0(solid-js@1.9.5)': + dependencies: + '@tanstack/query-core': 5.71.0 + solid-js: 1.9.5 + '@tanstack/solid-store@0.7.0(solid-js@1.9.5)': dependencies: '@tanstack/store': 0.7.0 From 6b753d31907f8f79f9687bc06f7cd5f2d9a0b097 Mon Sep 17 00:00:00 2001 From: Birk Skyum <birk.skyum@pm.me> Date: Sun, 30 Mar 2025 23:16:41 +0200 Subject: [PATCH 2/9] reimplement --- .../kitchen-sink-solid-query/src/main.tsx | 126 +++++++++--------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/examples/solid/kitchen-sink-solid-query/src/main.tsx b/examples/solid/kitchen-sink-solid-query/src/main.tsx index 0df7d9263e..a669eef48c 100644 --- a/examples/solid/kitchen-sink-solid-query/src/main.tsx +++ b/examples/solid/kitchen-sink-solid-query/src/main.tsx @@ -79,13 +79,13 @@ const userQueryOptions = (userId: number) => if (!user) { throw new Error('User not found.') } - return user }, }) const useCreateInvoiceMutation = () => { return createMutation(() => ({ + mutationKey: ['invoices', 'create'], mutationFn: postInvoice, onSuccess: () => queryClient.invalidateQueries(), })) @@ -93,6 +93,7 @@ const useCreateInvoiceMutation = () => { const useUpdateInvoiceMutation = (invoiceId: number) => { return createMutation(() => ({ + mutationKey: ['invoices', 'update', invoiceId], mutationFn: patchInvoice, onSuccess: () => queryClient.invalidateQueries(), gcTime: 1000 * 10, @@ -231,7 +232,6 @@ function DashboardLayoutComponent() { ).map(([to, label, exact]) => { return ( <Link - to={to} activeOptions={{ exact }} activeProps={{ class: `font-bold` }} @@ -264,7 +264,7 @@ function DashboardIndexComponent() { <div class="p-2"> <div class="p-2"> Welcome to the dashboard! You have{' '} - <strong>{invoices?.length || 0} total invoices</strong>. + <strong>{invoices?.length} total invoices</strong>. </div> </div> ) @@ -280,7 +280,7 @@ const invoicesLayoutRoute = createRoute({ function InvoicesLayoutComponent() { const invoicesQuery = createQuery(() => invoicesQueryOptions()) - const invoices = invoicesQuery.data || [] + const invoices = invoicesQuery.data // const updateInvoiceMutation = useUpdateInvoiceMutation() // const createInvoiceMutation = useCreateInvoiceMutation() @@ -288,7 +288,7 @@ function InvoicesLayoutComponent() { <div class="flex-1 flex"> {/* {routerTransitionIsPending ? 'pending' : 'null'} */} <div class="divide-y w-48"> - {invoices.map((invoice: Invoice) => { + {invoices?.map((invoice) => { // const updateSubmission = updateInvoiceMutation.submissions.find( // (d) => d.variables?.id === invoice.id, // ) @@ -364,12 +364,11 @@ function InvoicesIndexComponent() { onSubmit={(event) => { event.preventDefault() event.stopPropagation() - const form = event.target as HTMLFormElement - const formData = new FormData(form) + const formData = new FormData(event.target as HTMLFormElement) createInvoiceMutation.mutate({ title: formData.get('title') as string, body: formData.get('body') as string, - } as any) + }) }} class="space-y-2" > @@ -378,9 +377,9 @@ function InvoicesIndexComponent() { <div> <button class="bg-blue-500 rounded p-2 uppercase text-white font-black disabled:opacity-50" - disabled={createInvoiceMutation.isPending} + disabled={createInvoiceMutation.status === 'pending'} > - {createInvoiceMutation.isPending ? ( + {createInvoiceMutation.status === 'pending' ? ( <> Creating <Spinner /> </> @@ -389,11 +388,11 @@ function InvoicesIndexComponent() { )} </button> </div> - {createInvoiceMutation.isSuccess ? ( + {createInvoiceMutation.status === 'success' ? ( <div class="inline-block px-2 py-1 rounded bg-green-500 text-white animate-bounce [animation-iteration-count:2.5] [animation-duration:.3s]"> Created! </div> - ) : createInvoiceMutation.isError ? ( + ) : createInvoiceMutation.status === 'error' ? ( <div class="inline-block px-2 py-1 rounded bg-red-500 text-white animate-bounce [animation-iteration-count:2.5] [animation-duration:.3s]"> Failed to create. </div> @@ -431,14 +430,16 @@ function InvoiceComponent() { const params = invoiceRoute.useParams() const search = invoiceRoute.useSearch() const navigate = useNavigate({ from: invoiceRoute.fullPath }) - const invoiceQuery = createQuery(() => invoiceQueryOptions(params().invoiceId)) - const invoice = invoiceQuery.data || {} as Invoice + const invoiceQuery = createQuery(() => + invoiceQueryOptions(params().invoiceId), + ) + const invoice = invoiceQuery.data const updateInvoiceMutation = useUpdateInvoiceMutation(params().invoiceId) const [notes, setNotes] = Solid.createSignal(search().notes ?? '') Solid.createEffect(() => { navigate({ - search: (old) => ({ + search: (old: any) => ({ ...old, notes: notes() ? notes() : undefined, }), @@ -452,23 +453,22 @@ function InvoiceComponent() { onSubmit={(event) => { event.preventDefault() event.stopPropagation() - const form = event.target as HTMLFormElement - const formData = new FormData(form) + const formData = new FormData(event.target as HTMLFormElement) updateInvoiceMutation.mutate({ - id: invoice.id, + id: invoice!.id, title: formData.get('title') as string, body: formData.get('body') as string, - } as any) + }) }} class="p-2 space-y-2" > <InvoiceFields - invoice={invoice} - disabled={updateInvoiceMutation.isPending} + invoice={invoice!} + disabled={updateInvoiceMutation.status === 'pending'} /> <div> <Link - search={(old) => ({ + search={(old: any) => ({ ...old, showNotes: old.showNotes ? undefined : true, })} @@ -484,7 +484,7 @@ function InvoiceComponent() { <div class="h-2" /> <textarea value={notes()} - onInput={(e) => { + onChange={(e) => { setNotes(e.target.value) }} rows={5} @@ -501,18 +501,18 @@ function InvoiceComponent() { <div> <button class="bg-blue-500 rounded p-2 uppercase text-white font-black disabled:opacity-50" - disabled={updateInvoiceMutation.isPending} + disabled={updateInvoiceMutation.status === 'pending'} > Save </button> </div> - {updateInvoiceMutation.data?.id === invoice.id ? ( + {updateInvoiceMutation.variables?.id === invoice?.id ? ( <div> - {updateInvoiceMutation.isSuccess ? ( + {updateInvoiceMutation.status === 'success' ? ( <div class="inline-block px-2 py-1 rounded bg-green-500 text-white animate-bounce [animation-iteration-count:2.5] [animation-duration:.3s]"> Saved! </div> - ) : updateInvoiceMutation.isError ? ( + ) : updateInvoiceMutation.status === 'error' ? ( <div class="inline-block px-2 py-1 rounded bg-red-500 text-white animate-bounce [animation-iteration-count:2.5] [animation-duration:.3s]"> Failed to save. </div> @@ -551,15 +551,17 @@ const usersLayoutRoute = createRoute({ function UsersComponent() { const navigate = useNavigate({ from: usersLayoutRoute.fullPath }) const search = usersLayoutRoute.useSearch() - const usersQuery = createQuery(() => usersQueryOptions(usersLayoutRoute.useLoaderDeps())) - const users = usersQuery.data || [] - const sortBy = search().usersView?.sortBy ?? 'name' - const filterBy = search().usersView?.filterBy + const usersQuery = createQuery(() => + usersQueryOptions(usersLayoutRoute.useLoaderDeps()), + ) + const users = usersQuery.data + const sortBy = Solid.createMemo(() => search().usersView?.sortBy ?? 'name') + const filterBy = Solid.createMemo(() => search().usersView?.filterBy) - const [filterDraft, setFilterDraft] = Solid.createSignal(filterBy ?? '') + const [filterDraft, setFilterDraft] = Solid.createSignal(filterBy() ?? '') Solid.createEffect(() => { - setFilterDraft(filterBy ?? '') + setFilterDraft(filterBy() ?? '') }) const sortedUsers = Solid.createMemo(() => { @@ -567,22 +569,22 @@ function UsersComponent() { return !sortBy ? users - : [...users].sort((a, b) => { - return a[sortBy] > b[sortBy] ? 1 : -1 + : [...users].sort((a: any, b: any) => { + return a[sortBy()] > b[sortBy()] ? 1 : -1 }) }) const filteredUsers = Solid.createMemo(() => { - if (!filterBy) return sortedUsers() + if (!filterBy()) return sortedUsers() - return sortedUsers().filter((user: any) => - user.name.toLowerCase().includes(filterBy.toLowerCase()), + return sortedUsers().filter((user) => + user.name.toLowerCase().includes(filterBy()?.toLowerCase() ?? ''), ) }) const setSortBy = (sortBy: UsersViewSortBy) => navigate({ - search: (old) => { + search: (old: any) => { return { ...old, usersView: { @@ -596,7 +598,7 @@ function UsersComponent() { Solid.createEffect(() => { navigate({ - search: (old) => { + search: (old: any) => { return { ...old, usersView: { @@ -607,7 +609,7 @@ function UsersComponent() { }, replace: true, }) - }) + }, [filterDraft]) return ( <div class="flex-1 flex"> @@ -615,7 +617,7 @@ function UsersComponent() { <div class="py-2 px-3 flex gap-2 items-center bg-gray-100 dark:bg-gray-800"> <div>Sort By:</div> <select - value={sortBy} + value={sortBy()} onChange={(e) => setSortBy(e.target.value as UsersViewSortBy)} class="flex-1 border p-1 px-2 rounded" > @@ -628,12 +630,12 @@ function UsersComponent() { <div>Filter By:</div> <input value={filterDraft()} - onInput={(e) => setFilterDraft(e.target.value)} + onChange={(e) => setFilterDraft(e.target.value)} placeholder="Search Names..." class="min-w-0 flex-1 border p-1 px-2 rounded" /> </div> - {filteredUsers().map((user: any) => { + {filteredUsers().map((user) => { return ( <div> <Link @@ -797,27 +799,26 @@ function LoginComponent() { }) const search = useSearch({ from: loginRoute.fullPath }) const [username, setUsername] = Solid.createSignal('') - const status = Solid.createMemo(() => routeContext()?.status) - const onSubmit = (e: Event) => { + const onSubmit = (e: any) => { e.preventDefault() routeContext()?.auth.login(username()) router.invalidate() } // Ah, the subtle nuances of client side auth. 🙄 - Solid.createEffect(() => { - if (status() === 'loggedIn' && search().redirect) { + Solid.createRenderEffect(() => { + if (routeContext().status === 'loggedIn' && search().redirect) { router.history.push(search().redirect!) } - }) + }, [routeContext().status, search().redirect]) - return status() === 'loggedIn' ? ( + return status === 'loggedIn' ? ( <div> - Logged in as <strong>{auth.username}</strong> + Logged in as <strong>{routeContext().auth.username}</strong> <div class="h-2" /> <button - onClick={() => auth.logout()} + onClick={() => routeContext().auth.logout()} class="text-sm bg-blue-500 text-white border inline-block py-1 px-2 rounded" > Log out @@ -831,11 +832,12 @@ function LoginComponent() { <form onSubmit={onSubmit} class="flex gap-2"> <input value={username()} - onInput={(e) => setUsername(e.currentTarget.value)} + onChange={(e) => setUsername(e.target.value)} placeholder="Username" class="border p-1 px-2 rounded" /> <button + onClick={() => routeContext().auth.logout()} class="text-sm bg-blue-500 text-white border inline-block py-1 px-2 rounded" > Login @@ -986,7 +988,7 @@ function App() { max="5000" step="100" value={loaderDelay()} - onInput={(e) => setLoaderDelay(e.target.valueAsNumber)} + onChange={(e) => setLoaderDelay(e.target.valueAsNumber)} class="w-full" /> </div> @@ -1011,7 +1013,7 @@ function App() { max="5000" step="100" value={pendingMs()} - onInput={(e) => setPendingMs(e.target.valueAsNumber)} + onChange={(e) => setPendingMs(e.target.valueAsNumber)} class="w-full" /> </div> @@ -1023,7 +1025,7 @@ function App() { max="5000" step="100" value={pendingMinMs()} - onInput={(e) => setPendingMinMs(e.target.valueAsNumber)} + onChange={(e) => setPendingMinMs(e.target.valueAsNumber)} class="w-full" /> </div> @@ -1056,7 +1058,7 @@ function InvoiceFields({ <h2 class="font-bold text-lg"> <input name="title" - value={invoice.title || ''} + value={invoice.title} placeholder="Invoice Title" class="border border-opacity-50 rounded p-2 w-full" disabled={disabled} @@ -1065,7 +1067,7 @@ function InvoiceFields({ <div> <textarea name="body" - value={invoice.body || ''} + value={invoice.body} rows={6} placeholder="Invoice Body..." class="border border-opacity-50 p-2 rounded w-full" @@ -1098,18 +1100,16 @@ function Spinner({ show, wait }: { show?: boolean; wait?: `delay-${number}` }) { } function useSessionStorage<T>(key: string, initialValue: T) { + const stored = sessionStorage.getItem(key) const [state, setState] = Solid.createSignal<T>( - typeof sessionStorage !== 'undefined' && - sessionStorage.getItem(key) !== null - ? JSON.parse(sessionStorage.getItem(key)!) - : initialValue + stored ? JSON.parse(stored) : initialValue, ) Solid.createEffect(() => { sessionStorage.setItem(key, JSON.stringify(state())) }) - return [state, setState] as const + return [state, setState] } const rootElement = document.getElementById('app')! From b51d7dea69f4e34e7663071a0105bfbff7a271a2 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 30 Mar 2025 21:21:50 +0000 Subject: [PATCH 3/9] ci: apply automated fixes --- .../src/hooks/useSessionStorage.tsx | 5 +++-- .../src/main.tsx | 16 +++++++++------- .../src/routes/_auth.profile.tsx | 2 -- .../src/routes/_auth.tsx | 2 +- .../src/routes/_pathlessLayout.route-a.tsx | 2 -- .../src/routes/_pathlessLayout.route-b.tsx | 2 -- .../src/routes/dashboard.index.tsx | 3 +-- .../src/routes/dashboard.invoices.$invoiceId.tsx | 4 +++- .../src/routes/dashboard.invoices.index.tsx | 1 - .../src/routes/dashboard.invoices.route.tsx | 6 ++---- .../src/routes/dashboard.route.tsx | 1 - .../src/routes/dashboard.users.index.tsx | 2 -- .../src/routes/dashboard.users.route.tsx | 8 +++++--- .../src/routes/dashboard.users.user.tsx | 3 +-- .../src/routes/expensive/index.tsx | 1 - .../src/routes/foo/bar.tsx | 1 - .../src/routes/login.tsx | 9 +++++---- .../src/utils/queryOptions.ts | 1 - 18 files changed, 30 insertions(+), 39 deletions(-) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/hooks/useSessionStorage.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/hooks/useSessionStorage.tsx index 710fd6797c..ff2ca73be0 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/hooks/useSessionStorage.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/hooks/useSessionStorage.tsx @@ -1,9 +1,10 @@ import * as Solid from 'solid-js' export function useSessionStorage<T>(key: string, initialValue: T) { - const stored = sessionStorage.getItem(key) - const [state, setState] = Solid.createSignal<T>(stored ? JSON.parse(stored) : initialValue) + const [state, setState] = Solid.createSignal<T>( + stored ? JSON.parse(stored) : initialValue, + ) Solid.createEffect(() => { sessionStorage.setItem(key, JSON.stringify(state())) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/main.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/main.tsx index 276870d3ba..3af4ea3f7b 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/main.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/main.tsx @@ -1,4 +1,4 @@ -import {render} from 'solid-js/web' +import { render } from 'solid-js/web' import { ErrorComponent, RouterProvider, @@ -26,7 +26,7 @@ const router = createRouter({ context: { auth: undefined!, // We'll inject this when we render queryClient: queryClient, // Type assertion to fix the type mismatch - }, + }, defaultPreload: 'intent', // Since we're using Solid Query, we don't want loader calls to ever be stale // This will ensure that the loader is always called when the route is preloaded or visited @@ -142,10 +142,12 @@ function App() { const rootElement = document.getElementById('app')! if (!rootElement.innerHTML) { - render(()=> - <QueryClientProvider client={queryClient}> - <App /> - </QueryClientProvider>, - rootElement + render( + () => ( + <QueryClientProvider client={queryClient}> + <App /> + </QueryClientProvider> + ), + rootElement, ) } diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.profile.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.profile.tsx index 60bafd1de7..a248015704 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.profile.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.profile.tsx @@ -1,5 +1,3 @@ - - export const Route = createFileRoute({ component: ProfileComponent, }) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.tsx index 8e95c11edb..6936f9e0ff 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.tsx @@ -1,4 +1,4 @@ -import { redirect } from '@tanstack/solid-router' +import { redirect } from '@tanstack/solid-router' import { auth } from '../utils/auth' export const Route = createFileRoute({ diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-a.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-a.tsx index 1d1c583fa0..a0bd5240b7 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-a.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-a.tsx @@ -1,5 +1,3 @@ - - export const Route = createFileRoute({ component: LayoutAComponent, }) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-b.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-b.tsx index 3f3e60b070..2864ec1f28 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-b.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-b.tsx @@ -1,5 +1,3 @@ - - export const Route = createFileRoute({ component: LayoutBComponent, }) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.index.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.index.tsx index 2260d95bbc..73b9bb3151 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.index.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.index.tsx @@ -1,4 +1,3 @@ - import { createQuery } from '@tanstack/solid-query' import { invoicesQueryOptions } from '../utils/queryOptions' @@ -9,7 +8,7 @@ export const Route = createFileRoute({ }) function DashboardIndexComponent() { - const invoicesQuery = createQuery(()=>invoicesQueryOptions()) + const invoicesQuery = createQuery(() => invoicesQueryOptions()) const invoices = invoicesQuery.data return ( diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx index d8a07649fd..cf7ea7e4dc 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx @@ -33,7 +33,9 @@ function InvoiceComponent() { const search = Route.useSearch() const params = Route.useParams() const navigate = useNavigate({ from: Route.fullPath }) - const invoiceQuery = createQuery(()=>invoiceQueryOptions(params().invoiceId)) + const invoiceQuery = createQuery(() => + invoiceQueryOptions(params().invoiceId), + ) const invoice = invoiceQuery.data const updateInvoiceMutation = useUpdateInvoiceMutation(params().invoiceId) const [notes, setNotes] = Solid.createSignal(search().notes ?? '') diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.index.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.index.tsx index f5faa8022a..3cd88fe7fd 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.index.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.index.tsx @@ -1,4 +1,3 @@ - import { InvoiceFields } from '../components/InvoiceFields' import { Spinner } from '../components/Spinner' import { useCreateInvoiceMutation } from '../utils/queryOptions' diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.route.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.route.tsx index 72797ba863..455a1ff644 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.route.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.route.tsx @@ -1,6 +1,4 @@ -import { Link, - MatchRoute, - Outlet } from '@tanstack/solid-router' +import { Link, MatchRoute, Outlet } from '@tanstack/solid-router' import { createQuery } from '@tanstack/solid-query' import { Spinner } from '../components/Spinner' import { invoicesQueryOptions } from '../utils/queryOptions' @@ -12,7 +10,7 @@ export const Route = createFileRoute({ }) function InvoicesComponent() { - const invoicesQuery = createQuery(()=>invoicesQueryOptions()) + const invoicesQuery = createQuery(() => invoicesQueryOptions()) const invoices = invoicesQuery.data return ( diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.route.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.route.tsx index 42a54751de..648050e69a 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.route.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.route.tsx @@ -20,7 +20,6 @@ function DashboardComponent() { ).map(([to, label, exact]) => { return ( <Link - to={to} activeOptions={{ exact }} activeProps={{ class: `font-bold` }} diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.index.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.index.tsx index 68d2555648..f5adc3afac 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.index.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.index.tsx @@ -1,5 +1,3 @@ - - export const Route = createFileRoute({ component: UsersIndexComponent, }) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.route.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.route.tsx index 198b6bf935..f0d5640214 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.route.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.route.tsx @@ -1,10 +1,12 @@ /* eslint-disable @typescript-eslint/no-unnecessary-condition */ import * as Solid from 'solid-js' -import { Link, +import { + Link, MatchRoute, Outlet, retainSearchParams, - useNavigate, } from '@tanstack/solid-router' + useNavigate, +} from '@tanstack/solid-router' import { createQuery } from '@tanstack/solid-query' import { z } from 'zod' import { Spinner } from '../components/Spinner' @@ -34,7 +36,7 @@ export const Route = createFileRoute({ function UsersComponent() { const navigate = useNavigate({ from: Route.fullPath }) const search = Route.useSearch() - const usersQuery = createQuery(()=>usersQueryOptions(Route.useLoaderDeps())) + const usersQuery = createQuery(() => usersQueryOptions(Route.useLoaderDeps())) const users = usersQuery.data const sortBy = search().usersView?.sortBy ?? 'name' const filterBy = search().usersView?.filterBy diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.user.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.user.tsx index b019571639..ebd5fe213d 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.user.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.user.tsx @@ -1,4 +1,3 @@ - import { createQuery } from '@tanstack/solid-query' import { z } from 'zod' import { userQueryOptions } from '../utils/queryOptions' @@ -17,7 +16,7 @@ export const Route = createFileRoute({ function UserComponent() { const search = Route.useSearch() - const userQuery = createQuery(()=>userQueryOptions(search().userId)) + const userQuery = createQuery(() => userQueryOptions(search().userId)) const user = userQuery.data return ( diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/expensive/index.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/expensive/index.tsx index 6ee020c9c4..5066d67ac5 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/expensive/index.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/expensive/index.tsx @@ -1,4 +1,3 @@ - import Expensive from './-components/Expensive' export const Route = createFileRoute({ diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/foo/bar.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/foo/bar.tsx index 0a2d9a6ccd..c91111016b 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/foo/bar.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/foo/bar.tsx @@ -1,4 +1,3 @@ - import { z } from 'zod' export const Route = createFileRoute({ diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/login.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/login.tsx index 346ef780fe..f6002a7ef6 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/login.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/login.tsx @@ -1,5 +1,5 @@ import * as Solid from 'solid-js' -import { useRouter } from '@tanstack/solid-router' +import { useRouter } from '@tanstack/solid-router' import { z } from 'zod' export const Route = createFileRoute({ @@ -11,9 +11,10 @@ export const Route = createFileRoute({ function LoginComponent() { const router = useRouter() - const routeContext: Solid.Accessor<{ auth: any; status: any }> = Route.useRouteContext({ - select: ({ auth }:{auth:any}) => ({ auth, status: auth.status }), - }) + const routeContext: Solid.Accessor<{ auth: any; status: any }> = + Route.useRouteContext({ + select: ({ auth }: { auth: any }) => ({ auth, status: auth.status }), + }) const search = Route.useSearch() const [username, setUsername] = Solid.createSignal('') diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/utils/queryOptions.ts b/examples/solid/kitchen-sink-solid-query-file-based/src/utils/queryOptions.ts index 8955524132..19d2ba78f3 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/utils/queryOptions.ts +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/utils/queryOptions.ts @@ -9,7 +9,6 @@ import { postInvoice, } from './mockTodos' - export const invoicesQueryOptions = () => queryOptions({ queryKey: ['invoices'], From 937930de3b6dec0c46ee131b21fa9c7ef49ee5f0 Mon Sep 17 00:00:00 2001 From: Birk Skyum <birk.skyum@pm.me> Date: Mon, 7 Apr 2025 13:36:44 +0200 Subject: [PATCH 4/9] update query package --- .../package.json | 4 +- .../src/routes/dashboard.index.tsx | 4 +- .../routes/dashboard.invoices.$invoiceId.tsx | 4 +- .../src/routes/dashboard.users.user.tsx | 4 +- .../kitchen-sink-solid-query/package.json | 4 +- .../kitchen-sink-solid-query/src/main.tsx | 18 +++--- pnpm-lock.yaml | 58 +++++++++++++------ 7 files changed, 59 insertions(+), 37 deletions(-) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/package.json b/examples/solid/kitchen-sink-solid-query-file-based/package.json index abb6175bf2..5ac4c20f89 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/package.json +++ b/examples/solid/kitchen-sink-solid-query-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/solid-query": "^5.71.0", - "@tanstack/solid-query-devtools": "^5.71.0", + "@tanstack/solid-query": "^5.72.0", + "@tanstack/solid-query-devtools": "^5.72.0", "@tanstack/solid-router": "^1.114.29", "@tanstack/solid-router-devtools": "^1.114.29", "@tanstack/router-plugin": "^1.114.29", diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.index.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.index.tsx index 73b9bb3151..fb74cd1f8c 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.index.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.index.tsx @@ -1,4 +1,4 @@ -import { createQuery } from '@tanstack/solid-query' +import { useQuery } from '@tanstack/solid-query' import { invoicesQueryOptions } from '../utils/queryOptions' export const Route = createFileRoute({ @@ -8,7 +8,7 @@ export const Route = createFileRoute({ }) function DashboardIndexComponent() { - const invoicesQuery = createQuery(() => invoicesQueryOptions()) + const invoicesQuery = useQuery(() => invoicesQueryOptions()) const invoices = invoicesQuery.data return ( diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx index cf7ea7e4dc..6627dce9c9 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx @@ -1,6 +1,6 @@ import * as Solid from 'solid-js' import { Link, useNavigate } from '@tanstack/solid-router' -import { createQuery } from '@tanstack/solid-query' +import { useQuery } from '@tanstack/solid-query' import { z } from 'zod' import { InvoiceFields } from '../components/InvoiceFields' import { @@ -33,7 +33,7 @@ function InvoiceComponent() { const search = Route.useSearch() const params = Route.useParams() const navigate = useNavigate({ from: Route.fullPath }) - const invoiceQuery = createQuery(() => + const invoiceQuery = useQuery(() => invoiceQueryOptions(params().invoiceId), ) const invoice = invoiceQuery.data diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.user.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.user.tsx index ebd5fe213d..6b8380842d 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.user.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.user.tsx @@ -1,4 +1,4 @@ -import { createQuery } from '@tanstack/solid-query' +import { useQuery } from '@tanstack/solid-query' import { z } from 'zod' import { userQueryOptions } from '../utils/queryOptions' @@ -16,7 +16,7 @@ export const Route = createFileRoute({ function UserComponent() { const search = Route.useSearch() - const userQuery = createQuery(() => userQueryOptions(search().userId)) + const userQuery = useQuery(() => userQueryOptions(search().userId)) const user = userQuery.data return ( diff --git a/examples/solid/kitchen-sink-solid-query/package.json b/examples/solid/kitchen-sink-solid-query/package.json index 868e13d162..4171ea2dfa 100644 --- a/examples/solid/kitchen-sink-solid-query/package.json +++ b/examples/solid/kitchen-sink-solid-query/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/solid-query": "^5.71.0", - "@tanstack/solid-query-devtools": "^5.71.0", + "@tanstack/solid-query": "^5.72.0", + "@tanstack/solid-query-devtools": "^5.72.0", "@tanstack/solid-router": "^1.114.29", "@tanstack/solid-router-devtools": "^1.114.29", "immer": "^10.1.1", diff --git a/examples/solid/kitchen-sink-solid-query/src/main.tsx b/examples/solid/kitchen-sink-solid-query/src/main.tsx index a669eef48c..2048becb89 100644 --- a/examples/solid/kitchen-sink-solid-query/src/main.tsx +++ b/examples/solid/kitchen-sink-solid-query/src/main.tsx @@ -22,9 +22,9 @@ import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools' import { QueryClient, QueryClientProvider, - createMutation, - createQuery, queryOptions, + useMutation, + useQuery, } from '@tanstack/solid-query' import { SolidQueryDevtools } from '@tanstack/solid-query-devtools' import { z } from 'zod' @@ -84,7 +84,7 @@ const userQueryOptions = (userId: number) => }) const useCreateInvoiceMutation = () => { - return createMutation(() => ({ + return useMutation(() => ({ mutationKey: ['invoices', 'create'], mutationFn: postInvoice, onSuccess: () => queryClient.invalidateQueries(), @@ -92,7 +92,7 @@ const useCreateInvoiceMutation = () => { } const useUpdateInvoiceMutation = (invoiceId: number) => { - return createMutation(() => ({ + return useMutation(() => ({ mutationKey: ['invoices', 'update', invoiceId], mutationFn: patchInvoice, onSuccess: () => queryClient.invalidateQueries(), @@ -257,7 +257,7 @@ const dashboardIndexRoute = createRoute({ }) function DashboardIndexComponent() { - const invoicesQuery = createQuery(() => invoicesQueryOptions()) + const invoicesQuery = useQuery(() => invoicesQueryOptions()) const invoices = invoicesQuery.data return ( @@ -279,7 +279,7 @@ const invoicesLayoutRoute = createRoute({ }) function InvoicesLayoutComponent() { - const invoicesQuery = createQuery(() => invoicesQueryOptions()) + const invoicesQuery = useQuery(() => invoicesQueryOptions()) const invoices = invoicesQuery.data // const updateInvoiceMutation = useUpdateInvoiceMutation() // const createInvoiceMutation = useCreateInvoiceMutation() @@ -430,7 +430,7 @@ function InvoiceComponent() { const params = invoiceRoute.useParams() const search = invoiceRoute.useSearch() const navigate = useNavigate({ from: invoiceRoute.fullPath }) - const invoiceQuery = createQuery(() => + const invoiceQuery = useQuery(() => invoiceQueryOptions(params().invoiceId), ) const invoice = invoiceQuery.data @@ -551,7 +551,7 @@ const usersLayoutRoute = createRoute({ function UsersComponent() { const navigate = useNavigate({ from: usersLayoutRoute.fullPath }) const search = usersLayoutRoute.useSearch() - const usersQuery = createQuery(() => + const usersQuery = useQuery(() => usersQueryOptions(usersLayoutRoute.useLoaderDeps()), ) const users = usersQuery.data @@ -718,7 +718,7 @@ const userRoute = createRoute({ function UserComponent() { const search = userRoute.useSearch() - const userQuery = createQuery(() => userQueryOptions(search().userId)) + const userQuery = useQuery(() => userQueryOptions(search().userId)) const user = userQuery.data return ( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fe907c74db..50f8023701 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5477,11 +5477,11 @@ importers: examples/solid/kitchen-sink-solid-query: dependencies: '@tanstack/solid-query': - specifier: ^5.71.0 - version: 5.71.0(solid-js@1.9.5) + specifier: ^5.72.0 + version: 5.72.0(solid-js@1.9.5) '@tanstack/solid-query-devtools': - specifier: ^5.71.0 - version: 5.71.0(@tanstack/solid-query@5.71.0(solid-js@1.9.5))(solid-js@1.9.5) + specifier: ^5.72.0 + version: 5.72.0(@tanstack/solid-query@5.72.0(solid-js@1.9.5))(solid-js@1.9.5) '@tanstack/solid-router': specifier: ^1.114.29 version: link:../../../packages/solid-router @@ -5514,11 +5514,11 @@ importers: specifier: ^5.7.2 version: 5.8.2 vite: - specifier: 6.1.2 - version: 6.1.2(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0) + specifier: 6.1.4 + version: 6.1.4(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@6.1.2(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0)) + version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@6.1.4(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0)) examples/solid/kitchen-sink-solid-query-file-based: dependencies: @@ -5526,11 +5526,11 @@ importers: specifier: workspace:* version: link:../../../packages/router-plugin '@tanstack/solid-query': - specifier: ^5.71.0 - version: 5.71.0(solid-js@1.9.5) + specifier: ^5.72.0 + version: 5.72.0(solid-js@1.9.5) '@tanstack/solid-query-devtools': - specifier: ^5.71.0 - version: 5.71.0(@tanstack/solid-query@5.71.0(solid-js@1.9.5))(solid-js@1.9.5) + specifier: ^5.72.0 + version: 5.72.0(@tanstack/solid-query@5.72.0(solid-js@1.9.5))(solid-js@1.9.5) '@tanstack/solid-router': specifier: ^1.114.29 version: link:../../../packages/solid-router @@ -5563,11 +5563,11 @@ importers: specifier: ^5.7.2 version: 5.8.2 vite: - specifier: 6.1.2 - version: 6.1.2(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0) + specifier: 6.1.4 + version: 6.1.4(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@6.1.2(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0)) + version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@6.1.4(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0)) examples/solid/quickstart-file-based: dependencies: @@ -10021,12 +10021,18 @@ packages: '@tanstack/query-core@5.71.5': resolution: {integrity: sha512-XOQ5SyjCdwhxyLksGKWSL5poqyEXYPDnsrZAzJm2LgrMm4Yh6VOrfC+IFosXreDw9HNqC11YAMY3HlfHjNzuaA==} + '@tanstack/query-core@5.72.0': + resolution: {integrity: sha512-aa3p6Mou++JLLxxxVX9AB9uGeRIGc0JWkw96GASXuMG8K3D+JpYbSFcqXbkGFJ1eX2jKHPurmCBoO43RjjXJCA==} + '@tanstack/query-devtools@5.67.2': resolution: {integrity: sha512-O4QXFFd7xqp6EX7sdvc9tsVO8nm4lpWBqwpgjpVLW5g7IeOY6VnS/xvs/YzbRhBVkKTMaJMOUGU7NhSX+YGoNg==} '@tanstack/query-devtools@5.71.5': resolution: {integrity: sha512-Fq1JeAp+I52Md/KnyeFxzG7G0RpdHgeOfDNhSPkZQs/JqqXuAfpUf+wFHDz+vP0GZbSnla2JmcLSQebOkIb1yA==} + '@tanstack/query-devtools@5.72.0': + resolution: {integrity: sha512-/SJr77+epgxmwMB5Wjlg1TiN6bJObAJKi/mIHWQcon0VJxH3NNOtGCpQRbWih8xOq9oQ5kuIYLftYfD0j2ujew==} + '@tanstack/react-query-devtools@5.67.2': resolution: {integrity: sha512-cmj2DxBc+/9btQ66n5xI8wTtAma2BLVa403K7zIYiguzJ/kV201jnGensYqJeu1Rd8uRMLLRM74jLVMLDWNRJA==} peerDependencies: @@ -10056,13 +10062,19 @@ packages: '@tanstack/solid-query': ^5.71.9 solid-js: ^1.6.0 + '@tanstack/solid-query-devtools@5.72.0': + resolution: {integrity: sha512-K4Gdyk/yx8OSAxO/hwol1Ou2fNOGmZ9m4E8fRrUP5wzcDXaP3Lh1a6CRg6bunRiE6/12rNdy7xpvyZIsyKCNBQ==} + peerDependencies: + '@tanstack/solid-query': ^5.72.0 + solid-js: ^1.6.0 + '@tanstack/solid-query@5.71.9': resolution: {integrity: sha512-ue9xAX2okLGdXxSWcnYfOedL/QgOx9eNKEJdoM9D3RF5xxxn3p+dXp7+Buzqu2OmfwRnAjHkWRASgS/9NlDMLg==} peerDependencies: solid-js: ^1.6.0 - '@tanstack/solid-query@5.71.0': - resolution: {integrity: sha512-Gz3MyZtOda9ppPnoPcaohEqwj3ovlU1Mg1IuOr0BSIe4EzkRW8Wpn47VqMi2I2wtryNStPZ2tIU/N98w8IfO/Q==} + '@tanstack/solid-query@5.72.0': + resolution: {integrity: sha512-zQ27dZEakN05hZB9C+qTKgtBU3N7Li3rx++nUOd4K5LtrTMjiIEp2FEbYoGe9B0Pn9tF5CIthc9fknl/qBVI5g==} peerDependencies: solid-js: ^1.6.0 @@ -18214,10 +18226,14 @@ snapshots: '@tanstack/query-core@5.71.5': {} + '@tanstack/query-core@5.72.0': {} + '@tanstack/query-devtools@5.67.2': {} '@tanstack/query-devtools@5.71.5': {} + '@tanstack/query-devtools@5.72.0': {} + '@tanstack/react-query-devtools@5.67.2(@tanstack/react-query@5.66.0(react@19.0.0))(react@19.0.0)': dependencies: '@tanstack/query-devtools': 5.67.2 @@ -18248,14 +18264,20 @@ snapshots: '@tanstack/solid-query': 5.71.9(solid-js@1.9.5) solid-js: 1.9.5 + '@tanstack/solid-query-devtools@5.72.0(@tanstack/solid-query@5.72.0(solid-js@1.9.5))(solid-js@1.9.5)': + dependencies: + '@tanstack/query-devtools': 5.72.0 + '@tanstack/solid-query': 5.72.0(solid-js@1.9.5) + solid-js: 1.9.5 + '@tanstack/solid-query@5.71.9(solid-js@1.9.5)': dependencies: '@tanstack/query-core': 5.71.5 solid-js: 1.9.5 - '@tanstack/solid-query@5.71.0(solid-js@1.9.5)': + '@tanstack/solid-query@5.72.0(solid-js@1.9.5)': dependencies: - '@tanstack/query-core': 5.71.0 + '@tanstack/query-core': 5.72.0 solid-js: 1.9.5 '@tanstack/solid-store@0.7.0(solid-js@1.9.5)': From 189c44a6e5b21aad528152b5e7720392d8d04e37 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 11:37:49 +0000 Subject: [PATCH 5/9] ci: apply automated fixes --- .../src/routes/dashboard.invoices.$invoiceId.tsx | 4 +--- examples/solid/kitchen-sink-solid-query/src/main.tsx | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx index 6627dce9c9..27ced1eabe 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx @@ -33,9 +33,7 @@ function InvoiceComponent() { const search = Route.useSearch() const params = Route.useParams() const navigate = useNavigate({ from: Route.fullPath }) - const invoiceQuery = useQuery(() => - invoiceQueryOptions(params().invoiceId), - ) + const invoiceQuery = useQuery(() => invoiceQueryOptions(params().invoiceId)) const invoice = invoiceQuery.data const updateInvoiceMutation = useUpdateInvoiceMutation(params().invoiceId) const [notes, setNotes] = Solid.createSignal(search().notes ?? '') diff --git a/examples/solid/kitchen-sink-solid-query/src/main.tsx b/examples/solid/kitchen-sink-solid-query/src/main.tsx index 2048becb89..a235041c8e 100644 --- a/examples/solid/kitchen-sink-solid-query/src/main.tsx +++ b/examples/solid/kitchen-sink-solid-query/src/main.tsx @@ -430,9 +430,7 @@ function InvoiceComponent() { const params = invoiceRoute.useParams() const search = invoiceRoute.useSearch() const navigate = useNavigate({ from: invoiceRoute.fullPath }) - const invoiceQuery = useQuery(() => - invoiceQueryOptions(params().invoiceId), - ) + const invoiceQuery = useQuery(() => invoiceQueryOptions(params().invoiceId)) const invoice = invoiceQuery.data const updateInvoiceMutation = useUpdateInvoiceMutation(params().invoiceId) const [notes, setNotes] = Solid.createSignal(search().notes ?? '') From aae90dc9983ab6f068d7208ac1f62ea55c03a32d Mon Sep 17 00:00:00 2001 From: Birk Skyum <birk.skyum@pm.me> Date: Mon, 7 Apr 2025 14:09:38 +0200 Subject: [PATCH 6/9] update route tree --- .../src/routeTree.gen.ts | 284 ++++-------------- 1 file changed, 64 insertions(+), 220 deletions(-) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routeTree.gen.ts b/examples/solid/kitchen-sink-solid-query-file-based/src/routeTree.gen.ts index 729ef3f63f..cf3b758de5 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routeTree.gen.ts +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routeTree.gen.ts @@ -10,128 +10,128 @@ // Import Routes -import type { FileRoutesByPath, CreateFileRoute } from '@tanstack/solid-router' import { Route as rootRoute } from './routes/__root' -import { Route as LoginRouteImport } from './routes/login' -import { Route as PathlessLayoutRouteImport } from './routes/_pathlessLayout' -import { Route as AuthRouteImport } from './routes/_auth' -import { Route as DashboardRouteRouteImport } from './routes/dashboard.route' -import { Route as IndexRouteImport } from './routes/index' -import { Route as ExpensiveIndexRouteImport } from './routes/expensive/index' -import { Route as DashboardIndexRouteImport } from './routes/dashboard.index' -import { Route as FooBarRouteImport } from './routes/foo/bar' -import { Route as PathlessLayoutRouteBRouteImport } from './routes/_pathlessLayout.route-b' -import { Route as PathlessLayoutRouteARouteImport } from './routes/_pathlessLayout.route-a' -import { Route as AuthProfileRouteImport } from './routes/_auth.profile' -import { Route as DashboardUsersRouteRouteImport } from './routes/dashboard.users.route' -import { Route as DashboardInvoicesRouteRouteImport } from './routes/dashboard.invoices.route' -import { Route as DashboardUsersIndexRouteImport } from './routes/dashboard.users.index' -import { Route as DashboardInvoicesIndexRouteImport } from './routes/dashboard.invoices.index' -import { Route as DashboardUsersUserRouteImport } from './routes/dashboard.users.user' -import { Route as DashboardInvoicesInvoiceIdRouteImport } from './routes/dashboard.invoices.$invoiceId' +import { Route as LoginImport } from './routes/login' +import { Route as PathlessLayoutImport } from './routes/_pathlessLayout' +import { Route as AuthImport } from './routes/_auth' +import { Route as DashboardRouteImport } from './routes/dashboard.route' +import { Route as IndexImport } from './routes/index' +import { Route as ExpensiveIndexImport } from './routes/expensive/index' +import { Route as DashboardIndexImport } from './routes/dashboard.index' +import { Route as FooBarImport } from './routes/foo/bar' +import { Route as PathlessLayoutRouteBImport } from './routes/_pathlessLayout.route-b' +import { Route as PathlessLayoutRouteAImport } from './routes/_pathlessLayout.route-a' +import { Route as AuthProfileImport } from './routes/_auth.profile' +import { Route as DashboardUsersRouteImport } from './routes/dashboard.users.route' +import { Route as DashboardInvoicesRouteImport } from './routes/dashboard.invoices.route' +import { Route as DashboardUsersIndexImport } from './routes/dashboard.users.index' +import { Route as DashboardInvoicesIndexImport } from './routes/dashboard.invoices.index' +import { Route as DashboardUsersUserImport } from './routes/dashboard.users.user' +import { Route as DashboardInvoicesInvoiceIdImport } from './routes/dashboard.invoices.$invoiceId' // Create/Update Routes -const LoginRoute = LoginRouteImport.update({ +const LoginRoute = LoginImport.update({ id: '/login', path: '/login', getParentRoute: () => rootRoute, } as any) -const PathlessLayoutRoute = PathlessLayoutRouteImport.update({ +const PathlessLayoutRoute = PathlessLayoutImport.update({ id: '/_pathlessLayout', getParentRoute: () => rootRoute, } as any) -const AuthRoute = AuthRouteImport.update({ +const AuthRoute = AuthImport.update({ id: '/_auth', getParentRoute: () => rootRoute, } as any) -const DashboardRouteRoute = DashboardRouteRouteImport.update({ +const DashboardRouteRoute = DashboardRouteImport.update({ id: '/dashboard', path: '/dashboard', getParentRoute: () => rootRoute, } as any) -const IndexRoute = IndexRouteImport.update({ +const IndexRoute = IndexImport.update({ id: '/', path: '/', getParentRoute: () => rootRoute, } as any) -const ExpensiveIndexRoute = ExpensiveIndexRouteImport.update({ +const ExpensiveIndexRoute = ExpensiveIndexImport.update({ id: '/expensive/', path: '/expensive/', getParentRoute: () => rootRoute, } as any) -const DashboardIndexRoute = DashboardIndexRouteImport.update({ +const DashboardIndexRoute = DashboardIndexImport.update({ id: '/', path: '/', getParentRoute: () => DashboardRouteRoute, } as any) -const FooBarRoute = FooBarRouteImport.update({ +const FooBarRoute = FooBarImport.update({ id: '/foo/bar', path: '/foo/bar', getParentRoute: () => rootRoute, } as any) -const PathlessLayoutRouteBRoute = PathlessLayoutRouteBRouteImport.update({ +const PathlessLayoutRouteBRoute = PathlessLayoutRouteBImport.update({ id: '/route-b', path: '/route-b', getParentRoute: () => PathlessLayoutRoute, } as any) -const PathlessLayoutRouteARoute = PathlessLayoutRouteARouteImport.update({ +const PathlessLayoutRouteARoute = PathlessLayoutRouteAImport.update({ id: '/route-a', path: '/route-a', getParentRoute: () => PathlessLayoutRoute, } as any) -const AuthProfileRoute = AuthProfileRouteImport.update({ +const AuthProfileRoute = AuthProfileImport.update({ id: '/profile', path: '/profile', getParentRoute: () => AuthRoute, } as any) -const DashboardUsersRouteRoute = DashboardUsersRouteRouteImport.update({ +const DashboardUsersRouteRoute = DashboardUsersRouteImport.update({ id: '/users', path: '/users', getParentRoute: () => DashboardRouteRoute, } as any) -const DashboardInvoicesRouteRoute = DashboardInvoicesRouteRouteImport.update({ +const DashboardInvoicesRouteRoute = DashboardInvoicesRouteImport.update({ id: '/invoices', path: '/invoices', getParentRoute: () => DashboardRouteRoute, } as any) -const DashboardUsersIndexRoute = DashboardUsersIndexRouteImport.update({ +const DashboardUsersIndexRoute = DashboardUsersIndexImport.update({ id: '/', path: '/', getParentRoute: () => DashboardUsersRouteRoute, } as any) -const DashboardInvoicesIndexRoute = DashboardInvoicesIndexRouteImport.update({ +const DashboardInvoicesIndexRoute = DashboardInvoicesIndexImport.update({ id: '/', path: '/', getParentRoute: () => DashboardInvoicesRouteRoute, } as any) -const DashboardUsersUserRoute = DashboardUsersUserRouteImport.update({ +const DashboardUsersUserRoute = DashboardUsersUserImport.update({ id: '/user', path: '/user', getParentRoute: () => DashboardUsersRouteRoute, } as any) -const DashboardInvoicesInvoiceIdRoute = - DashboardInvoicesInvoiceIdRouteImport.update({ +const DashboardInvoicesInvoiceIdRoute = DashboardInvoicesInvoiceIdImport.update( + { id: '/$invoiceId', path: '/$invoiceId', getParentRoute: () => DashboardInvoicesRouteRoute, - } as any) + } as any, +) // Populate the FileRoutesByPath interface @@ -141,280 +141,124 @@ declare module '@tanstack/solid-router' { id: '/' path: '/' fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + preLoaderRoute: typeof IndexImport parentRoute: typeof rootRoute } '/dashboard': { id: '/dashboard' path: '/dashboard' fullPath: '/dashboard' - preLoaderRoute: typeof DashboardRouteRouteImport + preLoaderRoute: typeof DashboardRouteImport parentRoute: typeof rootRoute } '/_auth': { id: '/_auth' path: '' fullPath: '' - preLoaderRoute: typeof AuthRouteImport + preLoaderRoute: typeof AuthImport parentRoute: typeof rootRoute } '/_pathlessLayout': { id: '/_pathlessLayout' path: '' fullPath: '' - preLoaderRoute: typeof PathlessLayoutRouteImport + preLoaderRoute: typeof PathlessLayoutImport parentRoute: typeof rootRoute } '/login': { id: '/login' path: '/login' fullPath: '/login' - preLoaderRoute: typeof LoginRouteImport + preLoaderRoute: typeof LoginImport parentRoute: typeof rootRoute } '/dashboard/invoices': { id: '/dashboard/invoices' path: '/invoices' fullPath: '/dashboard/invoices' - preLoaderRoute: typeof DashboardInvoicesRouteRouteImport - parentRoute: typeof DashboardRouteRouteImport + preLoaderRoute: typeof DashboardInvoicesRouteImport + parentRoute: typeof DashboardRouteImport } '/dashboard/users': { id: '/dashboard/users' path: '/users' fullPath: '/dashboard/users' - preLoaderRoute: typeof DashboardUsersRouteRouteImport - parentRoute: typeof DashboardRouteRouteImport + preLoaderRoute: typeof DashboardUsersRouteImport + parentRoute: typeof DashboardRouteImport } '/_auth/profile': { id: '/_auth/profile' path: '/profile' fullPath: '/profile' - preLoaderRoute: typeof AuthProfileRouteImport - parentRoute: typeof AuthRouteImport + preLoaderRoute: typeof AuthProfileImport + parentRoute: typeof AuthImport } '/_pathlessLayout/route-a': { id: '/_pathlessLayout/route-a' path: '/route-a' fullPath: '/route-a' - preLoaderRoute: typeof PathlessLayoutRouteARouteImport - parentRoute: typeof PathlessLayoutRouteImport + preLoaderRoute: typeof PathlessLayoutRouteAImport + parentRoute: typeof PathlessLayoutImport } '/_pathlessLayout/route-b': { id: '/_pathlessLayout/route-b' path: '/route-b' fullPath: '/route-b' - preLoaderRoute: typeof PathlessLayoutRouteBRouteImport - parentRoute: typeof PathlessLayoutRouteImport + preLoaderRoute: typeof PathlessLayoutRouteBImport + parentRoute: typeof PathlessLayoutImport } '/foo/bar': { id: '/foo/bar' path: '/foo/bar' fullPath: '/foo/bar' - preLoaderRoute: typeof FooBarRouteImport + preLoaderRoute: typeof FooBarImport parentRoute: typeof rootRoute } '/dashboard/': { id: '/dashboard/' path: '/' fullPath: '/dashboard/' - preLoaderRoute: typeof DashboardIndexRouteImport - parentRoute: typeof DashboardRouteRouteImport + preLoaderRoute: typeof DashboardIndexImport + parentRoute: typeof DashboardRouteImport } '/expensive/': { id: '/expensive/' path: '/expensive' fullPath: '/expensive' - preLoaderRoute: typeof ExpensiveIndexRouteImport + preLoaderRoute: typeof ExpensiveIndexImport parentRoute: typeof rootRoute } '/dashboard/invoices/$invoiceId': { id: '/dashboard/invoices/$invoiceId' path: '/$invoiceId' fullPath: '/dashboard/invoices/$invoiceId' - preLoaderRoute: typeof DashboardInvoicesInvoiceIdRouteImport - parentRoute: typeof DashboardInvoicesRouteRouteImport + preLoaderRoute: typeof DashboardInvoicesInvoiceIdImport + parentRoute: typeof DashboardInvoicesRouteImport } '/dashboard/users/user': { id: '/dashboard/users/user' path: '/user' fullPath: '/dashboard/users/user' - preLoaderRoute: typeof DashboardUsersUserRouteImport - parentRoute: typeof DashboardUsersRouteRouteImport + preLoaderRoute: typeof DashboardUsersUserImport + parentRoute: typeof DashboardUsersRouteImport } '/dashboard/invoices/': { id: '/dashboard/invoices/' path: '/' fullPath: '/dashboard/invoices/' - preLoaderRoute: typeof DashboardInvoicesIndexRouteImport - parentRoute: typeof DashboardInvoicesRouteRouteImport + preLoaderRoute: typeof DashboardInvoicesIndexImport + parentRoute: typeof DashboardInvoicesRouteImport } '/dashboard/users/': { id: '/dashboard/users/' path: '/' fullPath: '/dashboard/users/' - preLoaderRoute: typeof DashboardUsersIndexRouteImport - parentRoute: typeof DashboardUsersRouteRouteImport + preLoaderRoute: typeof DashboardUsersIndexImport + parentRoute: typeof DashboardUsersRouteImport } } } -// Add type-safety to the createFileRoute function across the route tree - -declare module './routes/index' { - const createFileRoute: CreateFileRoute< - '/', - FileRoutesByPath['/']['parentRoute'], - FileRoutesByPath['/']['id'], - FileRoutesByPath['/']['path'], - FileRoutesByPath['/']['fullPath'] - > -} -declare module './routes/dashboard.route' { - const createFileRoute: CreateFileRoute< - '/dashboard', - FileRoutesByPath['/dashboard']['parentRoute'], - FileRoutesByPath['/dashboard']['id'], - FileRoutesByPath['/dashboard']['path'], - FileRoutesByPath['/dashboard']['fullPath'] - > -} -declare module './routes/_auth' { - const createFileRoute: CreateFileRoute< - '/_auth', - FileRoutesByPath['/_auth']['parentRoute'], - FileRoutesByPath['/_auth']['id'], - FileRoutesByPath['/_auth']['path'], - FileRoutesByPath['/_auth']['fullPath'] - > -} -declare module './routes/_pathlessLayout' { - const createFileRoute: CreateFileRoute< - '/_pathlessLayout', - FileRoutesByPath['/_pathlessLayout']['parentRoute'], - FileRoutesByPath['/_pathlessLayout']['id'], - FileRoutesByPath['/_pathlessLayout']['path'], - FileRoutesByPath['/_pathlessLayout']['fullPath'] - > -} -declare module './routes/login' { - const createFileRoute: CreateFileRoute< - '/login', - FileRoutesByPath['/login']['parentRoute'], - FileRoutesByPath['/login']['id'], - FileRoutesByPath['/login']['path'], - FileRoutesByPath['/login']['fullPath'] - > -} -declare module './routes/dashboard.invoices.route' { - const createFileRoute: CreateFileRoute< - '/dashboard/invoices', - FileRoutesByPath['/dashboard/invoices']['parentRoute'], - FileRoutesByPath['/dashboard/invoices']['id'], - FileRoutesByPath['/dashboard/invoices']['path'], - FileRoutesByPath['/dashboard/invoices']['fullPath'] - > -} -declare module './routes/dashboard.users.route' { - const createFileRoute: CreateFileRoute< - '/dashboard/users', - FileRoutesByPath['/dashboard/users']['parentRoute'], - FileRoutesByPath['/dashboard/users']['id'], - FileRoutesByPath['/dashboard/users']['path'], - FileRoutesByPath['/dashboard/users']['fullPath'] - > -} -declare module './routes/_auth.profile' { - const createFileRoute: CreateFileRoute< - '/_auth/profile', - FileRoutesByPath['/_auth/profile']['parentRoute'], - FileRoutesByPath['/_auth/profile']['id'], - FileRoutesByPath['/_auth/profile']['path'], - FileRoutesByPath['/_auth/profile']['fullPath'] - > -} -declare module './routes/_pathlessLayout.route-a' { - const createFileRoute: CreateFileRoute< - '/_pathlessLayout/route-a', - FileRoutesByPath['/_pathlessLayout/route-a']['parentRoute'], - FileRoutesByPath['/_pathlessLayout/route-a']['id'], - FileRoutesByPath['/_pathlessLayout/route-a']['path'], - FileRoutesByPath['/_pathlessLayout/route-a']['fullPath'] - > -} -declare module './routes/_pathlessLayout.route-b' { - const createFileRoute: CreateFileRoute< - '/_pathlessLayout/route-b', - FileRoutesByPath['/_pathlessLayout/route-b']['parentRoute'], - FileRoutesByPath['/_pathlessLayout/route-b']['id'], - FileRoutesByPath['/_pathlessLayout/route-b']['path'], - FileRoutesByPath['/_pathlessLayout/route-b']['fullPath'] - > -} -declare module './routes/foo/bar' { - const createFileRoute: CreateFileRoute< - '/foo/bar', - FileRoutesByPath['/foo/bar']['parentRoute'], - FileRoutesByPath['/foo/bar']['id'], - FileRoutesByPath['/foo/bar']['path'], - FileRoutesByPath['/foo/bar']['fullPath'] - > -} -declare module './routes/dashboard.index' { - const createFileRoute: CreateFileRoute< - '/dashboard/', - FileRoutesByPath['/dashboard/']['parentRoute'], - FileRoutesByPath['/dashboard/']['id'], - FileRoutesByPath['/dashboard/']['path'], - FileRoutesByPath['/dashboard/']['fullPath'] - > -} -declare module './routes/expensive/index' { - const createFileRoute: CreateFileRoute< - '/expensive/', - FileRoutesByPath['/expensive/']['parentRoute'], - FileRoutesByPath['/expensive/']['id'], - FileRoutesByPath['/expensive/']['path'], - FileRoutesByPath['/expensive/']['fullPath'] - > -} -declare module './routes/dashboard.invoices.$invoiceId' { - const createFileRoute: CreateFileRoute< - '/dashboard/invoices/$invoiceId', - FileRoutesByPath['/dashboard/invoices/$invoiceId']['parentRoute'], - FileRoutesByPath['/dashboard/invoices/$invoiceId']['id'], - FileRoutesByPath['/dashboard/invoices/$invoiceId']['path'], - FileRoutesByPath['/dashboard/invoices/$invoiceId']['fullPath'] - > -} -declare module './routes/dashboard.users.user' { - const createFileRoute: CreateFileRoute< - '/dashboard/users/user', - FileRoutesByPath['/dashboard/users/user']['parentRoute'], - FileRoutesByPath['/dashboard/users/user']['id'], - FileRoutesByPath['/dashboard/users/user']['path'], - FileRoutesByPath['/dashboard/users/user']['fullPath'] - > -} -declare module './routes/dashboard.invoices.index' { - const createFileRoute: CreateFileRoute< - '/dashboard/invoices/', - FileRoutesByPath['/dashboard/invoices/']['parentRoute'], - FileRoutesByPath['/dashboard/invoices/']['id'], - FileRoutesByPath['/dashboard/invoices/']['path'], - FileRoutesByPath['/dashboard/invoices/']['fullPath'] - > -} -declare module './routes/dashboard.users.index' { - const createFileRoute: CreateFileRoute< - '/dashboard/users/', - FileRoutesByPath['/dashboard/users/']['parentRoute'], - FileRoutesByPath['/dashboard/users/']['id'], - FileRoutesByPath['/dashboard/users/']['path'], - FileRoutesByPath['/dashboard/users/']['fullPath'] - > -} - // Create and export the route tree interface DashboardInvoicesRouteRouteChildren { From 4b1aaa451cd10f4cc2185826dc1cca15f1df5850 Mon Sep 17 00:00:00 2001 From: Birk Skyum <birk.skyum@pm.me> Date: Mon, 7 Apr 2025 14:15:59 +0200 Subject: [PATCH 7/9] path file route --- .../basic-solid-query-file-based/src/routes/__root.tsx | 2 +- .../src/routes/_auth.profile.tsx | 4 +++- .../src/routes/_auth.tsx | 4 ++-- .../src/routes/_pathlessLayout.route-a.tsx | 4 +++- .../src/routes/_pathlessLayout.route-b.tsx | 4 +++- .../src/routes/_pathlessLayout.tsx | 4 ++-- .../src/routes/dashboard.index.tsx | 3 ++- .../src/routes/dashboard.invoices.$invoiceId.tsx | 6 +++--- .../src/routes/dashboard.invoices.index.tsx | 3 ++- .../src/routes/dashboard.invoices.route.tsx | 8 ++++---- .../src/routes/dashboard.route.tsx | 4 ++-- .../src/routes/dashboard.users.index.tsx | 4 +++- .../src/routes/dashboard.users.route.tsx | 7 ++++--- .../src/routes/dashboard.users.user.tsx | 3 ++- .../src/routes/expensive/index.tsx | 3 ++- .../src/routes/foo/bar.tsx | 3 ++- .../src/routes/index.tsx | 4 ++-- .../src/routes/login.tsx | 4 ++-- 18 files changed, 44 insertions(+), 30 deletions(-) diff --git a/examples/solid/basic-solid-query-file-based/src/routes/__root.tsx b/examples/solid/basic-solid-query-file-based/src/routes/__root.tsx index 78fc2644c7..9eba2b6dfe 100644 --- a/examples/solid/basic-solid-query-file-based/src/routes/__root.tsx +++ b/examples/solid/basic-solid-query-file-based/src/routes/__root.tsx @@ -5,8 +5,8 @@ import { createRootRouteWithContext, } from '@tanstack/solid-router' import { SolidQueryDevtools } from '@tanstack/solid-query-devtools' -import type { QueryClient } from '@tanstack/solid-query' import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools' +import type { QueryClient } from '@tanstack/solid-query' export const Route = createRootRouteWithContext<{ queryClient: QueryClient diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.profile.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.profile.tsx index a248015704..fcb37f84cb 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.profile.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.profile.tsx @@ -1,4 +1,6 @@ -export const Route = createFileRoute({ +import { createFileRoute } from "@tanstack/solid-router" + +export const Route = createFileRoute('/_auth/profile')({ component: ProfileComponent, }) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.tsx index 6936f9e0ff..a5b98f8631 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.tsx @@ -1,7 +1,7 @@ -import { redirect } from '@tanstack/solid-router' +import { createFileRoute, redirect } from '@tanstack/solid-router' import { auth } from '../utils/auth' -export const Route = createFileRoute({ +export const Route = createFileRoute('/_auth')({ // Before loading, authenticate the user via our auth context // This will also happen during prefetching (e.g. hovering over links, etc) beforeLoad: ({ context, location }) => { diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-a.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-a.tsx index a0bd5240b7..f821d279bb 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-a.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-a.tsx @@ -1,4 +1,6 @@ -export const Route = createFileRoute({ +import { createFileRoute } from "@tanstack/solid-router" + +export const Route = createFileRoute('/_pathlessLayout/route-a')({ component: LayoutAComponent, }) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-b.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-b.tsx index 2864ec1f28..97b7879c57 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-b.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-b.tsx @@ -1,4 +1,6 @@ -export const Route = createFileRoute({ +import { createFileRoute } from "@tanstack/solid-router" + +export const Route = createFileRoute('/_pathlessLayout/route-b')({ component: LayoutBComponent, }) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.tsx index 1ae71fc856..7d1a8084c3 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.tsx @@ -1,6 +1,6 @@ -import { Outlet } from '@tanstack/solid-router' +import { Outlet, createFileRoute } from '@tanstack/solid-router' -export const Route = createFileRoute({ +export const Route = createFileRoute('/_pathlessLayout')({ component: LayoutComponent, }) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.index.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.index.tsx index fb74cd1f8c..a0985ab160 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.index.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.index.tsx @@ -1,7 +1,8 @@ import { useQuery } from '@tanstack/solid-query' +import { createFileRoute } from '@tanstack/solid-router' import { invoicesQueryOptions } from '../utils/queryOptions' -export const Route = createFileRoute({ +export const Route = createFileRoute('/dashboard/')({ loader: (opts) => opts.context.queryClient.ensureQueryData(invoicesQueryOptions()), component: DashboardIndexComponent, diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx index 27ced1eabe..2fe75cdfab 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.$invoiceId.tsx @@ -1,5 +1,5 @@ import * as Solid from 'solid-js' -import { Link, useNavigate } from '@tanstack/solid-router' +import { Link, createFileRoute, useNavigate } from '@tanstack/solid-router' import { useQuery } from '@tanstack/solid-query' import { z } from 'zod' import { InvoiceFields } from '../components/InvoiceFields' @@ -8,7 +8,7 @@ import { useUpdateInvoiceMutation, } from '../utils/queryOptions' -export const Route = createFileRoute({ +export const Route = createFileRoute('/dashboard/invoices/$invoiceId')({ params: { parse: (params) => ({ invoiceId: z.number().int().parse(Number(params.invoiceId)), @@ -64,7 +64,7 @@ function InvoiceComponent() { class="p-2 space-y-2" > <InvoiceFields - invoice={invoice!} + invoice={invoice} disabled={updateInvoiceMutation.status === 'pending'} /> <div> diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.index.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.index.tsx index 3cd88fe7fd..e3d3e66247 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.index.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.index.tsx @@ -1,9 +1,10 @@ +import { createFileRoute } from '@tanstack/solid-router' import { InvoiceFields } from '../components/InvoiceFields' import { Spinner } from '../components/Spinner' import { useCreateInvoiceMutation } from '../utils/queryOptions' import type { Invoice } from '../utils/mockTodos' -export const Route = createFileRoute({ +export const Route = createFileRoute('/dashboard/invoices/')({ component: InvoicesIndexComponent, }) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.route.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.route.tsx index 455a1ff644..a0c4be69f1 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.route.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.route.tsx @@ -1,16 +1,16 @@ -import { Link, MatchRoute, Outlet } from '@tanstack/solid-router' -import { createQuery } from '@tanstack/solid-query' +import { Link, MatchRoute, Outlet, createFileRoute } from '@tanstack/solid-router' +import { useQuery } from '@tanstack/solid-query' import { Spinner } from '../components/Spinner' import { invoicesQueryOptions } from '../utils/queryOptions' -export const Route = createFileRoute({ +export const Route = createFileRoute('/dashboard/invoices')({ loader: (opts) => opts.context.queryClient.ensureQueryData(invoicesQueryOptions()), component: InvoicesComponent, }) function InvoicesComponent() { - const invoicesQuery = createQuery(() => invoicesQueryOptions()) + const invoicesQuery = useQuery(() => invoicesQueryOptions()) const invoices = invoicesQuery.data return ( diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.route.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.route.tsx index 648050e69a..056483eb1a 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.route.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.route.tsx @@ -1,6 +1,6 @@ -import { Link, Outlet } from '@tanstack/solid-router' +import { Link, Outlet, createFileRoute } from '@tanstack/solid-router' -export const Route = createFileRoute({ +export const Route = createFileRoute('/dashboard')({ component: DashboardComponent, }) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.index.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.index.tsx index f5adc3afac..42e6b21e2c 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.index.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.index.tsx @@ -1,4 +1,6 @@ -export const Route = createFileRoute({ +import { createFileRoute } from "@tanstack/solid-router" + +export const Route = createFileRoute('/dashboard/users/')({ component: UsersIndexComponent, }) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.route.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.route.tsx index f0d5640214..704dea02b2 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.route.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.route.tsx @@ -4,17 +4,18 @@ import { Link, MatchRoute, Outlet, + createFileRoute, retainSearchParams, useNavigate, } from '@tanstack/solid-router' -import { createQuery } from '@tanstack/solid-query' +import { useQuery } from '@tanstack/solid-query' import { z } from 'zod' import { Spinner } from '../components/Spinner' import { usersQueryOptions } from '../utils/queryOptions' type UsersViewSortBy = 'name' | 'id' | 'email' -export const Route = createFileRoute({ +export const Route = createFileRoute('/dashboard/users')({ validateSearch: z.object({ usersView: z .object({ @@ -36,7 +37,7 @@ export const Route = createFileRoute({ function UsersComponent() { const navigate = useNavigate({ from: Route.fullPath }) const search = Route.useSearch() - const usersQuery = createQuery(() => usersQueryOptions(Route.useLoaderDeps())) + const usersQuery = useQuery(() => usersQueryOptions(Route.useLoaderDeps())) const users = usersQuery.data const sortBy = search().usersView?.sortBy ?? 'name' const filterBy = search().usersView?.filterBy diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.user.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.user.tsx index 6b8380842d..26e51474ee 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.user.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.user.tsx @@ -1,8 +1,9 @@ import { useQuery } from '@tanstack/solid-query' import { z } from 'zod' +import { createFileRoute } from '@tanstack/solid-router' import { userQueryOptions } from '../utils/queryOptions' -export const Route = createFileRoute({ +export const Route = createFileRoute('/dashboard/users/user')({ validateSearch: z.object({ userId: z.number(), }), diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/expensive/index.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/expensive/index.tsx index 5066d67ac5..f1ae88b32b 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/expensive/index.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/expensive/index.tsx @@ -1,5 +1,6 @@ +import { createFileRoute } from '@tanstack/solid-router' import Expensive from './-components/Expensive' -export const Route = createFileRoute({ +export const Route = createFileRoute('/expensive/')({ component: Expensive, }) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/foo/bar.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/foo/bar.tsx index c91111016b..05d7f0f2d2 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/foo/bar.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/foo/bar.tsx @@ -1,6 +1,7 @@ +import { createFileRoute } from '@tanstack/solid-router' import { z } from 'zod' -export const Route = createFileRoute({ +export const Route = createFileRoute('/foo/bar')({ component: () => <div>{JSON.stringify(Route.useSearch())}</div>, validateSearch: z.object({ asdf: z.string() }), }) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/index.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/index.tsx index a5b44c1bb6..eed9991668 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/index.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/index.tsx @@ -1,6 +1,6 @@ -import { Link } from '@tanstack/solid-router' +import { Link, createFileRoute } from '@tanstack/solid-router' -export const Route = createFileRoute({ +export const Route = createFileRoute('/')({ component: IndexComponent, }) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/login.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/login.tsx index f6002a7ef6..b1e8dddd82 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/login.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/login.tsx @@ -1,8 +1,8 @@ import * as Solid from 'solid-js' -import { useRouter } from '@tanstack/solid-router' +import { createFileRoute, useRouter } from '@tanstack/solid-router' import { z } from 'zod' -export const Route = createFileRoute({ +export const Route = createFileRoute('/login')({ validateSearch: z.object({ redirect: z.string().optional(), }), From 84e17b4d9eff8ea6eec1c77be3e805597926b6f4 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 12:17:15 +0000 Subject: [PATCH 8/9] ci: apply automated fixes --- .../src/routes/_auth.profile.tsx | 2 +- .../src/routes/_pathlessLayout.route-a.tsx | 2 +- .../src/routes/_pathlessLayout.route-b.tsx | 2 +- .../src/routes/dashboard.invoices.route.tsx | 7 ++++++- .../src/routes/dashboard.users.index.tsx | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.profile.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.profile.tsx index fcb37f84cb..c72820435f 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.profile.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_auth.profile.tsx @@ -1,4 +1,4 @@ -import { createFileRoute } from "@tanstack/solid-router" +import { createFileRoute } from '@tanstack/solid-router' export const Route = createFileRoute('/_auth/profile')({ component: ProfileComponent, diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-a.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-a.tsx index f821d279bb..c1c392cbad 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-a.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-a.tsx @@ -1,4 +1,4 @@ -import { createFileRoute } from "@tanstack/solid-router" +import { createFileRoute } from '@tanstack/solid-router' export const Route = createFileRoute('/_pathlessLayout/route-a')({ component: LayoutAComponent, diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-b.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-b.tsx index 97b7879c57..30dd035c93 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-b.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/_pathlessLayout.route-b.tsx @@ -1,4 +1,4 @@ -import { createFileRoute } from "@tanstack/solid-router" +import { createFileRoute } from '@tanstack/solid-router' export const Route = createFileRoute('/_pathlessLayout/route-b')({ component: LayoutBComponent, diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.route.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.route.tsx index a0c4be69f1..705f94adac 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.route.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.invoices.route.tsx @@ -1,4 +1,9 @@ -import { Link, MatchRoute, Outlet, createFileRoute } from '@tanstack/solid-router' +import { + Link, + MatchRoute, + Outlet, + createFileRoute, +} from '@tanstack/solid-router' import { useQuery } from '@tanstack/solid-query' import { Spinner } from '../components/Spinner' import { invoicesQueryOptions } from '../utils/queryOptions' diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.index.tsx b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.index.tsx index 42e6b21e2c..75ca9dbf93 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.index.tsx +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/routes/dashboard.users.index.tsx @@ -1,4 +1,4 @@ -import { createFileRoute } from "@tanstack/solid-router" +import { createFileRoute } from '@tanstack/solid-router' export const Route = createFileRoute('/dashboard/users/')({ component: UsersIndexComponent, From 37bc3c054a74ceb5a5a1c34dc41e294bbf1502f8 Mon Sep 17 00:00:00 2001 From: Birk Skyum <birk.skyum@pm.me> Date: Mon, 7 Apr 2025 14:18:58 +0200 Subject: [PATCH 9/9] fix import order --- .../src/utils/queryOptions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/solid/kitchen-sink-solid-query-file-based/src/utils/queryOptions.ts b/examples/solid/kitchen-sink-solid-query-file-based/src/utils/queryOptions.ts index 19d2ba78f3..78150ea272 100644 --- a/examples/solid/kitchen-sink-solid-query-file-based/src/utils/queryOptions.ts +++ b/examples/solid/kitchen-sink-solid-query-file-based/src/utils/queryOptions.ts @@ -1,4 +1,4 @@ -import { createMutation, queryOptions } from '@tanstack/solid-query' +import { queryOptions, useMutation } from '@tanstack/solid-query' import { queryClient } from '../main' import { fetchInvoiceById, @@ -37,14 +37,14 @@ export const userQueryOptions = (userId: number) => }) export const useCreateInvoiceMutation = () => { - return createMutation(() => ({ + return useMutation(() => ({ mutationFn: postInvoice, onSuccess: () => queryClient.invalidateQueries(), })) } export const useUpdateInvoiceMutation = (invoiceId: number) => { - return createMutation(() => ({ + return useMutation(() => ({ mutationFn: patchInvoice, onSuccess: () => queryClient.invalidateQueries(), gcTime: 1000 * 10,