Skip to content

Commit

Permalink
fix(next-app-router): better detection of route change (#6467)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhayab authored Dec 10, 2024
1 parent 57abee4 commit 8da8096
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 18 deletions.
25 changes: 12 additions & 13 deletions packages/react-instantsearch-nextjs/src/InstantSearchNext.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +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 React, { useEffect, useMemo, useRef } from 'react';
import {
InstantSearch,
InstantSearchRSCContext,
Expand All @@ -21,11 +20,9 @@ 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 @@ -51,11 +48,17 @@ export function InstantSearchNext<
}: InstantSearchNextProps<TUiState, TRouteState>) {
const isMounting = useRef(true);
const isServer = typeof window === 'undefined';
const pathname = usePathname();
const hasRouteChanged =
!isServer &&
window[InstantSearchLastPath] &&
window[InstantSearchLastPath] !== pathname;

const hasRouteChanged = useMemo(() => {
// On server, always return false
if (isServer) {
return false;
}

// On client, route has changed if initialResults have been cleaned up
const hasInitialResults = window[InstantSearchInitialResults] !== undefined;
return !hasInitialResults;
}, [isServer]);

// We only want to trigger a search from a server environment
// or if a Next.js route change has happened on the client
Expand All @@ -69,10 +72,6 @@ export function InstantSearchNext<
};
}, []);

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

const nonce = safelyRunOnBrowser(() => undefined, {
fallback: () => headers().get('x-nonce') || undefined,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ import { SearchBox } from 'react-instantsearch';

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

import type { InitialResults } from 'instantsearch.js';

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

const mockPathname = jest.fn();
jest.mock('next/navigation', () => ({
...jest.requireActual('next/navigation'),
Expand All @@ -31,6 +40,9 @@ describe('rerendering', () => {

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

// Simulate initialResults injection
window[InstantSearchInitialResults] = {};
});

it('does not trigger a client-side search by default', async () => {
Expand All @@ -50,21 +62,24 @@ describe('rerendering', () => {
});

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

// Unmount InstantSearch due to route change
await act(async () => {
await wait(0);
unmount();
await wait(0);
});

mockPathname.mockImplementation(() => '/b');
rerender(<Component />);
// Render InstantSearch on new route
render(<Component />);

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

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

Expand Down

0 comments on commit 8da8096

Please sign in to comment.