Skip to content

Commit be5cd9d

Browse files
authored
feat: Add ensureQueryData support (#150)
1 parent 58a6089 commit be5cd9d

20 files changed

+8596
-4604
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
## Features
88

99
- Generates custom react hooks that use React Query's `useQuery`, `useSuspenseQuery`, `useMutation` and `useInfiniteQuery` hooks
10+
- Generates custom functions that use React Query's `ensureQueryData` and `prefetchQuery` functions
1011
- Generates query keys and functions for query caching
1112
- Generates pure TypeScript clients generated by [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts)
1213

@@ -79,9 +80,10 @@ $ openapi-rq -i ./petstore.yaml
7980
- queries
8081
- index.ts <- main file that exports common types, variables, and queries. Does not export suspense or prefetch hooks
8182
- common.ts <- common types
83+
- ensureQueryData.ts <- generated ensureQueryData functions
8284
- queries.ts <- generated query hooks
8385
- suspenses.ts <- generated suspense hooks
84-
- prefetch.ts <- generated prefetch hooks learn more about prefetching in in link below
86+
- prefetch.ts <- generated prefetch functions learn more about prefetching in in link below
8587
- requests <- output code generated by @hey-api/openapi-ts
8688
```
8789

biome.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
"coverage",
1111
"examples/nextjs-app/openapi",
1212
"examples/nextjs-app/.next",
13+
"examples/tanstack-router-app/openapi",
14+
"examples/tanstack-router-app/src/routeTree.gen.ts",
1315
".vscode"
1416
]
1517
},
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Local
2+
.DS_Store
3+
*.local
4+
*.log*
5+
6+
# Dist
7+
node_modules
8+
dist/
9+
.vinxi
10+
.output
11+
.vercel
12+
.netlify
13+
.wrangler
14+
15+
# IDE
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Vite App</title>
7+
<script src="https://cdn.tailwindcss.com"></script>
8+
<style type="text/tailwindcss">
9+
html {
10+
color-scheme: light dark;
11+
}
12+
* {
13+
@apply border-gray-200 dark:border-gray-800;
14+
}
15+
body {
16+
@apply bg-gray-50 text-gray-950 dark:bg-gray-900 dark:text-gray-200;
17+
}
18+
</style>
19+
</head>
20+
<body>
21+
<div id="app"></div>
22+
<script type="module" src="/src/main.tsx"></script>
23+
</body>
24+
</html>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "tanstack-router-app",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"dev": "run-p dev:mock dev:tanstack",
8+
"dev:mock": "prism mock ../petstore.yaml --dynamic",
9+
"typecheck": "tsc --noEmit",
10+
"dev:tanstack": "vite --port=3001",
11+
"build": "vite build",
12+
"serve": "vite preview",
13+
"start": "vite",
14+
"generate:api": "rimraf ./openapi && node ../../dist/cli.mjs -i ../petstore.yaml -c axios --request ./request.ts --format=biome --lint=biome"
15+
},
16+
"devDependencies": {
17+
"@stoplight/prism-cli": "^5.5.2",
18+
"@tanstack/router-plugin": "^1.58.4",
19+
"@types/react": "^18.3.3",
20+
"@types/react-dom": "^18.3.0",
21+
"@vitejs/plugin-react": "^4.3.1",
22+
"npm-run-all": "^4.1.5",
23+
"vite": "^5.4.4"
24+
},
25+
"dependencies": {
26+
"@tanstack/react-query": "^5.32.1",
27+
"@tanstack/react-query-devtools": "^5.32.1",
28+
"@tanstack/react-router": "^1.58.7",
29+
"@tanstack/react-router-with-query": "^1.58.7",
30+
"@tanstack/router-devtools": "^1.58.7",
31+
"@tanstack/start": "^1.58.7",
32+
"axios": "^1.6.7",
33+
"react": "^18.3.1",
34+
"react-dom": "^18.3.1"
35+
}
36+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import axios from "axios";
2+
import type { RawAxiosRequestHeaders } from "axios";
3+
4+
import type { ApiRequestOptions } from "./ApiRequestOptions";
5+
import { CancelablePromise } from "./CancelablePromise";
6+
import type { OpenAPIConfig } from "./OpenAPI";
7+
8+
// Optional: Get and link the cancelation token, so the request can be aborted.
9+
const source = axios.CancelToken.source();
10+
11+
const axiosInstance = axios.create({
12+
// Your custom Axios instance config
13+
baseURL: "http://localhost:4010",
14+
headers: {
15+
// Your custom headers
16+
} satisfies RawAxiosRequestHeaders,
17+
});
18+
19+
// Add a request interceptor
20+
axiosInstance.interceptors.request.use(
21+
(config) => {
22+
// Do something before request is sent
23+
if (!config.url || !config.params) {
24+
return config;
25+
}
26+
27+
for (const [key, value] of Object.entries<string>(config.params)) {
28+
const stringToSearch = `{${key}}`;
29+
if (
30+
config.url !== undefined &&
31+
config.url.search(stringToSearch) !== -1
32+
) {
33+
config.url = config.url.replace(`{${key}}`, encodeURIComponent(value));
34+
delete config.params[key];
35+
}
36+
}
37+
38+
return config;
39+
},
40+
(error) => {
41+
// Do something with request error
42+
return Promise.reject(error);
43+
},
44+
);
45+
46+
// Add a response interceptor
47+
axiosInstance.interceptors.response.use(
48+
(response) => {
49+
// Any status code that lie within the range of 2xx cause this function to trigger
50+
// Do something with response data
51+
return response;
52+
},
53+
(error) => {
54+
// Any status codes that falls outside the range of 2xx cause this function to trigger
55+
// Do something with response error
56+
return Promise.reject(error);
57+
},
58+
);
59+
60+
export const request = <T>(
61+
config: OpenAPIConfig,
62+
options: ApiRequestOptions,
63+
): CancelablePromise<T> => {
64+
return new CancelablePromise((resolve, reject, onCancel) => {
65+
onCancel(() => source.cancel("The user aborted a request."));
66+
67+
let formattedHeaders = options.headers as RawAxiosRequestHeaders;
68+
if (options.mediaType) {
69+
formattedHeaders = {
70+
...options.headers,
71+
"Content-Type": options.mediaType,
72+
} satisfies RawAxiosRequestHeaders;
73+
}
74+
75+
return axiosInstance
76+
.request({
77+
url: options.url,
78+
data: options.body,
79+
method: options.method,
80+
params: {
81+
...options.query,
82+
...options.path,
83+
},
84+
headers: formattedHeaders,
85+
cancelToken: source.token,
86+
})
87+
.then((res) => {
88+
resolve(res.data);
89+
})
90+
.catch((error) => {
91+
reject(error);
92+
});
93+
});
94+
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2+
import { RouterProvider, createRouter } from "@tanstack/react-router";
3+
import React from "react";
4+
import ReactDOM from "react-dom/client";
5+
import { routeTree } from "./routeTree.gen";
6+
7+
const queryClient = new QueryClient({
8+
defaultOptions: {
9+
queries: {
10+
staleTime: 60 * 1000,
11+
},
12+
},
13+
});
14+
15+
// Set up a Router instance
16+
const router = createRouter({
17+
routeTree,
18+
defaultPreload: "intent",
19+
// Since we're using React Query, we don't want loader calls to ever be stale
20+
// This will ensure that the loader is always called when the route is preloaded or visited
21+
defaultPreloadStaleTime: 0,
22+
context: {
23+
queryClient,
24+
},
25+
});
26+
27+
// Register things for typesafety
28+
declare module "@tanstack/react-router" {
29+
interface Register {
30+
router: typeof router;
31+
}
32+
}
33+
34+
// biome-ignore lint/style/noNonNullAssertion: This is a demo app
35+
const rootElement = document.getElementById("app")!;
36+
37+
if (!rootElement.innerHTML) {
38+
const root = ReactDOM.createRoot(rootElement);
39+
root.render(
40+
<QueryClientProvider client={queryClient}>
41+
<RouterProvider router={router} />
42+
</QueryClientProvider>,
43+
);
44+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/* prettier-ignore-start */
2+
3+
/* eslint-disable */
4+
5+
// @ts-nocheck
6+
7+
// noinspection JSUnusedGlobalSymbols
8+
9+
// This file is auto-generated by TanStack Router
10+
11+
// Import Routes
12+
13+
import { Route as rootRoute } from "./routes/__root";
14+
import { Route as AboutImport } from "./routes/about";
15+
import { Route as IndexImport } from "./routes/index";
16+
17+
// Create/Update Routes
18+
19+
const AboutRoute = AboutImport.update({
20+
path: "/about",
21+
getParentRoute: () => rootRoute,
22+
} as any);
23+
24+
const IndexRoute = IndexImport.update({
25+
path: "/",
26+
getParentRoute: () => rootRoute,
27+
} as any);
28+
29+
// Populate the FileRoutesByPath interface
30+
31+
declare module "@tanstack/react-router" {
32+
interface FileRoutesByPath {
33+
"/": {
34+
id: "/";
35+
path: "/";
36+
fullPath: "/";
37+
preLoaderRoute: typeof IndexImport;
38+
parentRoute: typeof rootRoute;
39+
};
40+
"/about": {
41+
id: "/about";
42+
path: "/about";
43+
fullPath: "/about";
44+
preLoaderRoute: typeof AboutImport;
45+
parentRoute: typeof rootRoute;
46+
};
47+
}
48+
}
49+
50+
// Create and export the route tree
51+
52+
export interface FileRoutesByFullPath {
53+
"/": typeof IndexRoute;
54+
"/about": typeof AboutRoute;
55+
}
56+
57+
export interface FileRoutesByTo {
58+
"/": typeof IndexRoute;
59+
"/about": typeof AboutRoute;
60+
}
61+
62+
export interface FileRoutesById {
63+
__root__: typeof rootRoute;
64+
"/": typeof IndexRoute;
65+
"/about": typeof AboutRoute;
66+
}
67+
68+
export interface FileRouteTypes {
69+
fileRoutesByFullPath: FileRoutesByFullPath;
70+
fullPaths: "/" | "/about";
71+
fileRoutesByTo: FileRoutesByTo;
72+
to: "/" | "/about";
73+
id: "__root__" | "/" | "/about";
74+
fileRoutesById: FileRoutesById;
75+
}
76+
77+
export interface RootRouteChildren {
78+
IndexRoute: typeof IndexRoute;
79+
AboutRoute: typeof AboutRoute;
80+
}
81+
82+
const rootRouteChildren: RootRouteChildren = {
83+
IndexRoute: IndexRoute,
84+
AboutRoute: AboutRoute,
85+
};
86+
87+
export const routeTree = rootRoute
88+
._addFileChildren(rootRouteChildren)
89+
._addFileTypes<FileRouteTypes>();
90+
91+
/* prettier-ignore-end */
92+
93+
/* ROUTE_MANIFEST_START
94+
{
95+
"routes": {
96+
"__root__": {
97+
"filePath": "__root.tsx",
98+
"children": [
99+
"/",
100+
"/about"
101+
]
102+
},
103+
"/": {
104+
"filePath": "index.tsx"
105+
},
106+
"/about": {
107+
"filePath": "about.tsx"
108+
}
109+
}
110+
}
111+
ROUTE_MANIFEST_END */

0 commit comments

Comments
 (0)