1- import { useState , useEffect } from "react"
1+ import { useState , useEffect , useRef } from "react"
22import type { ChangelogItem } from "~/components/ChangelogSnippet/types.ts"
33import { matchesFilters } from "~/utils/changelogFilters.ts"
44import { parseURLParams , updateFilterURL , toggleItemInArray } from "~/utils/changelogFilterUtils.ts"
55
66export interface UseChangelogFiltersProps {
7- products : string [ ]
8- networks : string [ ]
9- types : string [ ]
107 items : ChangelogItem [ ]
118}
129
13- export const useChangelogFilters = ( { products, networks, types, items } : UseChangelogFiltersProps ) => {
14- const [ searchExpanded , setSearchExpanded ] = useState ( false )
15- const [ searchTerm , setSearchTerm ] = useState ( "" )
16- const [ selectedProducts , setSelectedProducts ] = useState < string [ ] > ( [ ] )
17- const [ selectedNetworks , setSelectedNetworks ] = useState < string [ ] > ( [ ] )
18- const [ selectedTypes , setSelectedTypes ] = useState < string [ ] > ( [ ] )
10+ interface FilterState {
11+ selectedProducts : string [ ]
12+ selectedNetworks : string [ ]
13+ selectedTypes : string [ ]
14+ searchTerm : string
15+ searchExpanded : boolean
16+ }
17+
18+ export const useChangelogFilters = ( { items } : UseChangelogFiltersProps ) => {
19+ const [ filters , setFilters ] = useState < FilterState > ( {
20+ selectedProducts : [ ] ,
21+ selectedNetworks : [ ] ,
22+ selectedTypes : [ ] ,
23+ searchTerm : "" ,
24+ searchExpanded : false ,
25+ } )
26+ const isInitialMount = useRef ( true )
27+ const hasLoadedFromURL = useRef ( false )
1928
2029 // Read URL parameters on mount
2130 useEffect ( ( ) => {
2231 const urlParams = parseURLParams ( )
2332
24- setSelectedProducts ( urlParams . products )
25- setSelectedNetworks ( urlParams . networks )
26- setSelectedTypes ( urlParams . types )
27- setSearchTerm ( urlParams . searchTerm )
28- setSearchExpanded ( urlParams . searchExpanded )
33+ setFilters ( {
34+ selectedProducts : urlParams . products ,
35+ selectedNetworks : urlParams . networks ,
36+ selectedTypes : urlParams . types ,
37+ searchTerm : urlParams . searchTerm ,
38+ searchExpanded : urlParams . searchExpanded ,
39+ } )
40+
41+ hasLoadedFromURL . current = true
2942 } , [ ] )
3043
31- // Update URL when filters change
44+ // Update URL when filters change (but not on initial mount)
3245 useEffect ( ( ) => {
33- updateFilterURL ( selectedProducts , selectedNetworks , selectedTypes , searchTerm )
34- } , [ selectedProducts , selectedNetworks , selectedTypes , searchTerm ] )
46+ // Skip the first render and the initial load from URL
47+ if ( isInitialMount . current ) {
48+ isInitialMount . current = false
49+ return
50+ }
51+
52+ // Skip if we just loaded from URL
53+ if ( hasLoadedFromURL . current ) {
54+ hasLoadedFromURL . current = false
55+ return
56+ }
57+
58+ updateFilterURL ( filters . selectedProducts , filters . selectedNetworks , filters . selectedTypes , filters . searchTerm )
59+ } , [ filters ] )
3560
3661 // Filter items and update the display
3762 useEffect ( ( ) => {
@@ -43,9 +68,9 @@ export const useChangelogFilters = ({ products, networks, types, items }: UseCha
4368 const emptyState = document . querySelector ( ".empty-state" ) as HTMLElement
4469 const changelogList = document . querySelector ( ".changelog-list" ) as HTMLElement
4570
46- if ( searchTerm ) {
71+ if ( filters . searchTerm ) {
4772 // Search takes priority - filter by search term
48- const searchLower = searchTerm . toLowerCase ( )
73+ const searchLower = filters . searchTerm . toLowerCase ( )
4974 let visibleCount = 0
5075
5176 changelogItems . forEach ( ( item ) => {
@@ -82,14 +107,20 @@ export const useChangelogFilters = ({ products, networks, types, items }: UseCha
82107 } else {
83108 // Apply filter logic
84109 let visibleCount = 0
85- const hasFilters = selectedProducts . length > 0 || selectedNetworks . length > 0 || selectedTypes . length > 0
110+ const hasFilters =
111+ filters . selectedProducts . length > 0 || filters . selectedNetworks . length > 0 || filters . selectedTypes . length > 0
86112
87113 changelogItems . forEach ( ( item ) => {
88114 const index = parseInt ( item . getAttribute ( "data-index" ) || "0" )
89115 const changelogItem = items [ index ]
90116
91117 if ( hasFilters && changelogItem ) {
92- const matches = matchesFilters ( changelogItem , selectedProducts , selectedNetworks , selectedTypes )
118+ const matches = matchesFilters (
119+ changelogItem ,
120+ filters . selectedProducts ,
121+ filters . selectedNetworks ,
122+ filters . selectedTypes
123+ )
93124 if ( matches ) {
94125 ; ( item as HTMLElement ) . style . display = ""
95126 visibleCount ++
@@ -132,54 +163,53 @@ export const useChangelogFilters = ({ products, networks, types, items }: UseCha
132163 }
133164 }
134165 }
135- } , [ searchTerm , selectedProducts , selectedNetworks , selectedTypes , items ] )
166+ } , [ filters , items ] )
136167
137168 const handleSearchChange = ( value : string ) => {
138- setSearchTerm ( value )
169+ setFilters ( ( prev ) => ( { ... prev , searchTerm : value } ) )
139170 }
140171
141172 const handleSearchToggle = ( expanded : boolean ) => {
142- setSearchExpanded ( expanded )
173+ setFilters ( ( prev ) => ( { ... prev , searchExpanded : expanded } ) )
143174 }
144175
145176 const toggleSelection = ( type : "product" | "network" | "type" , value : string ) => {
146- switch ( type ) {
147- case "product" :
148- setSelectedProducts ( ( prev ) => toggleItemInArray ( prev , value ) )
149- break
150- case "network" :
151- setSelectedNetworks ( ( prev ) => toggleItemInArray ( prev , value ) )
152- break
153- case "type" :
154- setSelectedTypes ( ( prev ) => toggleItemInArray ( prev , value ) )
155- break
156- }
177+ setFilters ( ( prev ) => {
178+ switch ( type ) {
179+ case "product" :
180+ return { ...prev , selectedProducts : toggleItemInArray ( prev . selectedProducts , value ) }
181+ case "network" :
182+ return { ...prev , selectedNetworks : toggleItemInArray ( prev . selectedNetworks , value ) }
183+ case "type" :
184+ return { ...prev , selectedTypes : toggleItemInArray ( prev . selectedTypes , value ) }
185+ default :
186+ return prev
187+ }
188+ } )
157189 }
158190
159191 const clearProductFilters = ( ) => {
160- setSelectedProducts ( [ ] )
192+ setFilters ( ( prev ) => ( { ... prev , selectedProducts : [ ] } ) )
161193 }
162194
163195 const clearNetworkFilters = ( ) => {
164- setSelectedNetworks ( [ ] )
196+ setFilters ( ( prev ) => ( { ... prev , selectedNetworks : [ ] } ) )
165197 }
166198
167199 const clearTypeFilters = ( ) => {
168- setSelectedTypes ( [ ] )
200+ setFilters ( ( prev ) => ( { ... prev , selectedTypes : [ ] } ) )
169201 }
170202
171203 const clearAllFilters = ( ) => {
172- setSelectedProducts ( [ ] )
173- setSelectedNetworks ( [ ] )
174- setSelectedTypes ( [ ] )
204+ setFilters ( ( prev ) => ( { ...prev , selectedProducts : [ ] , selectedNetworks : [ ] , selectedTypes : [ ] } ) )
175205 }
176206
177207 return {
178- searchExpanded,
179- searchTerm,
180- selectedProducts,
181- selectedNetworks,
182- selectedTypes,
208+ searchExpanded : filters . searchExpanded ,
209+ searchTerm : filters . searchTerm ,
210+ selectedProducts : filters . selectedProducts ,
211+ selectedNetworks : filters . selectedNetworks ,
212+ selectedTypes : filters . selectedTypes ,
183213 handleSearchChange,
184214 handleSearchToggle,
185215 toggleSelection,
0 commit comments