InstantSearch renders null after repeated App Router navigation (deferred dispose not restarted)
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- nextjs, react, typescript
- Domain
- frontend
Research direction
Start in packages/react-instantsearch-core/src/lib/useInstantSearchApi.tsx and trace the deferred cleanup and re-subscribe branches around cleanupTimerRef. Reproduce the issue with two App Router pages and repeated soft navigation; done means plain InstantSearch continues rendering its search box and results instead of returning null after the disposed instance is reused.
Written by the indexing model from the issue text.
Description
Description
When using React InstantSearch with the Next.js App Router, navigating between two pages that each render <InstantSearch> causes the widget to render nothing (blank search box and results) after a few client-side navigations. <InstantSearch> returns null because search.started is false.
This happens with plain <InstantSearch> (not InstantSearchNext). It reproduces reliably after ~3 soft navigations and does not throw — the subtree just silently disappears.
Root cause
In useInstantSearchApi, dispose() is deferred on unmount via setTimeout(cleanup) and tracked in cleanupTimerRef, so an unmount immediately followed by a re-subscribe (a React "update") cancels the dispose:
// unmount / teardown
cleanupTimerRef.current = setTimeout(cleanup); // cleanup = () => search.dispose()
// re-subscribe
if (cleanupTimerRef.current === null) {
if (!search.started) { search.start(); forceUpdate(); }
} else {
// "just an update" — cancel the pending dispose, assume still started
clearTimeout(cleanupTimerRef.current);
search._preventWidgetCleanup = false;
}
This assumes the gap between teardown and re-subscribe is a single tick. The Next.js App Router preserves the page component fiber across navigation, so searchRef/cleanupTimerRef survive, but the user spends seconds on another route. By the time they navigate back and React re-subscribes:
- the deferred
setTimeout(cleanup)has already fired → the reused instance is disposed (started === false), and cleanupTimerRef.currentis still the (already-fired) timer id, i.e. non-null.
So the re-subscribe takes the else branch, calls clearTimeout on a dead timer, and never restarts the disposed instance. <InstantSearch> then renders null forever.
Steps to reproduce
- Next.js App Router (tested Next 16, React 19,
react-instantsearch7.33.0). - Two routes, each rendering its own
<InstantSearch>(differentindexName). - Soft-navigate A → B → A → B a few times (via
<Link>). - After ~3 navigations the search box + results vanish;
search.startedisfalse.
Proposed fix
Reset cleanupTimerRef and restart the search if the reused instance was already disposed:
} else {
clearTimeout(cleanupTimerRef.current);
cleanupTimerRef.current = null;
search._preventWidgetCleanup = false;
// The deferred dispose may have already fired during a long navigation
// round-trip (e.g. App Router preserving the page fiber), leaving the
// reused instance disposed. Restart it so InstantSearch doesn't render null.
if (!search.started) {
search.start();
forceUpdate();
}
}
Verified against the reproduction above (headless-Chrome/CDP harness): failing 100% of the time → passing 100%.
InstantSearchNext sidesteps this because its provider sets ssrSearchRef, and the teardown short-circuits (if (serverState?.ssrSearchRef) return;) so the instance is never disposed — but plain <InstantSearch> + App Router hits the bug.
Environment
- react-instantsearch / react-instantsearch-core: 7.33.0
- react / react-dom: 19
- next: 16 (App Router)
Source: packages/react-instantsearch-core/src/lib/useInstantSearchApi.tsx
- Dominant language
- TypeScript
- Stars
- 4.1k
- Forks
- 554
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 43
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from algolia/instantsearch
-
Library: Create InstantSearch App
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
algolia/instantsearch#5195 · 1 reaction ·
-
Difficulty 5/5 Over a week Newbie friendliness 28/100
algolia/instantsearch#7239 ·
-
triage
Difficulty 4/5 3-5 days Newbie friendliness 55/100
algolia/instantsearch#7237 · 4 comments ·
-
Difficulty 5/5 Over a week Newbie friendliness 32/100
algolia/instantsearch#7234 ·
-
Difficulty 5/5 Over a week Newbie friendliness 30/100
algolia/instantsearch#7204 ·
All issues in algolia/instantsearch
Similar issues
-
calcite-components needs triage refactor
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Esri/calcite-design-system#15203 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 91/100
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Difficulty 1/5 Under an hour Newbie friendliness 95/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Automattic/studio#4908 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 90/100