From 83ea282b7d55ea382201eab7a558de0876a00712 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 30 May 2026 01:11:39 -0700 Subject: [PATCH] fix: guard ssh status async state (#3546) --- .../sidebar/SshDisconnectedDialog.tsx | 18 +++++++++++++++--- .../components/status-bar/SshStatusSegment.tsx | 18 +++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/renderer/src/components/sidebar/SshDisconnectedDialog.tsx b/src/renderer/src/components/sidebar/SshDisconnectedDialog.tsx index 50bbfec7b..b75c386a3 100644 --- a/src/renderer/src/components/sidebar/SshDisconnectedDialog.tsx +++ b/src/renderer/src/components/sidebar/SshDisconnectedDialog.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { toast } from 'sonner' import { Loader2, Server, ServerOff } from 'lucide-react' import { @@ -41,16 +41,28 @@ export function SshDisconnectedDialog({ status }: SshDisconnectedDialogProps): React.JSX.Element { const [connecting, setConnecting] = useState(false) + const mountedRef = useRef(true) + + useEffect(() => { + mountedRef.current = true + return () => { + mountedRef.current = false + } + }, []) const handleReconnect = useCallback(async () => { setConnecting(true) try { await window.api.ssh.connect({ targetId }) - onOpenChange(false) + if (mountedRef.current) { + onOpenChange(false) + } } catch (err) { toast.error(err instanceof Error ? err.message : 'Reconnection failed') } finally { - setConnecting(false) + if (mountedRef.current) { + setConnecting(false) + } } }, [targetId, onOpenChange]) diff --git a/src/renderer/src/components/status-bar/SshStatusSegment.tsx b/src/renderer/src/components/status-bar/SshStatusSegment.tsx index 31c129c6f..f59308378 100644 --- a/src/renderer/src/components/status-bar/SshStatusSegment.tsx +++ b/src/renderer/src/components/status-bar/SshStatusSegment.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react' +import React, { useCallback, useEffect, useRef, useState } from 'react' import { AlertTriangle, Cloud, Loader2, MonitorSmartphone, Server, ServerOff } from 'lucide-react' import { toast } from 'sonner' import { @@ -113,8 +113,16 @@ function TargetRow({ syncStatus: RemoteWorkspaceSyncStatus | undefined }): React.JSX.Element { const [busy, setBusy] = useState(false) + const mountedRef = useRef(true) const recordFeatureInteraction = useAppStore((s) => s.recordFeatureInteraction) + useEffect(() => { + mountedRef.current = true + return () => { + mountedRef.current = false + } + }, []) + const handleConnect = useCallback(async () => { setBusy(true) try { @@ -123,7 +131,9 @@ function TargetRow({ } catch (err) { toast.error(err instanceof Error ? err.message : 'Connection failed') } finally { - setBusy(false) + if (mountedRef.current) { + setBusy(false) + } } }, [recordFeatureInteraction, targetId]) @@ -135,7 +145,9 @@ function TargetRow({ } catch (err) { toast.error(err instanceof Error ? err.message : 'Disconnect failed') } finally { - setBusy(false) + if (mountedRef.current) { + setBusy(false) + } } }, [recordFeatureInteraction, targetId])