|
1 | 1 | import {useIsFocused} from '@react-navigation/native'; |
2 | | -import {useEffect, useRef} from 'react'; |
| 2 | +import {useEffect} from 'react'; |
| 3 | +import type {SearchQueryJSON} from '@components/Search/types'; |
3 | 4 | import {updateAdvancedFilters} from '@libs/actions/Search'; |
| 5 | +import {buildSearchQueryString} from '@libs/SearchQueryUtils'; |
4 | 6 | import type {SearchAdvancedFiltersForm} from '@src/types/form'; |
5 | 7 |
|
6 | 8 | /** |
7 | | - * Syncs computed filter form values to the SEARCH_ADVANCED_FILTERS_FORM Onyx key |
8 | | - * whenever they change. Call from SearchAdvanceFiltersButton which already computes formValues |
9 | | - * via useFilterFormValues. |
10 | | - * |
11 | | - * The isFocused guard prevents the blurred (previous) SearchPage instance—kept |
12 | | - * mounted during navigation transition animations—from overwriting the Onyx form |
13 | | - * state that was already written by the newly focused instance. |
14 | | - * |
15 | | - * On narrow layout (iOS native), navigating to Advanced Filters causes this screen |
16 | | - * to lose focus. When the user returns, the screen regains focus and this effect |
17 | | - * would re-fire. Without the prevIsFocusedRef guard below, it would overwrite any |
18 | | - * form changes made by Advanced Filters (e.g. resetting a date range) with stale |
19 | | - * values derived from the unchanged URL query parameter. |
| 9 | + * Module-level: tracks the last URL query signature that was synced into the |
| 10 | + * SEARCH_ADVANCED_FILTERS_FORM Onyx key. We deliberately keep this outside the |
| 11 | + * hook so it survives unmount/remount of SearchAdvancedFiltersButton. The |
| 12 | + * button is gated by SearchActionsBarSwitch (`showStatic` flips during |
| 13 | + * `startTransition` after navigation) which causes the hook to remount and |
| 14 | + * would otherwise reset a per-component ref to null — causing the sync to |
| 15 | + * fire again with form values derived from the *old* URL, clobbering any |
| 16 | + * Onyx.merge that was just done by the Advanced Filters flow (e.g. Save Date |
| 17 | + * before the URL has been replaced by View Results). |
20 | 18 | */ |
21 | | -function useSearchFilterSync(formValues: Partial<SearchAdvancedFiltersForm>) { |
| 19 | +let lastSyncedQuerySig: string | null = null; |
| 20 | + |
| 21 | +/** |
| 22 | + * Syncs computed filter form values to the SEARCH_ADVANCED_FILTERS_FORM Onyx |
| 23 | + * key when the URL query string actually changes. The form is the source for |
| 24 | + * the filter pills shown in the Search header — overwriting it on every |
| 25 | + * re-render (or every fresh mount with the same URL) would erase concurrent |
| 26 | + * Onyx.merge writes from Advanced Filters and leave the pill missing. |
| 27 | + */ |
| 28 | +function useSearchFilterSync(queryJSON: SearchQueryJSON | undefined, formValues: Partial<SearchAdvancedFiltersForm>) { |
22 | 29 | const isFocused = useIsFocused(); |
23 | | - const prevIsFocusedRef = useRef(isFocused); |
24 | 30 |
|
25 | 31 | useEffect(() => { |
26 | | - const wasPreviouslyFocused = prevIsFocusedRef.current; |
27 | | - prevIsFocusedRef.current = isFocused; |
28 | | - |
29 | 32 | if (!isFocused) { |
30 | 33 | return; |
31 | 34 | } |
32 | | - |
33 | | - // Skip syncing when just regaining focus to avoid overwriting form |
34 | | - // changes made while this screen was unfocused (e.g. Advanced Filters). |
35 | | - if (!wasPreviouslyFocused) { |
| 35 | + const querySig = queryJSON ? buildSearchQueryString(queryJSON) : null; |
| 36 | + if (lastSyncedQuerySig === querySig) { |
36 | 37 | return; |
37 | 38 | } |
38 | | - |
| 39 | + lastSyncedQuerySig = querySig; |
39 | 40 | updateAdvancedFilters(formValues, true); |
40 | | - }, [formValues, isFocused]); |
| 41 | + }, [queryJSON, formValues, isFocused]); |
41 | 42 | } |
42 | 43 |
|
43 | 44 | export default useSearchFilterSync; |
0 commit comments