pjdev2d-cli 1.1.2 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/tanstack-query/api-base.ts +68 -0
- package/templates/tanstack-query/api-queries.ts +274 -0
- package/templates/tanstack-query/{services/client.ts → client.ts} +8 -30
- package/templates/tanstack-query/{services/module → module}/index.ts +12 -12
- package/templates/tanstack-query/{services/module → module}/keys.ts +17 -13
- package/templates/tanstack-query/{services/module → module}/mappers.ts +15 -11
- package/templates/tanstack-query/module/mutations.ts +55 -0
- package/templates/tanstack-query/module/queries.ts +133 -0
- package/templates/tanstack-query/module/services.ts +66 -0
- package/templates/tanstack-query/route/index.tsx +0 -0
- package/templates/tanstack-query/services/base-query.ts +0 -0
- package/templates/tanstack-query/services/module/mutations.ts +0 -25
- package/templates/tanstack-query/services/module/queries.ts +0 -27
- package/templates/tanstack-query/services/module/services.ts +0 -18
- package/templates/tanstack-query/services/queries.ts +0 -67
- /package/templates/tanstack-query/{services/mutations.ts → api-mutations.ts} +0 -0
package/package.json
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
#ANCHOR : TYPE:1 VITE PROJECT
|
|
3
|
+
const BASE_URL = import.meta.env.VITE_API_URL;
|
|
4
|
+
|
|
5
|
+
Environment variables must start with VITE_ to be exposed to client-side code in Vite.
|
|
6
|
+
|
|
7
|
+
---------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
#ANCHOR : TYPE:2 NEXT.JS / NON-VITE PROJECTS
|
|
10
|
+
const BASE_URL = process.env.NEXT_PUBLIC_API_URL;
|
|
11
|
+
|
|
12
|
+
Environment variables must start with NEXT_PUBLIC_ to be available in Next.js browser code.
|
|
13
|
+
|
|
14
|
+
---------------------------------------------------------
|
|
15
|
+
#ANCHOR
|
|
16
|
+
Vite doesn't automatically provide Node's process.env in browser code.
|
|
17
|
+
|
|
18
|
+
| Framework | Access Method | Public Prefix |
|
|
19
|
+
| --------------- | ------------------- | ---------------- |
|
|
20
|
+
| Next.js | `process.env.X` | `NEXT_PUBLIC_` |
|
|
21
|
+
| Vite | `import.meta.env.X` | `VITE_` |
|
|
22
|
+
| Node.js Backend | `process.env.X` | No prefix needed |
|
|
23
|
+
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export const API_CONFIG = {
|
|
27
|
+
// NOTE: Change this BASE_URL to match your actual API endpoint
|
|
28
|
+
baseUrl: "http://localhost:5000/api",
|
|
29
|
+
timeout: 10000,
|
|
30
|
+
headers: {
|
|
31
|
+
"Accept": "application/json",
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
},
|
|
34
|
+
} as const;
|
|
35
|
+
|
|
36
|
+
export const QUERY_CLIENT_CONFIG = {
|
|
37
|
+
defaultOptions: {
|
|
38
|
+
queries: {
|
|
39
|
+
// # NOTE: Global TanStack Query configurations
|
|
40
|
+
retry: 3, // Automatically retries failed requests 3 times on failure
|
|
41
|
+
refetchOnWindowFocus: false, // Refetches stale active queries when the browser tab gets focused
|
|
42
|
+
staleTime: 1000 * 60 * 5, // Data is considered fresh for 5 minutes (prevents duplicate requests)
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
# NOTE: HOW AND WHERE TO USE QUERY_CLIENT_CONFIG
|
|
49
|
+
|
|
50
|
+
This configuration is imported and passed when initializing the QueryClient at the root of your application (e.g., in App.tsx, main.tsx, or layout.tsx):
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
54
|
+
import { QUERY_CLIENT_CONFIG } from "./api-base";
|
|
55
|
+
|
|
56
|
+
const queryClient = new QueryClient(QUERY_CLIENT_CONFIG);
|
|
57
|
+
|
|
58
|
+
export default function Providers({ children }) {
|
|
59
|
+
return (
|
|
60
|
+
<QueryClientProvider client={queryClient}>
|
|
61
|
+
{children}
|
|
62
|
+
</QueryClientProvider>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { ModuleQueries } from "./module";
|
|
2
|
+
|
|
3
|
+
export const apiQueries = {
|
|
4
|
+
module: ModuleQueries,
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export * from "./module";
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
# NOTE : here you export all the queries from the module folder so it will be easy to import in components
|
|
11
|
+
|
|
12
|
+
const { data: name } = useQuery(ModuleQueries.fetch_query_name_example());
|
|
13
|
+
const { data: name } = useSuspenseQuery(ModuleQueries.fetch_query_name_example());
|
|
14
|
+
|
|
15
|
+
// Example with parameters & request cancellation:
|
|
16
|
+
// const { data } = useQuery(ModuleQueries.fetch_query_with_params_example({ search: 'query', filter: 'active' }));
|
|
17
|
+
|
|
18
|
+
---------------------------------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
| Feature | `useQuery` | `useSuspenseQuery` |
|
|
21
|
+
| ------------------- | ---------------------------------------------- | --------------------------------------------------- |
|
|
22
|
+
| Loading state | Returns `isLoading`, `isPending`, `isFetching` | Does **not** return loading state |
|
|
23
|
+
| Data type | `data` can be `undefined` | `data` is guaranteed to exist when rendered |
|
|
24
|
+
| Error handling | Use `error` from hook | Error is caught by React Error Boundary |
|
|
25
|
+
| Loading UI | Handle manually (`if (isLoading)`) | Handled by React `<Suspense>` fallback |
|
|
26
|
+
| Component rendering | Renders immediately, then fetches | Suspends rendering until data is ready |
|
|
27
|
+
| Setup complexity | Simpler | Requires `<Suspense>` and usually an Error Boundary |
|
|
28
|
+
| Best for | Most applications | Apps fully using React Suspense |
|
|
29
|
+
|
|
30
|
+
----------------------------------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
# NOTE : Example for useQuery
|
|
33
|
+
const ModuleComponent = () => {
|
|
34
|
+
|
|
35
|
+
const { data, isLoading, error } = useQuery(
|
|
36
|
+
ModuleQueries.fetch_query_name_example()
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
if (isLoading) return <FallBackComponent />;
|
|
40
|
+
if (error) return <ErrorComponent />;
|
|
41
|
+
|
|
42
|
+
return <ModuleComponent data={data} />;
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
-----------------------------------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
# NOTE : Example for useSuspenseQuery
|
|
49
|
+
|
|
50
|
+
const ModuleComponent = () => {
|
|
51
|
+
|
|
52
|
+
const { data } = useSuspenseQuery(
|
|
53
|
+
ModuleQueries.fetch_query_name_example()
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return <ModuleComponent data={data} />;
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# NOTE : Wrapping
|
|
61
|
+
<Suspense fallback={<FallBackComponent />}>
|
|
62
|
+
<ModuleComponent />
|
|
63
|
+
</Suspense>
|
|
64
|
+
|
|
65
|
+
---------------------------------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
Use useQuery when you want to manage loading and errors inside the component.
|
|
68
|
+
Use useSuspenseQuery when your app already uses React Suspense and you want cleaner components with guaranteed data.
|
|
69
|
+
|
|
70
|
+
---------------------------------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
# NOTE : Example for useInfiniteQuery (Infinite Scrolling)
|
|
73
|
+
|
|
74
|
+
1. In queries.ts, define the option:
|
|
75
|
+
export const ModuleQueries = {
|
|
76
|
+
fetch_infinite_query_example: () =>
|
|
77
|
+
infiniteQueryOptions({
|
|
78
|
+
queryKey: [...ModuleKeys.module(), "infinite"] as const,
|
|
79
|
+
queryFn: async ({ pageParam = 1 }) => {
|
|
80
|
+
const raw = await ModuleService.get_infinite_query_example(pageParam as number);
|
|
81
|
+
return raw as { data: any[]; meta: { current_page: number; last_page: number } };
|
|
82
|
+
},
|
|
83
|
+
getNextPageParam: (raw) => {
|
|
84
|
+
// E.g., if page-based (extracting from 'meta' object):
|
|
85
|
+
const { current_page, last_page } = raw?.meta || {};
|
|
86
|
+
return current_page < last_page ? current_page + 1 : undefined;
|
|
87
|
+
},
|
|
88
|
+
initialPageParam: 1,
|
|
89
|
+
select: (raw) => ({
|
|
90
|
+
pages: raw.pages.map((pageData: any) => ({
|
|
91
|
+
...pageData,
|
|
92
|
+
data: ModuleMappers.fetch_infinite_query_example(pageData.data),
|
|
93
|
+
})),
|
|
94
|
+
pageParams: raw.pageParams,
|
|
95
|
+
}),
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
2. In your Component:
|
|
100
|
+
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
101
|
+
import { useEffect } from "react";
|
|
102
|
+
import { useInView } from "react-intersection-observer"; // optional, or use standard scroll listener
|
|
103
|
+
import { ModuleQueries } from "./queries";
|
|
104
|
+
|
|
105
|
+
const InfiniteListComponent = () => {
|
|
106
|
+
const {
|
|
107
|
+
data,
|
|
108
|
+
fetchNextPage,
|
|
109
|
+
hasNextPage,
|
|
110
|
+
isFetchingNextPage,
|
|
111
|
+
isLoading,
|
|
112
|
+
error
|
|
113
|
+
} = useInfiniteQuery(ModuleQueries.fetch_infinite_query_example());
|
|
114
|
+
|
|
115
|
+
// Optional hook to detect if target element is visible in the viewport
|
|
116
|
+
const { ref, inView } = useInView();
|
|
117
|
+
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (inView && hasNextPage && !isFetchingNextPage) {
|
|
120
|
+
fetchNextPage();
|
|
121
|
+
}
|
|
122
|
+
}, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]);
|
|
123
|
+
|
|
124
|
+
if (isLoading) return <Loading />;
|
|
125
|
+
if (error) return <Error />;
|
|
126
|
+
|
|
127
|
+
// Flatten the paginated data pages into a single flat array
|
|
128
|
+
const items = data ? data.pages.flatMap((page) => page.data) : [];
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<div>
|
|
132
|
+
<ul>
|
|
133
|
+
{items.map((item) => (
|
|
134
|
+
<li key={item.id}>{item.name}</li>
|
|
135
|
+
))}
|
|
136
|
+
</ul>
|
|
137
|
+
|
|
138
|
+
// This invisible boundary element triggers loading the next page when scrolled into view
|
|
139
|
+
<div ref={ref} style={{ height: "10px" }}>
|
|
140
|
+
{isFetchingNextPage ? "Loading more..." : ""}
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
---------------------------------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
# HOW INFINITE SCROLLING WORKS:
|
|
149
|
+
1. **Initial Load:** `useInfiniteQuery` calls `queryFn` using `initialPageParam` (usually page `1`). The response is saved in `data.pages[0]`.
|
|
150
|
+
2. **Next Page Calculation:** `getNextPageParam` is called with the last fetched page data. It checks if there is more data (e.g., if `current_page < last_page`). If returned, `hasNextPage` becomes `true`.
|
|
151
|
+
3. **Scroll Trigger:** When the user scrolls down, an observer (like `react-intersection-observer` on the bottom `div` element) fires an `inView` event.
|
|
152
|
+
4. **Fetch Call:** If `inView`, `hasNextPage`, and not currently loading, we call `fetchNextPage()`.
|
|
153
|
+
5. **Caching & Appending:** TanStack Query triggers `queryFn` passing the next page number (e.g., `2`) as the new `pageParam`. The new data is fetched and appended as a new array element inside `data.pages` (e.g., `data.pages[1]`). The UI re-renders with the flattened items list.
|
|
154
|
+
|
|
155
|
+
---------------------------------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
# NOTE : Example for Query Parameters (Filters & Cancellation)
|
|
158
|
+
|
|
159
|
+
This example shows a component using query parameters (including an array of brands) with automatic request cancellation:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { useState } from "react";
|
|
163
|
+
import { useQuery } from "@tanstack/react-query";
|
|
164
|
+
import { ModuleQueries } from "./queries";
|
|
165
|
+
import { useDebounce } from "@/hooks/use-debounce"; // custom debounce hook
|
|
166
|
+
|
|
167
|
+
const FilteredProductList = () => {
|
|
168
|
+
const [search, setSearch] = useState("");
|
|
169
|
+
const [selectedBrands, setSelectedBrands] = useState<string[]>([]); // e.g., ["samsung", "apple"]
|
|
170
|
+
const [sortBy, setSortBy] = useState("price_asc"); // Single key-value parameter example
|
|
171
|
+
|
|
172
|
+
// 1. Debounce fast inputs (like keystrokes) to prevent hammering the server
|
|
173
|
+
const debouncedSearch = useDebounce(search, 300);
|
|
174
|
+
|
|
175
|
+
// 2. Combine your states. Any change to these properties will update the queryKey
|
|
176
|
+
const filters = {
|
|
177
|
+
search: debouncedSearch,
|
|
178
|
+
brands: selectedBrands, // Array will be serialized as "brands=samsung&brands=apple" by the service
|
|
179
|
+
sort: sortBy, // Single key-value parameter (e.g., "price_asc")
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// 3. Pass the filter object directly to the query option builder
|
|
183
|
+
const { data, isLoading, isFetching, error } = useQuery(
|
|
184
|
+
ModuleQueries.fetch_query_with_params_example(filters)
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const toggleBrand = (brand: string) => {
|
|
188
|
+
setSelectedBrands(prev =>
|
|
189
|
+
prev.includes(brand) ? prev.filter(b => b !== brand) : [...prev, brand]
|
|
190
|
+
);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<div>
|
|
195
|
+
<input
|
|
196
|
+
type="text"
|
|
197
|
+
value={search}
|
|
198
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
199
|
+
placeholder="Search mobiles..."
|
|
200
|
+
/>
|
|
201
|
+
|
|
202
|
+
<select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
|
|
203
|
+
<option value="price_asc">Price: Low to High</option>
|
|
204
|
+
<option value="price_desc">Price: High to Low</option>
|
|
205
|
+
</select>
|
|
206
|
+
|
|
207
|
+
<div>
|
|
208
|
+
<label>
|
|
209
|
+
<input
|
|
210
|
+
type="checkbox"
|
|
211
|
+
checked={selectedBrands.includes("samsung")}
|
|
212
|
+
onChange={() => toggleBrand("samsung")}
|
|
213
|
+
/> Samsung
|
|
214
|
+
</label>
|
|
215
|
+
<label>
|
|
216
|
+
<input
|
|
217
|
+
type="checkbox"
|
|
218
|
+
checked={selectedBrands.includes("apple")}
|
|
219
|
+
onChange={() => toggleBrand("apple")}
|
|
220
|
+
/> Apple
|
|
221
|
+
</label>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
{isFetching && <span>Updating results (Old requests cancelled automatically)...</span>}
|
|
225
|
+
|
|
226
|
+
{isLoading ? (
|
|
227
|
+
<p>Loading...</p>
|
|
228
|
+
) : error ? (
|
|
229
|
+
<p>Error loading items</p>
|
|
230
|
+
) : (
|
|
231
|
+
<ul>
|
|
232
|
+
{data?.map((item: any) => (
|
|
233
|
+
<li key={item.id}>{item.name}</li>
|
|
234
|
+
))}
|
|
235
|
+
</ul>
|
|
236
|
+
)}
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
};
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---------------------------------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
# NOTE: WHY WE DO NOT GET DUPLICATE KEY ERRORS IN FRONTEND STATE
|
|
245
|
+
Even though the final URL repeats the same key (e.g., `?brands=samsung&brands=apple`),
|
|
246
|
+
your frontend React state or router object only has one clean unique key mapping to an array:
|
|
247
|
+
`const filters = { brands: ["samsung", "apple"] };`
|
|
248
|
+
|
|
249
|
+
The service layer safely iterates over the array and appends the items individually to the query parameters (`queryParams.append`).
|
|
250
|
+
This keeps your frontend state easy to manage without causing duplicate key errors.
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
useEffect(() => {
|
|
258
|
+
// 1. Get query string from URL (or SessionStorage if URL is empty)
|
|
259
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
260
|
+
// 2. Parse the single values
|
|
261
|
+
const searchVal = searchParams.get("search") || "";
|
|
262
|
+
const sortVal = searchParams.get("sort") || "price_asc";
|
|
263
|
+
// 3. Parse the repeated values as an array
|
|
264
|
+
const brandsVal = searchParams.getAll("brands"); // returns ["samsung", "apple"]
|
|
265
|
+
// 4. Populate your component state
|
|
266
|
+
setSearch(searchVal);
|
|
267
|
+
setSortBy(sortVal);
|
|
268
|
+
setSelectedBrands(brandsVal);
|
|
269
|
+
}, []);
|
|
270
|
+
|
|
271
|
+
*/
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
|
@@ -1,41 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
#ANCHOR : TYPE:1 VITE PROJECT
|
|
3
|
-
const BASE_URL = process.env.NEXT_PUBLIC_API_URL;
|
|
4
|
-
|
|
5
|
-
Uses import.meta.env
|
|
6
|
-
Environment variables must start with VITE_ to be exposed to client-side code.
|
|
7
|
-
|
|
8
|
-
---------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
#ANCHOR : TYPE:2 NON-VITE PROJECTS
|
|
11
|
-
const BASE_URL = import.meta.env.VITE_API_URL;
|
|
12
|
-
|
|
13
|
-
Uses process.env
|
|
14
|
-
Environment variables must start with NEXT_PUBLIC_ to be available in the browser.
|
|
15
|
-
|
|
16
|
-
---------------------------------------------------------
|
|
17
|
-
#ANCHOR
|
|
18
|
-
Vite doesn't automatically provide Node's process.env in browser code.
|
|
19
|
-
|
|
20
|
-
| Framework | Access Method | Public Prefix |
|
|
21
|
-
| --------------- | ------------------- | ---------------- |
|
|
22
|
-
| Next.js | `process.env.X` | `NEXT_PUBLIC_` |
|
|
23
|
-
| Vite | `import.meta.env.X` | `VITE_` |
|
|
24
|
-
| Node.js Backend | `process.env.X` | No prefix needed |
|
|
25
|
-
|
|
26
|
-
*/
|
|
27
|
-
// NOTE BASE_URL to change with actual api
|
|
28
|
-
const BASE_URL = "http://localhost:5000/api";
|
|
1
|
+
import { API_CONFIG } from "./api-base";
|
|
29
2
|
|
|
30
3
|
//NOTE: apiRequest: This is the core function that all other methods use.
|
|
31
4
|
export async function apiRequest<T>(
|
|
32
5
|
endpoint: string,
|
|
33
6
|
options: RequestInit = {},
|
|
34
7
|
) {
|
|
35
|
-
|
|
8
|
+
// # NOTE: Retrieve your authentication token dynamically (e.g., from localStorage or cookies)
|
|
9
|
+
// const token = typeof window !== "undefined" ? localStorage.getItem("token") : null;
|
|
10
|
+
const token = null;
|
|
11
|
+
|
|
12
|
+
const response = await fetch(`${API_CONFIG.baseUrl}${endpoint}`, {
|
|
36
13
|
...options,
|
|
37
14
|
headers: {
|
|
38
|
-
|
|
15
|
+
...API_CONFIG.headers,
|
|
16
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
39
17
|
...options.headers,
|
|
40
18
|
},
|
|
41
19
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export { ModuleKeys } from "./keys";
|
|
2
|
-
export { ModuleMappers } from "./mappers";
|
|
3
|
-
export { ModuleService } from "./services";
|
|
4
|
-
export { ModuleQueries } from "./queries";
|
|
5
|
-
export { ModuleMutations } from "./mutations";
|
|
6
|
-
|
|
7
|
-
/*
|
|
8
|
-
# NOTE: here we can export types.ts as well if we defining types seperately in a file
|
|
9
|
-
export type * from "./types";
|
|
10
|
-
# NOTE : we can also export a mocks.ts file if we using mocks which contains dummy / fall back data
|
|
11
|
-
export { ModuleMocks } from "./mocks";
|
|
12
|
-
*/
|
|
1
|
+
export { ModuleKeys } from "./keys";
|
|
2
|
+
export { ModuleMappers } from "./mappers";
|
|
3
|
+
export { ModuleService } from "./services";
|
|
4
|
+
export { ModuleQueries } from "./queries";
|
|
5
|
+
export { ModuleMutations } from "./mutations";
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
# NOTE: here we can export types.ts as well if we defining types seperately in a file
|
|
9
|
+
export type * from "./types";
|
|
10
|
+
# NOTE : we can also export a mocks.ts file if we using mocks which contains dummy / fall back data
|
|
11
|
+
export { ModuleMocks } from "./mocks";
|
|
12
|
+
*/
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
export const ModuleKeys = {
|
|
2
|
-
module: () => ["moduleName"] as const,
|
|
3
|
-
fetch_query_name_example: () => [...ModuleKeys.module(), "fetch"] as const,
|
|
4
|
-
create_query_name_example: () => [...ModuleKeys.module(), "create"] as const,
|
|
5
|
-
update_query_name_example: () => [...ModuleKeys.module(), "update"] as const,
|
|
6
|
-
delete_query_name_example: () => [...ModuleKeys.module(), "delete"] as const,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
export const ModuleKeys = {
|
|
2
|
+
module: () => ["moduleName"] as const,
|
|
3
|
+
fetch_query_name_example: () => [...ModuleKeys.module(), "fetch"] as const,
|
|
4
|
+
create_query_name_example: () => [...ModuleKeys.module(), "create"] as const,
|
|
5
|
+
update_query_name_example: () => [...ModuleKeys.module(), "update"] as const,
|
|
6
|
+
delete_query_name_example: () => [...ModuleKeys.module(), "delete"] as const,
|
|
7
|
+
fetch_infinite_query_name_example: () => [...ModuleKeys.module(), "fetch-infinite"] as const,
|
|
8
|
+
fetch_query_with_params_example: (params: any) => [...ModuleKeys.module(), "fetch-with-params", params] as const,
|
|
9
|
+
fetch_query_by_id_example: (id: string) => [...ModuleKeys.module(), "fetch-by-id", id] as const,
|
|
10
|
+
fetch_query_combo_example: (id: string, params: any) => [...ModuleKeys.module(), "fetch-combo", id, params] as const,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
# NOTE: as const is used at end so that keys are immutable
|
|
15
|
+
# NOTE : ans insted of standard object keys usd as function so its easy and clean to use and maintain in larger scale
|
|
16
|
+
# NOTE : you can change *_name_example to your own query name
|
|
17
|
+
*/
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
/*
|
|
2
|
-
# NOTE : you can change *_name_example to your own query name
|
|
3
|
-
# NOTE : you can change raw to your own data after manipulation
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export const ModuleMappers = {
|
|
7
|
-
fetch_query_name_example(raw: any) {
|
|
8
|
-
const data = raw || "manipulate your data here and then return it";
|
|
9
|
-
return data;
|
|
10
|
-
},
|
|
11
|
-
|
|
1
|
+
/*
|
|
2
|
+
# NOTE : you can change *_name_example to your own query name
|
|
3
|
+
# NOTE : you can change raw to your own data after manipulation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const ModuleMappers = {
|
|
7
|
+
fetch_query_name_example(raw: any) {
|
|
8
|
+
const data = raw || "manipulate your data here and then return it";
|
|
9
|
+
return data;
|
|
10
|
+
},
|
|
11
|
+
fetch_infinite_query_example(raw: any) {
|
|
12
|
+
const data = raw || [];
|
|
13
|
+
return data;
|
|
14
|
+
},
|
|
15
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { mutationOptions, type QueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { ModuleKeys } from "./keys";
|
|
3
|
+
import { ModuleService } from "./services";
|
|
4
|
+
|
|
5
|
+
export const ModuleMutations = {
|
|
6
|
+
create_query_name_example: (queryClient: QueryClient) =>
|
|
7
|
+
mutationOptions({
|
|
8
|
+
mutationFn: (newItem: any) =>
|
|
9
|
+
ModuleService.post_query_name_example(newItem),
|
|
10
|
+
onMutate: async (newItem: any) => {
|
|
11
|
+
/*
|
|
12
|
+
# OPTIMISTIC UPDATE TECHNIQUE:
|
|
13
|
+
1. Cancel outgoing refetches so they don't overwrite our optimistic update.
|
|
14
|
+
2. Snapshot the current cache value.
|
|
15
|
+
3. Optimistically insert the new item into the cache.
|
|
16
|
+
4. Return context containing the previous value for error rollbacks.
|
|
17
|
+
*/
|
|
18
|
+
await queryClient.cancelQueries({ queryKey: ModuleKeys.fetch_query_name_example() });
|
|
19
|
+
|
|
20
|
+
const previousItems = queryClient.getQueryData(ModuleKeys.fetch_query_name_example());
|
|
21
|
+
|
|
22
|
+
queryClient.setQueryData(ModuleKeys.fetch_query_name_example(), (old: any) => {
|
|
23
|
+
return old ? [...old, newItem] : [newItem];
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return { previousItems };
|
|
27
|
+
},
|
|
28
|
+
onSuccess: async () => {
|
|
29
|
+
/*
|
|
30
|
+
# NOTE: In case of mutations, invalidate query keys to refresh with fresh server data.
|
|
31
|
+
*/
|
|
32
|
+
await queryClient.invalidateQueries({
|
|
33
|
+
queryKey: ModuleKeys.fetch_query_name_example(),
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
onError: (error, newItem, context: any) => {
|
|
37
|
+
/*
|
|
38
|
+
# ROLLBACK ON FAILURE:
|
|
39
|
+
If the mutation fails, rollback the cache to our snapshotted state.
|
|
40
|
+
*/
|
|
41
|
+
if (context?.previousItems) {
|
|
42
|
+
queryClient.setQueryData(
|
|
43
|
+
ModuleKeys.fetch_query_name_example(),
|
|
44
|
+
context.previousItems
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
onSettled: async () => {
|
|
49
|
+
// Always refetch after error or success to keep server in sync
|
|
50
|
+
await queryClient.invalidateQueries({
|
|
51
|
+
queryKey: ModuleKeys.fetch_query_name_example(),
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { queryOptions, infiniteQueryOptions, keepPreviousData } from "@tanstack/react-query";
|
|
2
|
+
|
|
3
|
+
import { ModuleKeys } from "./keys";
|
|
4
|
+
import { ModuleMappers } from "./mappers";
|
|
5
|
+
import { ModuleService } from "./services";
|
|
6
|
+
|
|
7
|
+
export const ModuleQueries = {
|
|
8
|
+
fetch_query_name_example: () =>
|
|
9
|
+
queryOptions({
|
|
10
|
+
queryKey: ModuleKeys.fetch_query_name_example(),
|
|
11
|
+
queryFn: () => {
|
|
12
|
+
/*
|
|
13
|
+
# NOTE: ModuleService.get_query_name_example() -> hits the api of query fetch or GET
|
|
14
|
+
Return the Promise directly to TanStack Query so it can natively handle retries and error states.
|
|
15
|
+
*/
|
|
16
|
+
return ModuleService.get_query_name_example();
|
|
17
|
+
},
|
|
18
|
+
select: (raw) => {
|
|
19
|
+
/*
|
|
20
|
+
# NOTE: ModuleMappers.fetch_query_name_example() -> manipulates the data from api into desird format before returning to ui or page
|
|
21
|
+
Using the 'select' property memoizes this transformation (only runs when cached data changes).
|
|
22
|
+
*/
|
|
23
|
+
return ModuleMappers.fetch_query_name_example(raw as any);
|
|
24
|
+
},
|
|
25
|
+
/*
|
|
26
|
+
# NOTE: keeps the last successfully fetched data visible on screen
|
|
27
|
+
while fetching new data or if a subsequent fetch fails (preventing layout flickers/empty states).
|
|
28
|
+
*/
|
|
29
|
+
placeholderData: keepPreviousData,
|
|
30
|
+
}),
|
|
31
|
+
|
|
32
|
+
fetch_infinite_query_example: () =>
|
|
33
|
+
infiniteQueryOptions({
|
|
34
|
+
queryKey: ModuleKeys.fetch_infinite_query_name_example(),
|
|
35
|
+
queryFn: ({ pageParam = 1 }) => {
|
|
36
|
+
/*
|
|
37
|
+
# NOTE: ModuleService.get_infinite_query_example(pageParam) -> hits the api of query fetch or GET
|
|
38
|
+
Return the Promise directly to TanStack Query so it can natively handle retries and error states.
|
|
39
|
+
*/
|
|
40
|
+
return ModuleService.get_infinite_query_example(pageParam as number);
|
|
41
|
+
},
|
|
42
|
+
getNextPageParam: (raw: any) => {
|
|
43
|
+
// --- Option A: Cursor-based Pagination ---
|
|
44
|
+
// return raw.nextCursor ?? undefined;
|
|
45
|
+
|
|
46
|
+
// --- Option B: Page-number Metadata Pagination (last_page, current_page) ---
|
|
47
|
+
// Access metadata from the 'meta' object returned in your API response
|
|
48
|
+
const { current_page, last_page } = raw?.meta || {};
|
|
49
|
+
const hasMore = current_page < last_page;
|
|
50
|
+
return hasMore ? current_page + 1 : undefined;
|
|
51
|
+
},
|
|
52
|
+
initialPageParam: 1,
|
|
53
|
+
select: (raw) => {
|
|
54
|
+
/*
|
|
55
|
+
# NOTE: ModuleMappers.fetch_infinite_query_example() -> manipulates the data from api into desird format before returning to ui or page
|
|
56
|
+
Using the 'select' property memoizes this transformation (only runs when cached data changes).
|
|
57
|
+
*/
|
|
58
|
+
return {
|
|
59
|
+
pages: raw.pages.map((pageData: any) => ({
|
|
60
|
+
...pageData,
|
|
61
|
+
data: ModuleMappers.fetch_infinite_query_example(pageData.data),
|
|
62
|
+
})),
|
|
63
|
+
pageParams: raw.pageParams,
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
placeholderData: keepPreviousData,
|
|
67
|
+
}),
|
|
68
|
+
|
|
69
|
+
fetch_query_with_params_example: (params: Record<string, any>) =>
|
|
70
|
+
queryOptions({
|
|
71
|
+
queryKey: ModuleKeys.fetch_query_with_params_example(params),
|
|
72
|
+
queryFn: ({ signal }) => {
|
|
73
|
+
/*
|
|
74
|
+
# NOTE: Pass the native 'signal' from the TanStack queryFn context down to the service call.
|
|
75
|
+
This enables automatic cancellation if query parameters change or the component unmounts.
|
|
76
|
+
*/
|
|
77
|
+
return ModuleService.get_query_with_params_example(params, signal);
|
|
78
|
+
},
|
|
79
|
+
select: (raw) => {
|
|
80
|
+
// Reusing the same mapper example for consistency
|
|
81
|
+
return ModuleMappers.fetch_query_name_example(raw as any);
|
|
82
|
+
},
|
|
83
|
+
placeholderData: keepPreviousData,
|
|
84
|
+
}),
|
|
85
|
+
|
|
86
|
+
fetch_query_by_id_example: (id?: string | null) =>
|
|
87
|
+
queryOptions({
|
|
88
|
+
queryKey: ModuleKeys.fetch_query_by_id_example(id || ""),
|
|
89
|
+
queryFn: ({ signal }) => {
|
|
90
|
+
return ModuleService.get_query_by_id_example(id!, signal);
|
|
91
|
+
},
|
|
92
|
+
select: (raw) => {
|
|
93
|
+
return ModuleMappers.fetch_query_name_example(raw as any);
|
|
94
|
+
},
|
|
95
|
+
placeholderData: keepPreviousData,
|
|
96
|
+
/*
|
|
97
|
+
# NOTE: Conditional/Dependent Queries
|
|
98
|
+
Setting 'enabled: !!id' stops the query from executing automatically if the ID is missing (undefined, null, or empty).
|
|
99
|
+
*/
|
|
100
|
+
enabled: !!id,
|
|
101
|
+
}),
|
|
102
|
+
|
|
103
|
+
fetch_query_combo_example: (id: string | null | undefined, params: Record<string, any>) =>
|
|
104
|
+
queryOptions({
|
|
105
|
+
queryKey: ModuleKeys.fetch_query_combo_example(id || "", params),
|
|
106
|
+
queryFn: ({ signal }) => {
|
|
107
|
+
return ModuleService.get_query_combo_example(id!, params, signal);
|
|
108
|
+
},
|
|
109
|
+
select: (raw) => {
|
|
110
|
+
return ModuleMappers.fetch_query_name_example(raw as any);
|
|
111
|
+
},
|
|
112
|
+
placeholderData: keepPreviousData,
|
|
113
|
+
enabled: !!id, // Automatically stops API call if ID is null/undefined
|
|
114
|
+
}),
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/*
|
|
118
|
+
# NOTE: RAW DATA VS. MAPPED DATA & MEMOIZATION
|
|
119
|
+
|
|
120
|
+
1. **Where Raw Data Lives:**
|
|
121
|
+
- The raw response returned by the API (`queryFn`) is stored unmodified inside the **TanStack Query Cache**.
|
|
122
|
+
- This represents the exact payload from your backend database/server.
|
|
123
|
+
|
|
124
|
+
2. **Where Mapped Data Lives:**
|
|
125
|
+
- The mapped data is delivered directly to the **UI / React Component** consuming the hook.
|
|
126
|
+
- It is calculated on-the-fly by executing the `select` function on the cached raw data.
|
|
127
|
+
|
|
128
|
+
3. **How Memoization Works (Performance Optimization):**
|
|
129
|
+
- The `select` function is **automatically memoized** by TanStack Query.
|
|
130
|
+
- It will ONLY re-run when the cached raw data changes.
|
|
131
|
+
- If the component re-renders for other reasons (e.g., local UI states, parent re-renders, or window focus checks), TanStack Query skips the mapper execution completely and returns the already memoized mapped data instantly.
|
|
132
|
+
*/
|
|
133
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { apiClient } from "../client";
|
|
2
|
+
|
|
3
|
+
export const ModuleService = {
|
|
4
|
+
get_query_name_example: () => apiClient.get("/api_name/get"),
|
|
5
|
+
get_infinite_query_example: (page: number) =>
|
|
6
|
+
apiClient.get(`/api_name/list?page=${page}`),
|
|
7
|
+
get_query_with_params_example: (params: Record<string, any>, signal?: AbortSignal) => {
|
|
8
|
+
const queryParams = new URLSearchParams();
|
|
9
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
value.forEach((val) => {
|
|
12
|
+
if (val !== undefined && val !== null && val !== "") {
|
|
13
|
+
queryParams.append(key, String(val));
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
} else if (value !== undefined && value !== null && value !== "") {
|
|
17
|
+
queryParams.set(key, String(value));
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return apiClient.get(`/api_name/get-with-params?${queryParams.toString()}`, { signal });
|
|
21
|
+
},
|
|
22
|
+
get_query_by_id_example: (id: string, signal?: AbortSignal) =>
|
|
23
|
+
apiClient.get(`/api_name/get-by-id/${id}`, { signal }),
|
|
24
|
+
get_query_combo_example: (id: string, params: Record<string, any>, signal?: AbortSignal) => {
|
|
25
|
+
const queryParams = new URLSearchParams();
|
|
26
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
value.forEach((val) => {
|
|
29
|
+
if (val !== undefined && val !== null && val !== "") {
|
|
30
|
+
queryParams.append(key, String(val));
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
} else if (value !== undefined && value !== null && value !== "") {
|
|
34
|
+
queryParams.set(key, String(value));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return apiClient.get(`/api_name/get-combo/${id}?${queryParams.toString()}`, { signal });
|
|
38
|
+
},
|
|
39
|
+
post_query_name_example: (input: any) =>
|
|
40
|
+
apiClient.post("/api_name/post", input),
|
|
41
|
+
patch_query_name_example: (input: any) =>
|
|
42
|
+
apiClient.patch("/api_name/patch", input),
|
|
43
|
+
delete_query_name_example: (id: string) =>
|
|
44
|
+
apiClient.delete(`/api_name/${id}`),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
# NOTE: you can change *_name_example to your own query name
|
|
49
|
+
# NOTE: you can change /api_name to your own api name
|
|
50
|
+
# NOTE: you can change input to your own data type
|
|
51
|
+
# NOTE: you can change id to your own data type
|
|
52
|
+
|
|
53
|
+
-------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
# NOTE: WHY WE USE URLSearchParams & THE ROLE OF THE SERVICE LAYER
|
|
56
|
+
|
|
57
|
+
1. **Why we use `URLSearchParams`:**
|
|
58
|
+
- **Automatic URL-Encoding:** It automatically sanitizes special characters (like spaces, commas, or quotes) into browser-safe formats (e.g., space becomes `%20`), preventing broken URLs.
|
|
59
|
+
- **Dynamic Query String Building:** It generates the final string from a raw object dynamically (calling `.toString()` results in `key1=val1&key2=val2`).
|
|
60
|
+
|
|
61
|
+
2. **Role of this Service Layer (`get_query_with_params_example`):**
|
|
62
|
+
- **Decoupled Contracts:** It keeps components "dumb" about network specifics. The component only needs to pass a clean JavaScript object containing the filters.
|
|
63
|
+
- **Centralization:** If endpoints change or parameter serialization logic needs to adjust in the future, it is managed in this single file rather than modifying multiple UI files.
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
|
|
File without changes
|
|
File without changes
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { mutationOptions, type QueryClient } from "@tanstack/react-query";
|
|
2
|
-
import { ModuleKeys } from "./keys";
|
|
3
|
-
import { ModuleService } from "./services";
|
|
4
|
-
|
|
5
|
-
export const ModuleMutations = {
|
|
6
|
-
create_query_name_example: (queryClient: QueryClient) =>
|
|
7
|
-
mutationOptions({
|
|
8
|
-
mutationFn: (input_name: any) =>
|
|
9
|
-
ModuleService.post_query_name_example(input_name),
|
|
10
|
-
onMutate: () => {
|
|
11
|
-
/* Write optimistic update logic here */
|
|
12
|
-
},
|
|
13
|
-
onSuccess: async () => {
|
|
14
|
-
/*
|
|
15
|
-
# NOTE: In case of POST/PATCH/DELETE operations, it is very important to invalidate the queries that depend on the data that was mutated.
|
|
16
|
-
*/
|
|
17
|
-
await queryClient.invalidateQueries({
|
|
18
|
-
queryKey: ModuleKeys.fetch_query_name_example(),
|
|
19
|
-
});
|
|
20
|
-
},
|
|
21
|
-
onError: (error) => {
|
|
22
|
-
/* Write error logic here */
|
|
23
|
-
},
|
|
24
|
-
}),
|
|
25
|
-
};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { queryOptions } from "@tanstack/react-query";
|
|
2
|
-
|
|
3
|
-
import { ModuleKeys } from "./keys";
|
|
4
|
-
import { ModuleMappers } from "./mappers";
|
|
5
|
-
import { ModuleService } from "./services";
|
|
6
|
-
|
|
7
|
-
export const ModuleQueries = {
|
|
8
|
-
fetch_query_name_example: () =>
|
|
9
|
-
queryOptions({
|
|
10
|
-
queryKey: ModuleKeys.fetch_query_name_example(),
|
|
11
|
-
queryFn: async () => {
|
|
12
|
-
try {
|
|
13
|
-
/*
|
|
14
|
-
# NOTE: ModuleService.get_query_name_example() -> hts the api of query fetch or GET
|
|
15
|
-
# NOTE: ModuleMappers.fetch_query_name_example() -> manipulates the data from api into desird format before returning to ui or page
|
|
16
|
-
*/
|
|
17
|
-
const raw = await ModuleService.get_query_name_example();
|
|
18
|
-
return ModuleMappers.fetch_query_name_example(raw as any);
|
|
19
|
-
} catch {
|
|
20
|
-
/*
|
|
21
|
-
# NOTE: you can change null to empty array [] or fallback data that you will get from mocks.ts file or you can handel some other logic here
|
|
22
|
-
*/
|
|
23
|
-
return ModuleMappers.fetch_query_name_example(null);
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
}),
|
|
27
|
-
};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { apiClient } from "../client";
|
|
2
|
-
|
|
3
|
-
export const ModuleService = {
|
|
4
|
-
get_query_name_example: () => apiClient.get("/api_name/get"),
|
|
5
|
-
post_query_name_example: (input: any) =>
|
|
6
|
-
apiClient.post("/api_name/post", input),
|
|
7
|
-
patch_query_name_example: (input: any) =>
|
|
8
|
-
apiClient.patch("/api_name/patch", input),
|
|
9
|
-
delete_query_name_example: (id: string) =>
|
|
10
|
-
apiClient.delete(`/api_name/${id}`),
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
/*
|
|
14
|
-
# NOTE: you can change *_name_example to your own query name
|
|
15
|
-
# NOTE: you can change /api_name to your own api name
|
|
16
|
-
# NOTE: you can change input to your own data type
|
|
17
|
-
# NOTE: you can change id to your own data type
|
|
18
|
-
*/
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { ModuleQueries } from "./module";
|
|
2
|
-
|
|
3
|
-
export const apiQueries = {
|
|
4
|
-
module: ModuleQueries,
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export * from "./module";
|
|
8
|
-
|
|
9
|
-
/*
|
|
10
|
-
# NOTE : here you export all the queries from the module folder so it will be easy to import in components
|
|
11
|
-
|
|
12
|
-
const { data: name } = useQuery(ModuleQueries.fetch_query_name_example());
|
|
13
|
-
const { data: name } = useSuspenseQuery(ModuleQueries.fetch_query_name_example());
|
|
14
|
-
|
|
15
|
-
---------------------------------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
| Feature | `useQuery` | `useSuspenseQuery` |
|
|
18
|
-
| ------------------- | ---------------------------------------------- | --------------------------------------------------- |
|
|
19
|
-
| Loading state | Returns `isLoading`, `isPending`, `isFetching` | Does **not** return loading state |
|
|
20
|
-
| Data type | `data` can be `undefined` | `data` is guaranteed to exist when rendered |
|
|
21
|
-
| Error handling | Use `error` from hook | Error is caught by React Error Boundary |
|
|
22
|
-
| Loading UI | Handle manually (`if (isLoading)`) | Handled by React `<Suspense>` fallback |
|
|
23
|
-
| Component rendering | Renders immediately, then fetches | Suspends rendering until data is ready |
|
|
24
|
-
| Setup complexity | Simpler | Requires `<Suspense>` and usually an Error Boundary |
|
|
25
|
-
| Best for | Most applications | Apps fully using React Suspense |
|
|
26
|
-
|
|
27
|
-
----------------------------------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
# NOTE : Example for useQuery
|
|
30
|
-
const ModuleComponent = () => {
|
|
31
|
-
|
|
32
|
-
const { data, isLoading, error } = useQuery(
|
|
33
|
-
ModuleQueries.fetch_query_name_example()
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
if (isLoading) return <FallBackComponent />;
|
|
37
|
-
if (error) return <ErrorComponent />;
|
|
38
|
-
|
|
39
|
-
return <ModuleComponent data={data} />;
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
-----------------------------------------------------------------------------------------------------
|
|
44
|
-
|
|
45
|
-
# NOTE : Example for useSuspenseQuery
|
|
46
|
-
|
|
47
|
-
const ModuleComponent = () => {
|
|
48
|
-
|
|
49
|
-
const { data } = useSuspenseQuery(
|
|
50
|
-
ModuleQueries.fetch_query_name_example()
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
return <ModuleComponent data={data} />;
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
# NOTE : Wrapping
|
|
58
|
-
<Suspense fallback={<FallBackComponent />}>
|
|
59
|
-
<ModuleComponent />
|
|
60
|
-
</Suspense>
|
|
61
|
-
|
|
62
|
-
---------------------------------------------------------------------------------------------------
|
|
63
|
-
|
|
64
|
-
Use useQuery when you want to manage loading and errors inside the component.
|
|
65
|
-
Use useSuspenseQuery when your app already uses React Suspense and you want cleaner components with guaranteed data.
|
|
66
|
-
|
|
67
|
-
*/
|
|
File without changes
|