Skip to content

Commit 2412683

Browse files
Feedback: better sync URL params and component state
1 parent 44d5e56 commit 2412683

File tree

1 file changed

+45
-53
lines changed
  • packages/web/src/app/[domain]/search/components/filterPanel

1 file changed

+45
-53
lines changed

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

Lines changed: 45 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
'use client';
22

3+
import { FileIcon } from "@/components/ui/fileIcon";
34
import { Repository, SearchResultFile } from "@/lib/types";
45
import { cn, getRepoCodeHostInfo } from "@/lib/utils";
5-
import { SetStateAction, useCallback, useEffect, useState } from "react";
6+
import { LaptopIcon } from "@radix-ui/react-icons";
7+
import Image from "next/image";
8+
import { useRouter, useSearchParams } from "next/navigation";
9+
import { useEffect, useMemo } from "react";
610
import { Entry } from "./entry";
711
import { Filter } from "./filter";
8-
import Image from "next/image";
9-
import { LaptopIcon } from "@radix-ui/react-icons";
10-
import { FileIcon } from "@/components/ui/fileIcon";
11-
import { useSearchParams } from "next/navigation";
12-
import { useRouter } from "next/navigation";
1312

1413
interface FilePanelProps {
1514
matches: SearchResultFile[];
@@ -34,7 +33,7 @@ export const FilterPanel = ({
3433
return value ? new Set(value.split(',')) : new Set();
3534
};
3635

37-
const [repos, setRepos] = useState<Record<string, Entry>>(() => {
36+
const repos = useMemo(() => {
3837
const selectedRepos = getSelectedFromQuery(REPOS_QUERY_PARAM);
3938
return aggregateMatches(
4039
"Repository",
@@ -60,10 +59,10 @@ export const FilterPanel = ({
6059
Icon,
6160
};
6261
}
63-
);
64-
});
62+
)
63+
}, [searchParams]);
6564

66-
const [languages, setLanguages] = useState<Record<string, Entry>>(() => {
65+
const languages = useMemo(() => {
6766
const selectedLanguages = getSelectedFromQuery(LANGUAGES_QUERY_PARAM);
6867
return aggregateMatches(
6968
"Language",
@@ -82,20 +81,7 @@ export const FilterPanel = ({
8281
} satisfies Entry;
8382
}
8483
);
85-
});
86-
87-
const onEntryClicked = useCallback((
88-
key: string,
89-
setter: (value: SetStateAction<Record<string, Entry>>) => void,
90-
) => {
91-
setter((values) => ({
92-
...values,
93-
[key]: {
94-
...values[key],
95-
isSelected: !values[key].isSelected,
96-
},
97-
}));
98-
}, []);
84+
}, [searchParams]);
9985

10086
// Calls `onFilterChanged` with the filtered list of matches
10187
// whenever the filter state changes.
@@ -113,47 +99,53 @@ export const FilterPanel = ({
11399

114100
}, [matches, repos, languages, onFilterChanged, searchParams, router]);
115101

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]);
140-
141-
const numRepos = Object.keys(repos).length > 100 ? '100+' : Object.keys(repos).length;
142-
const numLanguages = Object.keys(languages).length > 100 ? '100+' : Object.keys(languages).length;
102+
const numRepos = useMemo(() => Object.keys(repos).length > 100 ? '100+' : Object.keys(repos).length, [repos]);
103+
const numLanguages = useMemo(() => Object.keys(languages).length > 100 ? '100+' : Object.keys(languages).length, [languages]);
104+
143105
return (
144106
<div className="p-3 flex flex-col gap-3 h-full">
145107
<Filter
146108
title="Filter By Repository"
147109
searchPlaceholder={`Filter ${numRepos} repositories`}
148110
entries={Object.values(repos)}
149-
onEntryClicked={(key) => onEntryClicked(key, setRepos)}
111+
onEntryClicked={(key) => {
112+
const newRepos = { ...repos };
113+
newRepos[key].isSelected = !newRepos[key].isSelected;
114+
const selectedRepos = Object.keys(newRepos).filter((key) => newRepos[key].isSelected);
115+
const newParams = new URLSearchParams(searchParams.toString());
116+
117+
if (selectedRepos.length > 0) {
118+
newParams.set(REPOS_QUERY_PARAM, selectedRepos.join(','));
119+
} else {
120+
newParams.delete(REPOS_QUERY_PARAM);
121+
}
122+
123+
if (newParams.toString() !== searchParams.toString()) {
124+
router.replace(`?${newParams.toString()}`, { scroll: false });
125+
}
126+
}}
150127
className="max-h-[50%]"
151128
/>
152129
<Filter
153130
title="Filter By Language"
154131
searchPlaceholder={`Filter ${numLanguages} languages`}
155132
entries={Object.values(languages)}
156-
onEntryClicked={(key) => onEntryClicked(key, setLanguages)}
133+
onEntryClicked={(key) => {
134+
const newLanguages = { ...languages };
135+
newLanguages[key].isSelected = !newLanguages[key].isSelected;
136+
const selectedLanguages = Object.keys(newLanguages).filter((key) => newLanguages[key].isSelected);
137+
const newParams = new URLSearchParams(searchParams.toString());
138+
139+
if (selectedLanguages.length > 0) {
140+
newParams.set(LANGUAGES_QUERY_PARAM, selectedLanguages.join(','));
141+
} else {
142+
newParams.delete(LANGUAGES_QUERY_PARAM);
143+
}
144+
145+
if (newParams.toString() !== searchParams.toString()) {
146+
router.replace(`?${newParams.toString()}`, { scroll: false });
147+
}
148+
}}
157149
className="overflow-auto"
158150
/>
159151
</div>

0 commit comments

Comments
 (0)