prevent duplicate search when navigating back with a new hashtag (#9579)

Tapping a hashtag while search screen was showing previous results will
cause both useEffect and useDidUpdate to fire causing two concurrent search
calls. This guard prevents that scenario.
This commit is contained in:
Carlos Garcia 2026-03-17 11:12:39 +01:00 committed by GitHub
parent 5365109ca8
commit 82e5ff2ae8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 57 additions and 0 deletions

View file

@ -75,6 +75,10 @@ describe('SearchScreen', () => {
jest.clearAllMocks();
});
afterEach(() => {
jest.restoreAllMocks();
});
it('renders search screen correctly', () => {
const {getByTestId, getByText, getByPlaceholderText} = renderWithEverything(
<SearchScreen {...baseProps}/>,
@ -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(<SearchScreen {...baseProps}/>, {database});
// Initial focus with #old triggers useEffect search
await act(async () => {
currentIsFocused = true;
rerender(<SearchScreen {...baseProps}/>);
});
await waitFor(() => expect(searchPosts).toHaveBeenCalledWith(
expect.any(String), 'team1', expect.objectContaining({terms: '#old'}),
));
// Navigate away
await act(async () => {
currentIsFocused = false;
rerender(<SearchScreen {...baseProps}/>);
});
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(<SearchScreen {...baseProps}/>);
});
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: () => ({

View file

@ -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(() => {