diff --git a/app/screens/home/search/search.test.tsx b/app/screens/home/search/search.test.tsx index f88a3adf4..7a1dac3b3 100644 --- a/app/screens/home/search/search.test.tsx +++ b/app/screens/home/search/search.test.tsx @@ -75,6 +75,10 @@ describe('SearchScreen', () => { jest.clearAllMocks(); }); + afterEach(() => { + jest.restoreAllMocks(); + }); + it('renders search screen correctly', () => { const {getByTestId, getByText, getByPlaceholderText} = renderWithEverything( , @@ -238,6 +242,54 @@ describe('SearchScreen', () => { }); }); + it('should not trigger a duplicate search via useDidUpdate when refocused with a new searchTerm', async () => { + let currentIsFocused = false; + let currentSearchTerm = '#old'; + + jest.spyOn(require('@react-navigation/native'), 'useIsFocused').mockImplementation(() => currentIsFocused); + jest.spyOn(require('@react-navigation/native'), 'useNavigation').mockImplementation(() => ({ + getState: () => ({ + index: 0, + routes: [{params: {searchTerm: currentSearchTerm}}], + }), + })); + + const {rerender} = renderWithEverything(, {database}); + + // Initial focus with #old triggers useEffect search + await act(async () => { + currentIsFocused = true; + rerender(); + }); + + await waitFor(() => expect(searchPosts).toHaveBeenCalledWith( + expect.any(String), 'team1', expect.objectContaining({terms: '#old'}), + )); + + // Navigate away + await act(async () => { + currentIsFocused = false; + rerender(); + }); + + jest.clearAllMocks(); + + // Navigate back with a new hashtag — useDidUpdate and useEffect both fire, + // but useDidUpdate guard should skip since searchTerm !== lastSearchedValue + await act(async () => { + currentSearchTerm = '#new'; + currentIsFocused = true; + rerender(); + }); + + await waitFor(() => { + expect(searchPosts).toHaveBeenCalledTimes(1); + expect(searchPosts).toHaveBeenCalledWith( + expect.any(String), 'team1', expect.objectContaining({terms: '#new'}), + ); + }); + }); + it('populates searchTerm when navigating to screen with hashtag', async () => { const mockNavigation = { getState: () => ({ diff --git a/app/screens/home/search/search.tsx b/app/screens/home/search/search.tsx index ddb707323..63efdcdca 100644 --- a/app/screens/home/search/search.tsx +++ b/app/screens/home/search/search.tsx @@ -398,6 +398,11 @@ const SearchScreen = ({teamId, teams, crossTeamSearchEnabled}: Props) => { }, [isFocused]); useDidUpdate(() => { + if (searchTerm && searchTerm !== lastSearchedValue) { + // searchTerm changed (case for hashtag tap) — useEffect handles it; skip to avoid double search + return; + } + if (isFocused && lastSearchedValue && showResults) { // requestAnimationFrame for smooth UI updates requestAnimationFrame(() => {