Skip to content

Commit d016f93

Browse files
authored
feat: add usePaginatedQuery hook (#10803)
* wip: commit current progress on usePaginatedQuery * chore: add cacheTime to users query * chore: update cache logic for UsersPage usersQuery * wip: commit progress on Pagination * chore: add function overloads to prepareQuery * wip: commit progress on usePaginatedQuery * docs: add clarifying comment about implementation * chore: remove optional prefetch property from query options * chore: redefine queryKey * refactor: consolidate how queryKey/queryFn are called * refactor: clean up pagination code more * fix: remove redundant properties * refactor: clean up code * wip: commit progress on usePaginatedQuery * wip: commit current pagination progress * docs: clean up comments for clarity * wip: get type signatures compatible (breaks runtime logic slightly) * refactor: clean up type definitions * chore: add support for custom onInvalidPage functions * refactor: clean up type definitions more for clarity reasons * chore: delete Pagination component (separate PR) * chore: remove cacheTime fixes (to be resolved in future PR) * docs: add clarifying/intellisense comments for DX * refactor: link users queries to same queryKey implementation * docs: remove misleading comment * docs: more comments * chore: update onInvalidPage params for more flexibility * fix: remove explicit any * refactor: clean up type definitions * refactor: rename query params for consistency * refactor: clean up input validation for page changes * refactor/fix: update hook to be aware of async data * chore: add contravariance to dictionary * refactor: increase type-safety of usePaginatedQuery * docs: more comments * chore: move usePaginatedQuery file * fix: add back cacheTime * chore: swap in usePaginatedQuery for users table * chore: add goToFirstPage to usePaginatedQuery * fix: make page redirects work properly * refactor: clean up clamp logic * chore: swap in usePaginatedQuery for Audits table * refactor: move dependencies around * fix: remove deprecated properties from hook * refactor: clean up code more * docs: add todo comment * chore: update testing fixtures * wip: commit current progress for tests * fix: update useEffectEvent to sync via layout effects * wip: commit more progress on tests * wip: stub out all expected test cases * wip: more test progress * wip: more test progress * wip: commit more test progress * wip: AHHHHHHHH * chore: finish two more test cases * wip: add in all tests (still need to investigate prefetching * refactor: clean up code slightly * fix: remove math bugs when calculating pages * fix: wrap up all testing and clean up cases * docs: update comments for clarity * fix: update error-handling for invalid page handling * fix: apply suggestions
1 parent 329aa45 commit d016f93

File tree

11 files changed

+959
-65
lines changed

11 files changed

+959
-65
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"coderdenttest",
1919
"coderdtest",
2020
"codersdk",
21+
"contravariance",
2122
"cronstrue",
2223
"databasefake",
2324
"dbmem",

site/src/api/queries/audits.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { getAuditLogs } from "api/api";
2+
import { type AuditLogResponse } from "api/typesGenerated";
3+
import { useFilterParamsKey } from "components/Filter/filter";
4+
import { type UsePaginatedQueryOptions } from "hooks/usePaginatedQuery";
5+
6+
export function paginatedAudits(
7+
searchParams: URLSearchParams,
8+
): UsePaginatedQueryOptions<AuditLogResponse, string> {
9+
return {
10+
searchParams,
11+
queryPayload: () => searchParams.get(useFilterParamsKey) ?? "",
12+
queryKey: ({ payload, pageNumber }) => {
13+
return ["auditLogs", payload, pageNumber] as const;
14+
},
15+
queryFn: ({ payload, limit, offset }) => {
16+
return getAuditLogs({
17+
offset,
18+
limit,
19+
q: payload,
20+
});
21+
},
22+
};
23+
}

site/src/api/queries/users.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { QueryClient, type UseQueryOptions } from "react-query";
22
import * as API from "api/api";
3-
import {
3+
import type {
44
AuthorizationRequest,
55
GetUsersResponse,
66
UpdateUserPasswordRequest,
@@ -10,11 +10,36 @@ import {
1010
} from "api/typesGenerated";
1111
import { getAuthorizationKey } from "./authCheck";
1212
import { getMetadataAsJSON } from "utils/metadata";
13+
import { type UsePaginatedQueryOptions } from "hooks/usePaginatedQuery";
14+
import { prepareQuery } from "utils/filters";
15+
16+
export function usersKey(req: UsersRequest) {
17+
return ["users", req] as const;
18+
}
19+
20+
export function paginatedUsers(): UsePaginatedQueryOptions<
21+
GetUsersResponse,
22+
UsersRequest
23+
> {
24+
return {
25+
queryPayload: ({ limit, offset, searchParams }) => {
26+
return {
27+
limit,
28+
offset,
29+
q: prepareQuery(searchParams.get("filter") ?? ""),
30+
};
31+
},
32+
33+
queryKey: ({ payload }) => usersKey(payload),
34+
queryFn: ({ payload, signal }) => API.getUsers(payload, signal),
35+
};
36+
}
1337

1438
export const users = (req: UsersRequest): UseQueryOptions<GetUsersResponse> => {
1539
return {
16-
queryKey: ["users", req],
40+
queryKey: usersKey(req),
1741
queryFn: ({ signal }) => API.getUsers(req, signal),
42+
cacheTime: 5 * 1000 * 60,
1843
};
1944
};
2045

site/src/components/Filter/filter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ type UseFilterConfig = {
4545
onUpdate?: (newValue: string) => void;
4646
};
4747

48-
const useFilterParamsKey = "filter";
48+
export const useFilterParamsKey = "filter";
4949

5050
export const useFilter = ({
5151
fallbackFilter = "",

site/src/hooks/hookPolyfills.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* They do not have the same ESLinter exceptions baked in that the official
77
* hooks do, especially for dependency arrays.
88
*/
9-
import { useCallback, useEffect, useRef } from "react";
9+
import { useCallback, useLayoutEffect, useRef } from "react";
1010

1111
/**
1212
* A DIY version of useEffectEvent.
@@ -35,7 +35,12 @@ export function useEffectEvent<TArgs extends unknown[], TReturn = unknown>(
3535
callback: (...args: TArgs) => TReturn,
3636
) {
3737
const callbackRef = useRef(callback);
38-
useEffect(() => {
38+
39+
// useLayoutEffect should be overkill here 99% of the time, but if this were
40+
// defined as a regular effect, useEffectEvent would not be able to work with
41+
// any layout effects at all; the callback sync here would fire *after* the
42+
// layout effect that needs the useEffectEvent function
43+
useLayoutEffect(() => {
3944
callbackRef.current = callback;
4045
}, [callback]);
4146

0 commit comments

Comments
 (0)