fix: connection status indicator stuck at idle after component remount#2104
Draft
fix: connection status indicator stuck at idle after component remount#2104
Conversation
Root cause: @apollo/client-react-streaming stores the Apollo Client as a
window singleton (window[Symbol.for('ApolloClientSingleton')]). makeClient()
is only called once per browser session. On ApolloClientProvider remount the
graphql-ws event handlers still reference the old setState (from the first
mount), so the new component's state is never updated and the indicator shows
'idle' forever.
Fix:
- Route all graphql-ws on:{} callbacks through module-level mutable refs
(wsConnectionStateCallback / wsErrorCallback) that are re-pointed to the
current component's setState in a useEffect on every mount/remount.
- Use useState lazy initialisers reading lastKnownConnectionState /
lastKnownError so a remounting component immediately shows the correct
indicator value.
- Store globalWsClient at module level so resetConnection() works correctly
even after remounts when the per-instance wsClientRef is null.
- Replace the per-instance isResettingRef with a module-level wsIsResettingRef
so it persists correctly alongside the singleton WS client.
Agent-Logs-Url: https://github.com/FIRSTIsrael/lems/sessions/24c16f1d-7f5e-4fcd-b13d-a9558eeb631f
Co-authored-by: johnmeshulam <55348702+johnmeshulam@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Investigate why connection status changes to idle randomly
fix: connection status indicator stuck at idle after component remount
Apr 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
After extended usage, the connection indicator would permanently show
idledespite the WebSocket remaining fully functional. Root cause was a window-singleton behaviour in@apollo/client-react-streaming.Root Cause
ApolloNextAppProviderstores the Apollo Client (and its embedded graphql-ws client) aswindow[Symbol.for('ApolloClientSingleton')] ??= makeClient()— called once per browser session. On anyApolloClientProviderremount (locale change, error-boundary recovery, etc.), the new component instance starts with freshuseState('idle'), but the graphql-wson:{}handlers still hold a closure over the first mount'ssetState. React silently dropssetStatecalls on unmounted components, so the new instance's state is never updated — stuck at'idle'forever while subscriptions continue working normally.Changes
Module-level forwarding callbacks (
wsConnectionStateCallback,wsErrorCallback): graphql-ws event handlers now call through mutable module-level refs instead of directly closing over a per-instancesetState.useEffect(empty deps) re-wires on every mount: points the module-level callbacks at the current component'ssetState; resets to no-ops on unmount so stale events don't touch an unmounted component.useStatelazy initialisers readlastKnownConnectionState/lastKnownError(updated before each callback invocation) so a remounting component immediately reflects the correct state without waiting for the next WS event.globalWsClient+ module-levelwsIsResettingRef: ensuresresetConnection()and theclosed-event guard work correctly after a remount where the per-instance React refs are null.Type of change
Screenshots
No UI changes — only the connection indicator's correctness is affected.
Checklist