Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function SearchAdvancedFiltersButton({queryJSON}: SearchAdvancedFiltersButtonPro
const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout();
const expensifyIcons = useMemoizedLazyExpensifyIcons(['Filter']);
const filterFormValues = useFilterFormValues(queryJSON);
useSearchFilterSync(filterFormValues);
useSearchFilterSync(queryJSON, filterFormValues);

const openAdvancedFilters = () => {
updateAdvancedFilters(filterFormValues);
Expand Down
51 changes: 26 additions & 25 deletions src/hooks/useSearchFilterSync.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,44 @@
import {useIsFocused} from '@react-navigation/native';
import {useEffect, useRef} from 'react';
import {useEffect} from 'react';
import type {SearchQueryJSON} from '@components/Search/types';
import {updateAdvancedFilters} from '@libs/actions/Search';
import {buildSearchQueryString} from '@libs/SearchQueryUtils';
import type {SearchAdvancedFiltersForm} from '@src/types/form';

/**
* Syncs computed filter form values to the SEARCH_ADVANCED_FILTERS_FORM Onyx key
* whenever they change. Call from SearchAdvanceFiltersButton which already computes formValues
* via useFilterFormValues.
*
* The isFocused guard prevents the blurred (previous) SearchPage instance—kept
* mounted during navigation transition animations—from overwriting the Onyx form
* state that was already written by the newly focused instance.
*
* On narrow layout (iOS native), navigating to Advanced Filters causes this screen
* to lose focus. When the user returns, the screen regains focus and this effect
* would re-fire. Without the prevIsFocusedRef guard below, it would overwrite any
* form changes made by Advanced Filters (e.g. resetting a date range) with stale
* values derived from the unchanged URL query parameter.
* Module-level: tracks the last URL query signature that was synced into the
* SEARCH_ADVANCED_FILTERS_FORM Onyx key. We deliberately keep this outside the
* hook so it survives unmount/remount of SearchAdvancedFiltersButton. The
* button is gated by SearchActionsBarSwitch (`showStatic` flips during
* `startTransition` after navigation) which causes the hook to remount and
* would otherwise reset a per-component ref to null — causing the sync to
* fire again with form values derived from the *old* URL, clobbering any
* Onyx.merge that was just done by the Advanced Filters flow (e.g. Save Date
* before the URL has been replaced by View Results).
*/
function useSearchFilterSync(formValues: Partial<SearchAdvancedFiltersForm>) {
let lastSyncedQuerySig: string | null = null;

/**
* Syncs computed filter form values to the SEARCH_ADVANCED_FILTERS_FORM Onyx
* key when the URL query string actually changes. The form is the source for
* the filter pills shown in the Search header — overwriting it on every
* re-render (or every fresh mount with the same URL) would erase concurrent
* Onyx.merge writes from Advanced Filters and leave the pill missing.
*/
function useSearchFilterSync(queryJSON: SearchQueryJSON | undefined, formValues: Partial<SearchAdvancedFiltersForm>) {
const isFocused = useIsFocused();
const prevIsFocusedRef = useRef(isFocused);

useEffect(() => {
const wasPreviouslyFocused = prevIsFocusedRef.current;
prevIsFocusedRef.current = isFocused;

if (!isFocused) {
return;
}

// Skip syncing when just regaining focus to avoid overwriting form
// changes made while this screen was unfocused (e.g. Advanced Filters).
if (!wasPreviouslyFocused) {
const querySig = queryJSON ? buildSearchQueryString(queryJSON) : null;
if (lastSyncedQuerySig === querySig) {
return;
Comment on lines +36 to 37
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Re-sync filters when form values change for same query

This guard skips all updates when the query signature is unchanged, but formValues can change independently of the URL because useFilterFormValues is derived from async Onyx data (personal details, reports, policy tags/categories) and buildFilterFormValuesFromQuery() filters against those collections. In cold-load/deeplink flows, the first sync can write an incomplete form; after those collections hydrate, this early return blocks the follow-up updateAdvancedFilters(..., true), leaving SEARCH_ADVANCED_FILTERS_FORM stale and causing active pills/advanced-filter values to remain missing until the URL query changes.

Useful? React with 👍 / 👎.

}

lastSyncedQuerySig = querySig;
updateAdvancedFilters(formValues, true);
}, [formValues, isFocused]);
}, [queryJSON, formValues, isFocused]);
}

export default useSearchFilterSync;
Loading