Skip to content

Commit

Permalink
fix(next-app-router): prevent client-side search when rerendering (#6452
Browse files Browse the repository at this point in the history
)

* fix(next-app-router): prevent client-side search when rerendering

* ensure search is performed when remounted on client-side route change
  • Loading branch information
dhayab authored Dec 9, 2024
1 parent d68f02c commit 00aff64
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 2 deletions.
22 changes: 20 additions & 2 deletions packages/react-instantsearch-nextjs/src/InstantSearchNext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { safelyRunOnBrowser } from 'instantsearch.js/es/lib/utils';
import { headers } from 'next/headers';
import { usePathname } from 'next/navigation';
import React, { useEffect, useRef } from 'react';
import {
InstantSearch,
Expand All @@ -20,9 +21,11 @@ import type {
} from 'react-instantsearch-core';

const InstantSearchInitialResults = Symbol.for('InstantSearchInitialResults');
const InstantSearchLastPath = Symbol.for('InstantSearchLastPath');
declare global {
interface Window {
[InstantSearchInitialResults]?: InitialResults;
[InstantSearchLastPath]?: string;
}
}

Expand All @@ -47,6 +50,17 @@ export function InstantSearchNext<
...instantSearchProps
}: InstantSearchNextProps<TUiState, TRouteState>) {
const isMounting = useRef(true);
const isServer = typeof window === 'undefined';
const pathname = usePathname();
const hasRouteChanged =
!isServer &&
window[InstantSearchLastPath] &&
window[InstantSearchLastPath] !== pathname;

// We only want to trigger a search from a server environment
// or if a Next.js route change has happened on the client
const shouldTriggerSearch = isServer || hasRouteChanged;

useEffect(() => {
isMounting.current = false;
return () => {
Expand All @@ -55,6 +69,10 @@ export function InstantSearchNext<
};
}, []);

useEffect(() => {
window[InstantSearchLastPath] = pathname;
}, [pathname]);

const nonce = safelyRunOnBrowser(() => undefined, {
fallback: () => headers().get('x-nonce') || undefined,
});
Expand All @@ -77,9 +95,9 @@ This message will only be displayed in development mode.`
<InstantSearchRSCContext.Provider value={promiseRef}>
<InstantSearchSSRProvider initialResults={initialResults}>
<InstantSearch {...instantSearchProps} routing={routing}>
{!initialResults && <InitializePromise nonce={nonce} />}
{shouldTriggerSearch && <InitializePromise nonce={nonce} />}
{children}
{!initialResults && <TriggerSearch />}
{shouldTriggerSearch && <TriggerSearch />}
</InstantSearch>
</InstantSearchSSRProvider>
</InstantSearchRSCContext.Provider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* @jest-environment jsdom
*/

import { createSearchClient } from '@instantsearch/mocks';
import { wait } from '@instantsearch/testutils';
import { act, render } from '@testing-library/react';
import React from 'react';
import { SearchBox } from 'react-instantsearch';

import { InstantSearchNext } from '../InstantSearchNext';

const mockPathname = jest.fn();
jest.mock('next/navigation', () => ({
...jest.requireActual('next/navigation'),
usePathname() {
return mockPathname();
},
}));

describe('rerendering', () => {
const client = createSearchClient();

function Component() {
return (
<InstantSearchNext searchClient={client} indexName="indexName">
<SearchBox />
</InstantSearchNext>
);
}

beforeEach(() => {
(client.search as jest.Mock).mockClear();
});

it('does not trigger a client-side search by default', async () => {
const { rerender } = render(<Component />);

await act(async () => {
await wait(0);
});

rerender(<Component />);

await act(async () => {
await wait(0);
});

expect(client.search).toHaveBeenCalledTimes(0);
});

it('triggers a client-side search on route change', async () => {
mockPathname.mockImplementation(() => '/a');
const { rerender } = render(<Component />);

await act(async () => {
await wait(0);
});

mockPathname.mockImplementation(() => '/b');
rerender(<Component />);

await act(async () => {
await wait(0);
});

expect(client.search).not.toHaveBeenCalledTimes(0);
});
});

afterAll(() => {
jest.resetAllMocks();
});

0 comments on commit 00aff64

Please sign in to comment.