Skip to content

Commit f558aa4

Browse files
Fix selectedRepositories undefined error with generic filter validation (#321)
- Create filterUtils.ts with getDefaultFilters() and mergeFiltersWithDefaults() - Update ScriptsGrid, DownloadedScriptsTab, and FilterBar to use utility functions - Prevents crashes when loading old saved filters missing new properties - Future-proof: new filter properties automatically get defaults - Fixes TypeError: can't access property 'length', selectedRepositories is undefined
1 parent e8c2707 commit f558aa4

File tree

4 files changed

+52
-26
lines changed

4 files changed

+52
-26
lines changed

src/app/_components/DownloadedScriptsTab.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { FilterBar, type FilterState } from './FilterBar';
1010
import { ViewToggle } from './ViewToggle';
1111
import { Button } from './ui/button';
1212
import type { ScriptCard as ScriptCardType } from '~/types/script';
13+
import { getDefaultFilters, mergeFiltersWithDefaults } from './filterUtils';
1314

1415
interface DownloadedScriptsTabProps {
1516
onInstallScript?: (
@@ -25,14 +26,7 @@ export function DownloadedScriptsTab({ onInstallScript }: DownloadedScriptsTabPr
2526
const [isModalOpen, setIsModalOpen] = useState(false);
2627
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
2728
const [viewMode, setViewMode] = useState<'card' | 'list'>('card');
28-
const [filters, setFilters] = useState<FilterState>({
29-
searchQuery: '',
30-
showUpdatable: null,
31-
selectedTypes: [],
32-
selectedRepositories: [],
33-
sortBy: 'name',
34-
sortOrder: 'asc',
35-
});
29+
const [filters, setFilters] = useState<FilterState>(getDefaultFilters());
3630
const [saveFiltersEnabled, setSaveFiltersEnabled] = useState(false);
3731
const [isLoadingFilters, setIsLoadingFilters] = useState(true);
3832
const gridRef = useRef<HTMLDivElement>(null);
@@ -63,7 +57,7 @@ export function DownloadedScriptsTab({ onInstallScript }: DownloadedScriptsTabPr
6357
if (filtersResponse.ok) {
6458
const filtersData = await filtersResponse.json();
6559
if (filtersData.filters) {
66-
setFilters(filtersData.filters as FilterState);
60+
setFilters(mergeFiltersWithDefaults(filtersData.filters));
6761
}
6862
}
6963
}

src/app/_components/FilterBar.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Button } from "./ui/button";
55
import { ContextualHelpIcon } from "./ContextualHelpIcon";
66
import { Package, Monitor, Wrench, Server, FileText, Calendar, RefreshCw, Filter, GitBranch } from "lucide-react";
77
import { api } from "~/trpc/react";
8+
import { getDefaultFilters } from "./filterUtils";
89

910
export interface FilterState {
1011
searchQuery: string;
@@ -67,14 +68,7 @@ export function FilterBar({
6768
};
6869

6970
const clearAllFilters = () => {
70-
onFiltersChange({
71-
searchQuery: "",
72-
showUpdatable: null,
73-
selectedTypes: [],
74-
selectedRepositories: [],
75-
sortBy: "name",
76-
sortOrder: "asc",
77-
});
71+
onFiltersChange(getDefaultFilters());
7872
};
7973

8074
const hasActiveFilters =

src/app/_components/ScriptsGrid.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ViewToggle } from './ViewToggle';
1111
import { Button } from './ui/button';
1212
import { Clock } from 'lucide-react';
1313
import type { ScriptCard as ScriptCardType } from '~/types/script';
14+
import { getDefaultFilters, mergeFiltersWithDefaults } from './filterUtils';
1415

1516

1617
interface ScriptsGridProps {
@@ -25,14 +26,7 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
2526
const [viewMode, setViewMode] = useState<'card' | 'list'>('card');
2627
const [selectedSlugs, setSelectedSlugs] = useState<Set<string>>(new Set());
2728
const [downloadProgress, setDownloadProgress] = useState<{ current: number; total: number; currentScript: string; failed: Array<{ slug: string; error: string }> } | null>(null);
28-
const [filters, setFilters] = useState<FilterState>({
29-
searchQuery: '',
30-
showUpdatable: null,
31-
selectedTypes: [],
32-
selectedRepositories: [],
33-
sortBy: 'name',
34-
sortOrder: 'asc',
35-
});
29+
const [filters, setFilters] = useState<FilterState>(getDefaultFilters());
3630
const [saveFiltersEnabled, setSaveFiltersEnabled] = useState(false);
3731
const [isLoadingFilters, setIsLoadingFilters] = useState(true);
3832
const [isNewestMinimized, setIsNewestMinimized] = useState(false);
@@ -67,7 +61,7 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
6761
if (filtersResponse.ok) {
6862
const filtersData = await filtersResponse.json();
6963
if (filtersData.filters) {
70-
setFilters(filtersData.filters as FilterState);
64+
setFilters(mergeFiltersWithDefaults(filtersData.filters));
7165
}
7266
}
7367
}

src/app/_components/filterUtils.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { FilterState } from "./FilterBar";
2+
3+
/**
4+
* Returns the default FilterState with all properties initialized.
5+
* This serves as the single source of truth for default filter values.
6+
*/
7+
export function getDefaultFilters(): FilterState {
8+
return {
9+
searchQuery: "",
10+
showUpdatable: null,
11+
selectedTypes: [],
12+
selectedRepositories: [],
13+
sortBy: "name",
14+
sortOrder: "asc",
15+
};
16+
}
17+
18+
/**
19+
* Merges saved filters with defaults, ensuring all FilterState properties exist.
20+
* This prevents crashes when loading old saved filters that are missing new properties.
21+
*
22+
* @param savedFilters - Partial or undefined saved filters from storage
23+
* @returns Complete FilterState with all properties guaranteed to exist
24+
*/
25+
export function mergeFiltersWithDefaults(
26+
savedFilters: Partial<FilterState> | undefined
27+
): FilterState {
28+
const defaults = getDefaultFilters();
29+
30+
if (!savedFilters) {
31+
return defaults;
32+
}
33+
34+
// Merge saved filters with defaults, ensuring all properties exist
35+
return {
36+
searchQuery: savedFilters.searchQuery ?? defaults.searchQuery,
37+
showUpdatable: savedFilters.showUpdatable ?? defaults.showUpdatable,
38+
selectedTypes: savedFilters.selectedTypes ?? defaults.selectedTypes,
39+
selectedRepositories: savedFilters.selectedRepositories ?? defaults.selectedRepositories,
40+
sortBy: savedFilters.sortBy ?? defaults.sortBy,
41+
sortOrder: savedFilters.sortOrder ?? defaults.sortOrder,
42+
};
43+
}
44+

0 commit comments

Comments
 (0)