Skip to content

Commit 3b9461b

Browse files
update query params when filter panel changes
1 parent 78ec512 commit 3b9461b

File tree

3 files changed

+61
-34
lines changed

3 files changed

+61
-34
lines changed

packages/web/src/app/[domain]/search/components/filterPanel/index.tsx

+56-29
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,35 @@ import { Filter } from "./filter";
88
import Image from "next/image";
99
import { LaptopIcon } from "@radix-ui/react-icons";
1010
import { FileIcon } from "@/components/ui/fileIcon";
11+
import { useSearchParams } from "next/navigation";
12+
import { useRouter } from "next/navigation";
1113

1214
interface FilePanelProps {
1315
matches: SearchResultFile[];
1416
onFilterChanged: (filteredMatches: SearchResultFile[]) => void,
1517
repoMetadata: Record<string, Repository>;
1618
}
1719

20+
const LANGUAGES_QUERY_PARAM = "langs";
21+
const REPOS_QUERY_PARAM = "repos";
22+
1823
export const FilterPanel = ({
1924
matches,
2025
onFilterChanged,
2126
repoMetadata,
2227
}: FilePanelProps) => {
23-
const [repos, setRepos] = useState<Record<string, Entry>>({});
24-
const [languages, setLanguages] = useState<Record<string, Entry>>({});
25-
26-
useEffect(() => {
27-
const _repos = aggregateMatches(
28+
const router = useRouter();
29+
const searchParams = useSearchParams();
30+
31+
// Helper to parse query params into sets
32+
const getSelectedFromQuery = (param: string) => {
33+
const value = searchParams.get(param);
34+
return value ? new Set(value.split(',')) : new Set();
35+
};
36+
37+
const [repos, setRepos] = useState<Record<string, Entry>>(() => {
38+
const selectedRepos = getSelectedFromQuery(REPOS_QUERY_PARAM);
39+
return aggregateMatches(
2840
"Repository",
2941
matches,
3042
(key) => {
@@ -44,17 +56,16 @@ export const FilterPanel = ({
4456
key,
4557
displayName: info?.displayName ?? key,
4658
count: 0,
47-
isSelected: false,
59+
isSelected: selectedRepos.has(key),
4860
Icon,
4961
};
5062
}
5163
);
64+
});
5265

53-
setRepos(_repos);
54-
}, [matches, repoMetadata, setRepos]);
55-
56-
useEffect(() => {
57-
const _languages = aggregateMatches(
66+
const [languages, setLanguages] = useState<Record<string, Entry>>(() => {
67+
const selectedLanguages = getSelectedFromQuery(LANGUAGES_QUERY_PARAM);
68+
return aggregateMatches(
5869
"Language",
5970
matches,
6071
(key) => {
@@ -66,14 +77,12 @@ export const FilterPanel = ({
6677
key,
6778
displayName: key,
6879
count: 0,
69-
isSelected: false,
80+
isSelected: selectedLanguages.has(key),
7081
Icon: Icon,
7182
} satisfies Entry;
7283
}
73-
)
74-
75-
setLanguages(_languages);
76-
}, [matches, setLanguages]);
84+
);
85+
});
7786

7887
const onEntryClicked = useCallback((
7988
key: string,
@@ -88,28 +97,46 @@ export const FilterPanel = ({
8897
}));
8998
}, []);
9099

100+
// Calls `onFilterChanged` with the filtered list of matches
101+
// whenever the filter state changes.
91102
useEffect(() => {
92-
const selectedRepos = new Set(
93-
Object.entries(repos)
94-
.filter(([_, { isSelected }]) => isSelected)
95-
.map(([key]) => key)
96-
);
97-
98-
const selectedLanguages = new Set(
99-
Object.entries(languages)
100-
.filter(([_, { isSelected }]) => isSelected)
101-
.map(([key]) => key)
102-
);
103+
const selectedRepos = new Set(Object.keys(repos).filter((key) => repos[key].isSelected));
104+
const selectedLanguages = new Set(Object.keys(languages).filter((key) => languages[key].isSelected));
103105

104106
const filteredMatches = matches.filter((match) =>
105107
(
106108
(selectedRepos.size === 0 ? true : selectedRepos.has(match.Repository)) &&
107109
(selectedLanguages.size === 0 ? true : selectedLanguages.has(match.Language))
108110
)
109111
);
110-
111112
onFilterChanged(filteredMatches);
112-
}, [matches, repos, languages, onFilterChanged]);
113+
114+
}, [matches, repos, languages, onFilterChanged, searchParams, router]);
115+
116+
// Updates the query params when the filter state changes
117+
useEffect(() => {
118+
const selectedRepos = Object.keys(repos).filter((key) => repos[key].isSelected);
119+
const selectedLanguages = Object.keys(languages).filter((key) => languages[key].isSelected);
120+
121+
const newParams = new URLSearchParams(searchParams.toString());
122+
123+
if (selectedRepos.length > 0) {
124+
newParams.set(REPOS_QUERY_PARAM, selectedRepos.join(','));
125+
} else {
126+
newParams.delete(REPOS_QUERY_PARAM);
127+
}
128+
129+
if (selectedLanguages.length > 0) {
130+
newParams.set(LANGUAGES_QUERY_PARAM, selectedLanguages.join(','));
131+
} else {
132+
newParams.delete(LANGUAGES_QUERY_PARAM);
133+
}
134+
135+
// Only push if params actually changed
136+
if (newParams.toString() !== searchParams.toString()) {
137+
router.replace(`?${newParams.toString()}`, { scroll: false });
138+
}
139+
}, [repos, languages, searchParams, router]);
113140

114141
const numRepos = Object.keys(repos).length > 100 ? '100+' : Object.keys(repos).length;
115142
const numLanguages = Object.keys(languages).length > 100 ? '100+' : Object.keys(languages).length;

packages/web/src/app/[domain]/search/page.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const SearchPageInternal = () => {
4747
const domain = useDomain();
4848
const { toast } = useToast();
4949

50-
const { data: searchResponse, isLoading, error } = useQuery({
50+
const { data: searchResponse, isLoading: isSearchLoading, error } = useQuery({
5151
queryKey: ["search", searchQuery, maxMatchDisplayCount],
5252
queryFn: () => measure(() => unwrapServiceError(search({
5353
query: searchQuery,
@@ -91,7 +91,7 @@ const SearchPageInternal = () => {
9191
// repository metadata (like host type, repo name, etc.)
9292
// Convert this into a map of repo name to repo metadata
9393
// for easy lookup.
94-
const { data: repoMetadata } = useQuery({
94+
const { data: repoMetadata, isLoading: isRepoMetadataLoading } = useQuery({
9595
queryKey: ["repos"],
9696
queryFn: () => getRepos(domain),
9797
select: (data): Record<string, Repository> =>
@@ -194,7 +194,7 @@ const SearchPageInternal = () => {
194194
<Separator />
195195
</div>
196196

197-
{isLoading ? (
197+
{(isSearchLoading || isRepoMetadataLoading) ? (
198198
<div className="flex flex-col items-center justify-center h-full gap-2">
199199
<SymbolIcon className="h-6 w-6 animate-spin" />
200200
<p className="font-semibold text-center">Searching...</p>

packages/web/src/hooks/useNonEmptyQueryParam.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ import { useMemo } from "react";
1717
*/
1818
export const useNonEmptyQueryParam = (param: string) => {
1919
const searchParams = useSearchParams();
20-
const inviteId = useMemo(() => {
20+
const paramValue = useMemo(() => {
2121
return getSearchParam(param, searchParams);
2222
}, [param, searchParams]);
2323

24-
return inviteId;
24+
return paramValue;
2525
};
2626

2727
/**

0 commit comments

Comments
 (0)