Skip to content

Commit 1b520b4

Browse files
committed
feat: using nuqs library for queries on sort & filters
1 parent 778c0b4 commit 1b520b4

5 files changed

Lines changed: 96 additions & 66 deletions

File tree

frontend/app/clientLayout.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { grey } from "@mui/material/colors";
1010
import CssBaseline from "@mui/material/CssBaseline";
1111
import { createTheme, styled } from "@mui/material/styles";
1212
import ThemeProvider from "@mui/system/ThemeProvider";
13+
import { NuqsAdapter } from "nuqs/adapters/next/app";
1314
import React, {
1415
createContext,
1516
useCallback,
@@ -129,7 +130,9 @@ const ClientLayout: React.FC<{
129130
<ThemeProvider theme={theme}>
130131
<CssBaseline />
131132
<ReduxProvider store={store}>
132-
<App>{children}</App>
133+
<NuqsAdapter>
134+
<App>{children}</App>
135+
</NuqsAdapter>
133136
</ReduxProvider>
134137
</ThemeProvider>
135138
</DarkModeContext.Provider>

frontend/hooks/useQueryFilter.ts

Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { usePathname, useRouter, useSearchParams } from "next/navigation";
1+
import { parseAsString, useQueryStates } from "nuqs";
22
import { useEffect, useRef } from "react";
33
import { selectFilters, setFilter } from "redux/filtersSlice";
44

@@ -7,13 +7,9 @@ import { Filters } from "../types";
77
import { filterBarDropdown } from "../utils/constants";
88

99
// Type for the keys of the Filters type
10-
const queryFilter: (keyof Filters)[] = [
11-
"capacity",
12-
"usage",
13-
"location",
14-
"duration",
15-
"id",
16-
];
10+
const filterQueries: (keyof Filters)[] = filterBarDropdown.map(
11+
({ key }) => key
12+
);
1713

1814
// Create an object that maps each filter key to its values in an array
1915
const validFilterQueries: Partial<Record<keyof Filters, string[]>> =
@@ -24,43 +20,56 @@ const validFilterQueries: Partial<Record<keyof Filters, string[]>> =
2420
])
2521
);
2622

23+
// Function to check if a given filter key and value are valid
2724
const isValidFilter = (key: keyof Filters, value: string): boolean => {
2825
const isValid = validFilterQueries[key];
29-
if (isValid === undefined) return false;
30-
return isValid.includes(value);
26+
if (isValid) {
27+
return isValid.includes(value);
28+
}
29+
return false;
3130
};
3231

