History

Optimize websocket reconnect toast rerenders

Cursor Agent committed 5 months ago

Commit 92d5c511 parent 85d15171 file · +89 −33Browse files

Unified · Split

apps/web/src/components/WebSocketConnectionSurface.tsx+89−33
@@ -1,4 +1,4 @@
1−import { type ReactNode, useEffect, useEffectEvent, useRef, useState } from "react";
1+import { type ReactNode, type RefObject, useEffect, useEffectEvent, useRef } from "react";
22
33 import { type SlowRpcAckRequest, useSlowRpcAckRequests } from "../rpc/requestLatencyState";
44 import {
@@ -32,11 +32,6 @@ function formatConnectionMoment(isoDate: string | null): string | null { · 27 unmodified lines
3232 return connectionTimeFormatter.format(new Date(isoDate));
3333 }
3434
35−function formatRetryCountdown(nextRetryAt: string, nowMs: number): string {
36− const remainingMs = Math.max(0, new Date(nextRetryAt).getTime() - nowMs);
37− return `${Math.max(1, Math.ceil(remainingMs / 1000))}s`;
38−}
39−
4035 function describeOfflineToast(): string {
4136 return "WebSocket disconnected. Waiting for network.";
4237 }
@@ -53,6 +48,17 @@ function describeExhaustedToast(): string { · 10 unmodified lines
5348 return "Retries exhausted trying to reconnect";
5449 }
5550
51+function describeReconnectToast(status: WsConnectionStatus): string {
52+ const attemptLabel = formatReconnectAttemptLabel(status);
53+ if (status.nextRetryAt === null) {
54+ return `Reconnecting... ${attemptLabel}`;
55+ }
56+
57+ return `Next reconnect attempt is scheduled for ${
58+ formatConnectionMoment(status.nextRetryAt) ?? status.nextRetryAt
59+ }. ${attemptLabel}`;
60+}
61+
5662 function getConnectionDisplayName(status: WsConnectionStatus): string {
5763 return status.connectionLabel?.trim() || "T3 Server";
5864 }
@@ -145,15 +151,37 @@ export function shouldRestartStalledReconnect( · 86 unmodified lines
145151 );
146152 }
147153
148−export function WebSocketConnectionCoordinator() {
149− const status = useWsConnectionStatus();
150− const [nowMs, setNowMs] = useState(() => Date.now());
154+interface WebSocketConnectionRefs {
155+ readonly lastForcedReconnectAtRef: RefObject<number>;
156+ readonly previousDisconnectedAtRef: RefObject<string | null>;
157+ readonly previousUiStateRef: RefObject<WsConnectionUiState>;
158+ readonly toastIdRef: RefObject<ReturnType<typeof toastManager.add> | null>;
159+ readonly toastResetTimerRef: RefObject<number | null>;
160+}
161+
162+function useWebSocketConnectionRefs(status: WsConnectionStatus): WebSocketConnectionRefs {
151163 const lastForcedReconnectAtRef = useRef(0);
152164 const toastIdRef = useRef<ReturnType<typeof toastManager.add> | null>(null);
153165 const toastResetTimerRef = useRef<number | null>(null);
154− const previousUiStateRef = useRef<WsConnectionUiState>(getWsConnectionUiState(status));
166+ const previousUiStateRef = useRef<WsConnectionUiState | null>(null);
155167 const previousDisconnectedAtRef = useRef<string | null>(status.disconnectedAt);
168+ if (previousUiStateRef.current === null) {
169+ previousUiStateRef.current = getWsConnectionUiState(status);
170+ }
156171
172+ return {
173+ lastForcedReconnectAtRef,
174+ previousDisconnectedAtRef,
175+ previousUiStateRef: previousUiStateRef as RefObject<WsConnectionUiState>,
176+ toastIdRef,
177+ toastResetTimerRef,
178+ };
179+}
180+
181+function useWebSocketReconnectActions(
182+ lastForcedReconnectAtRef: RefObject<number>,
183+ toastResetTimerRef: RefObject<number | null>,
184+) {
157185 const runReconnect = useEffectEvent((showFailureToast: boolean) => {
158186 if (toastResetTimerRef.current !== null) {
159187 window.clearTimeout(toastResetTimerRef.current);
@@ -201,6 +229,18 @@ export function WebSocketConnectionCoordinator() { · 41 unmodified lines
201229 runReconnect(false);
202230 });
203231
232+ return {
233+ runReconnect,
234+ syncBrowserOnlineStatus,
235+ triggerAutoReconnect,
236+ triggerManualReconnect,
237+ };
238+}
239+
240+function useWebSocketAutoReconnectEvents(
241+ syncBrowserOnlineStatus: () => void,
242+ triggerAutoReconnect: (trigger: WsAutoReconnectTrigger) => void,
243+) {
204244 useEffect(() => {
205245 const handleOnline = () => {
206246 triggerAutoReconnect("online");
@@ -219,22 +259,12 @@ export function WebSocketConnectionCoordinator() { · 12 unmodified lines
219259 window.removeEventListener("focus", handleFocus);
220260 };
221261 }, []);
262+}
222263
223− useEffect(() => {
224− if (status.reconnectPhase !== "waiting" || status.nextRetryAt === null) {
225− return;
226− }
227−
228− setNowMs(Date.now());
229− const intervalId = window.setInterval(() => {
230− setNowMs(Date.now());
231− }, 1_000);
232−
233− return () => {
234− window.clearInterval(intervalId);
235− };
236− }, [status.nextRetryAt, status.reconnectPhase]);
237−
264+function useStalledReconnectGuard(
265+ status: WsConnectionStatus,
266+ runReconnect: (showFailureToast: boolean) => void,
267+) {
238268 useEffect(() => {
239269 if (
240270 status.reconnectPhase !== "waiting" ||
@@ -266,7 +296,14 @@ export function WebSocketConnectionCoordinator() { · 25 unmodified lines
266296 status.reconnectAttemptCount,
267297 status.reconnectPhase,
268298 ]);
299+}
269300
301+function useWebSocketConnectionToast(
302+ status: WsConnectionStatus,
303+ triggerManualReconnect: () => void,
304+ refs: WebSocketConnectionRefs,
305+) {
306+ const { previousDisconnectedAtRef, previousUiStateRef, toastIdRef, toastResetTimerRef } = refs;
270307 useEffect(() => {
271308 const uiState = getWsConnectionUiState(status);
272309 const previousUiState = previousUiStateRef.current;
@@ -316,10 +353,7 @@ export function WebSocketConnectionCoordinator() { · 43 unmodified lines
316353 data: {
317354 hideCopyButton: true,
318355 },
319− description:
320− status.nextRetryAt === null
321− ? `Reconnecting... ${formatReconnectAttemptLabel(status)}`
322− : `Reconnecting in ${formatRetryCountdown(status.nextRetryAt, nowMs)}... ${formatReconnectAttemptLabel(status)}`,
356+ description: describeReconnectToast(status),
323357 timeout: 0,
324358 title: buildReconnectTitle(status),
325359 type: "loading",
@@ -365,8 +399,10 @@ export function WebSocketConnectionCoordinator() { · 39 unmodified lines
365399
366400 previousUiStateRef.current = uiState;
367401 previousDisconnectedAtRef.current = status.disconnectedAt;
368− }, [nowMs, status]);
402+ }, [status]);
403+}
369404
405+function useToastResetTimerCleanup(toastResetTimerRef: RefObject<number | null>) {
370406 useEffect(() => {
371407 return () => {
372408 if (toastResetTimerRef.current !== null) {
@@ -374,13 +410,26 @@ export function WebSocketConnectionCoordinator() { · 1 unmodified line
374410 }
375411 };
376412 }, []);
413+}
414+
415+export function WebSocketConnectionCoordinator() {
416+ const status = useWsConnectionStatus();
417+ const refs = useWebSocketConnectionRefs(status);
418+ const { runReconnect, syncBrowserOnlineStatus, triggerAutoReconnect, triggerManualReconnect } =
419+ useWebSocketReconnectActions(refs.lastForcedReconnectAtRef, refs.toastResetTimerRef);
420+
421+ useWebSocketAutoReconnectEvents(syncBrowserOnlineStatus, triggerAutoReconnect);
422+ useStalledReconnectGuard(status, runReconnect);
423+ useWebSocketConnectionToast(status, triggerManualReconnect, refs);
424+ useToastResetTimerCleanup(refs.toastResetTimerRef);
377425
378426 return null;
379427 }
380428
381−export function SlowRpcAckToastCoordinator() {
382− const slowRequests = useSlowRpcAckRequests();
383− const status = useWsConnectionStatus();
429+function useSlowRpcAckToast(
430+ status: WsConnectionStatus,
431+ slowRequests: ReadonlyArray<SlowRpcAckRequest>,
432+) {
384433 const toastIdRef = useRef<ReturnType<typeof toastManager.add> | null>(null);
385434
386435 useEffect(() => {
@@ -418,6 +467,13 @@ export function SlowRpcAckToastCoordinator() { · 31 unmodified lines
418467 toastIdRef.current = toastManager.add(nextToast);
419468 }
420469 }, [slowRequests, status]);
470+}
471+
472+export function SlowRpcAckToastCoordinator() {
473+ const slowRequests = useSlowRpcAckRequests();
474+ const status = useWsConnectionStatus();
475+
476+ useSlowRpcAckToast(status, slowRequests);
421477
422478 return null;
423479 }

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t