Skip to content

Commit 4c77bff

Browse files
committed
fix: update API design of various utility hooks
1 parent 5dc67cf commit 4c77bff

File tree

10 files changed

+226
-131
lines changed

10 files changed

+226
-131
lines changed

site/src/api/queries/templates.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export const templateExamples = () => {
106106
};
107107
};
108108

109-
export const templateVersionRoot: string = "templateVersion"
109+
export const templateVersionRoot: string = "templateVersion";
110110

111111
export const templateVersion = (versionId: string) => {
112112
return {

site/src/components/Filter/Filter.tsx

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { useDebouncedFunction } from "hooks/debounce";
1616
import { ExternalLinkIcon } from "lucide-react";
1717
import { ChevronDownIcon } from "lucide-react";
1818
import { type FC, type ReactNode, useEffect, useRef, useState } from "react";
19-
import type { useSearchParams } from "react-router-dom";
2019

2120
type PresetFilter = {
2221
name: string;
@@ -27,35 +26,46 @@ type FilterValues = Record<string, string | undefined>;
2726

2827
type UseFilterConfig = {
2928
/**
30-
* The fallback value to use in the event that no filter params can be parsed
31-
* from the search params object. This value is allowed to change on
32-
* re-renders.
29+
* The fallback value to use in the event that no filter params can be
30+
* parsed from the search params object.
3331
*/
3432
fallbackFilter?: string;
35-
searchParamsResult: ReturnType<typeof useSearchParams>;
33+
searchParams: URLSearchParams;
34+
onSearchParamsChange: (newParams: URLSearchParams) => void;
3635
onUpdate?: (newValue: string) => void;
3736
};
3837

38+
export type UseFilterResult = Readonly<{
39+
query: string;
40+
values: FilterValues;
41+
used: boolean;
42+
update: (newValues: string | FilterValues) => void;
43+
debounceUpdate: (newValues: string | FilterValues) => void;
44+
cancelDebounce: () => void;
45+
}>;
46+
3947
export const useFilterParamsKey = "filter";
4048

4149
export const useFilter = ({
4250
fallbackFilter = "",
43-
searchParamsResult,
51+
searchParams,
52+
onSearchParamsChange,
4453
onUpdate,
45-
}: UseFilterConfig) => {
46-
const [searchParams, setSearchParams] = searchParamsResult;
54+
}: UseFilterConfig): UseFilterResult => {
4755
const query = searchParams.get(useFilterParamsKey) ?? fallbackFilter;
4856

4957
const update = (newValues: string | FilterValues) => {
5058
const serialized =
5159
typeof newValues === "string" ? newValues : stringifyFilter(newValues);
52-
53-
searchParams.set(useFilterParamsKey, serialized);
54-
setSearchParams(searchParams);
55-
56-
if (onUpdate !== undefined) {
57-
onUpdate(serialized);
60+
const noUpdateNeeded = searchParams.get(useFilterParamsKey) === serialized;
61+
if (noUpdateNeeded) {
62+
return;
5863
}
64+
65+
const copy = new URLSearchParams(searchParams);
66+
copy.set(useFilterParamsKey, serialized);
67+
onSearchParamsChange(copy);
68+
onUpdate?.(serialized);
5969
};
6070

6171
const { debounced: debounceUpdate, cancelDebounce } = useDebouncedFunction(
@@ -73,8 +83,6 @@ export const useFilter = ({
7383
};
7484
};
7585

76-
export type UseFilterResult = ReturnType<typeof useFilter>;
77-
7886
const parseFilterQuery = (filterQuery: string): FilterValues => {
7987
if (filterQuery === "") {
8088
return {};

site/src/hooks/usePagination.ts

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,41 @@
11
import { DEFAULT_RECORDS_PER_PAGE } from "components/PaginationWidget/utils";
2-
import type { useSearchParams } from "react-router-dom";
32

4-
export const usePagination = ({
5-
searchParamsResult,
6-
}: {
7-
searchParamsResult: ReturnType<typeof useSearchParams>;
8-
}) => {
9-
const [searchParams, setSearchParams] = searchParamsResult;
10-
const page = searchParams.get("page") ? Number(searchParams.get("page")) : 1;
11-
const limit = DEFAULT_RECORDS_PER_PAGE;
12-
const offset = page <= 0 ? 0 : (page - 1) * limit;
3+
const paginationKey = "page";
134

14-
const goToPage = (page: number) => {
15-
searchParams.set("page", page.toString());
16-
setSearchParams(searchParams);
17-
};
5+
type UsePaginationOptions = Readonly<{
6+
searchParams: URLSearchParams;
7+
onSearchParamsChange: (newParams: URLSearchParams) => void;
8+
}>;
9+
10+
type UsePaginationResult = Readonly<{
11+
page: number;
12+
limit: number;
13+
offset: number;
14+
goToPage: (page: number) => void;
15+
}>;
16+
17+
export function usePagination(
18+
options: UsePaginationOptions,
19+
): UsePaginationResult {
20+
const { searchParams, onSearchParamsChange } = options;
21+
const limit = DEFAULT_RECORDS_PER_PAGE;
22+
const rawPage = Number.parseInt(searchParams.get(paginationKey) || "0", 10);
23+
const page = Number.isNaN(rawPage) ? 1 : rawPage;
1824

1925
return {
2026
page,
2127
limit,
22-
goToPage,
23-
offset,
28+
offset: Math.max(0, (page - 1) * limit),
29+
goToPage: (newPage) => {
30+
const abortNavigation =
31+
page === newPage || !Number.isFinite(page) || !Number.isInteger(page);
32+
if (abortNavigation) {
33+
return;
34+
}
35+
36+
const copy = new URLSearchParams(searchParams);
37+
copy.set("page", page.toString());
38+
onSearchParamsChange(copy);
39+
},
2440
};
25-
};
41+
}

site/src/pages/AuditPage/AuditPage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ const AuditPage: FC = () => {
3333
const [searchParams, setSearchParams] = useSearchParams();
3434
const auditsQuery = usePaginatedQuery(paginatedAudits(searchParams));
3535
const filter = useFilter({
36-
searchParamsResult: [searchParams, setSearchParams],
36+
searchParams,
37+
onSearchParamsChange: setSearchParams,
3738
onUpdate: auditsQuery.goToFirstPage,
3839
});
3940

site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,7 @@ export const CreateWorkspacePageViewExperimental: FC<
108108
owner,
109109
setOwner,
110110
}) => {
111-
const [suggestedName, setSuggestedName] = useState(() =>
112-
generateWorkspaceName(),
113-
);
111+
const [suggestedName, setSuggestedName] = useState(generateWorkspaceName);
114112
const [showPresetParameters, setShowPresetParameters] = useState(false);
115113
const id = useId();
116114
const workspaceNameInputRef = useRef<HTMLInputElement>(null);
@@ -124,14 +122,14 @@ export const CreateWorkspacePageViewExperimental: FC<
124122

125123
// Only touched fields are sent to the websocket
126124
// Autofilled parameters are marked as touched since they have been modified
127-
const initialTouched = parameters.reduce(
125+
const initialTouched = parameters.reduce<Record<string, boolean>>(
128126
(touched, parameter) => {
129127
if (autofillByName[parameter.name] !== undefined) {
130128
touched[parameter.name] = true;
131129
}
132130
return touched;
133131
},
134-
{} as Record<string, boolean>,
132+
{},
135133
);
136134

137135
// The form parameters values hold the working state of the parameters that will be submitted when creating a workspace

site/src/pages/TemplatesPage/TemplatesPage.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ const TemplatesPage: FC = () => {
1414
const { permissions, user: me } = useAuthenticated();
1515
const { showOrganizations } = useDashboard();
1616

17-
const searchParamsResult = useSearchParams();
17+
const [searchParams, setSearchParams] = useSearchParams();
1818
const filter = useFilter({
1919
fallbackFilter: "deprecated:false",
20-
searchParamsResult,
20+
searchParams,
21+
onSearchParamsChange: setSearchParams,
2122
onUpdate: () => {}, // reset pagination
2223
});
2324

site/src/pages/UsersPage/UsersPage.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,8 @@ type UserPageProps = {
3939
const UsersPage: FC<UserPageProps> = ({ defaultNewPassword }) => {
4040
const queryClient = useQueryClient();
4141
const navigate = useNavigate();
42-
const searchParamsResult = useSearchParams();
42+
const [searchParams, setSearchParams] = useSearchParams();
4343
const { entitlements } = useDashboard();
44-
const [searchParams] = searchParamsResult;
4544

4645
const groupsByUserIdQuery = useQuery(groupsByUserId());
4746
const authMethodsQuery = useQuery(authMethods());
@@ -58,9 +57,10 @@ const UsersPage: FC<UserPageProps> = ({ defaultNewPassword }) => {
5857
enabled: viewDeploymentConfig,
5958
});
6059

61-
const usersQuery = usePaginatedQuery(paginatedUsers(searchParamsResult[0]));
60+
const usersQuery = usePaginatedQuery(paginatedUsers(searchParams));
6261
const useFilterResult = useFilter({
63-
searchParamsResult,
62+
searchParams,
63+
onSearchParamsChange: setSearchParams,
6464
onUpdate: usersQuery.goToFirstPage,
6565
});
6666

0 commit comments

Comments
 (0)