3332
// Function to handle query string parameters for filters in page.tsx (browse page)
3433
const useQueryFilter = () => {
3534
const dispatch = useDispatch();
36-
const router = useRouter();
37-
const searchParams = useSearchParams();
38-
const pathname = usePathname();
3935
const filters = useSelector(selectFilters);
4036

4137
// Only write to query parameters on initial load
4238
const initialLoad = useRef(true);
4339

44-
// On initial load, apply filters from query parameters to Redux state
40+
const [filterParams, setFilterParams] = useQueryStates(
41+
{
42+
capacity: parseAsString.withDefault(""),
43+
usage: parseAsString.withDefault(""),
44+
location: parseAsString.withDefault(""),
45+
duration: parseAsString.withDefault(""),
46+
id: parseAsString.withDefault(""),
47+
},
48+
{ shallow: true }
49+
);
50+
51+
// Apply filters from URL on load
4552
useEffect(() => {
46-
const params = new URLSearchParams(searchParams.toString());
53+
const invalidKeys: Partial<Record<keyof Filters, null>> = {};
4754
let hasInvalidFilter = false;
4855

49-
queryFilter.forEach((key) => {
50-
const value = searchParams.get(key);
51-
if (value && isValidFilter(key, value)) {
52-
dispatch(setFilter({ key, value }));
53-
} else {
54-
params.delete(key);
55-
hasInvalidFilter = true;
56+
filterQueries.forEach((key) => {
57+
const value = filterParams[key];
58+
if (value) {
59+
if (isValidFilter(key, value)) {
60+
dispatch(setFilter({ key, value }));
61+
} else {
62+
// Set as null to remove invalid filters
63+
invalidKeys[key] = null;
64+
hasInvalidFilter = true;
65+
}
5666
}
5767
});
5868

5969
// Check if need to update URL to remove invalid parameters
6070
if (hasInvalidFilter) {
61-
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
71+
setFilterParams(invalidKeys);
6272
}
63-
6473
// eslint-disable-next-line react-hooks/exhaustive-deps
6574
}, [dispatch]); // Apply filters on initial load (when user shares link with query params), and not on subsequent loads
6675

@@ -72,21 +81,15 @@ const useQueryFilter = () => {
7281
return;
7382
}
7483

75-
const params = new URLSearchParams(searchParams.toString());
76-
queryFilter.forEach((key) => {
77-
const value = filters[key];
78-
if (value && isValidFilter(key, value)) {
79-
// If the filter value exists and is a valid filter key, apply to query parameters
80-
params.set(key, value);
81-
} else {
82-
// Remove the key if the filter value is not in the filter
83-
params.delete(key);
84-
}
84+
const params: Partial<Record<keyof Filters, string | null>> = {};
85+
86+
filterQueries.forEach((key) => {
87+
params[key] = filters[key] ?? null;
8588
});
8689

87-
// Update the URL with new query parameters
88-
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
89-
}, [filters, pathname, router, searchParams]); // Update query parameters whenever filters change
90+
setFilterParams(params);
91+
// eslint-disable-next-line react-hooks/exhaustive-deps
92+
}, [filters]);
9093
};
9194

9295
export default useQueryFilter;

frontend/hooks/useQuerySort.ts

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,26 @@
1-
import { usePathname, useRouter, useSearchParams } from "next/navigation";
2-
import { useEffect, useRef, useState } from "react";
1+
import { parseAsString, useQueryState } from "nuqs";
2+
import { useEffect } from "react";
33

44
import { sortBarDropdown } from "../utils/constants";
55

66
const validSortQueries: string[] = sortBarDropdown.map((item) => item.value);
77

8-
const isValidSort = (value: string): boolean => {
9-
return validSortQueries.includes(value);
10-
};
8+
const isValidSort = (value: string): boolean =>
9+
validSortQueries.includes(value);
1110

12-
// Function to handle query string parameters for sorting in page.tsx (browse page)
1311
const useQuerySort = (): [string, (sort: string) => void] => {
14-
const router = useRouter();
15-
const searchParams = useSearchParams();
16-
const pathname = usePathname();
17-
const initialLoad = useRef(true);
18-
19-
// On initial load, fallback to alphabetical sort if no sort query parameter provided
20-
const [sort, setSort] = useState<string>(() => {
21-
const value = searchParams.get("sort");
22-
return value && isValidSort(value) ? value : "alphabetical";
23-
});
12+
const [sort, setSort] = useQueryState(
13+
"sort",
14+
parseAsString.withDefault("alphabetical")
15+
);
2416

25-
// Apply sort to URL when sort changes
2617
useEffect(() => {
27-
// Skip first run to avoid overwriting query parameters on initial load with empty state
28-
if (initialLoad.current) {
29-
initialLoad.current = false;
30-
return;
18+
if (sort && !isValidSort(sort)) {
19+
// Null resets the sort to the default value, which is "alphabetical"
20+
setSort(null);
3121
}
32-
33-
const params = new URLSearchParams(searchParams.toString());
34-
params.set("sort", sort);
35-
36-
// Update the URL with new query parameters
37-
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
38-
}, [sort, pathname, router, searchParams]); // Update query parameters whenever sort changes
22+
// eslint-disable-next-line react-hooks/exhaustive-deps
23+
}, []);
3924

4025
return [sort, setSort];
4126
};

frontend/package-lock.json

Lines changed: 38 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"lucide-react": "^1.7.0",
3333
"match-sorter": "^8.2.0",
3434
"next": "^15.4.10",
35+
"nuqs": "^2.9.4",
3536
"path-scurry": "^2.0.0",
3637
"react": "19.2.7",
3738
"react-big-calendar": "^1.6.9",

0 commit comments

Comments
 (0)