From e0d9c4c2b082f02eb82164bf87557dd347fd40f4 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 30 May 2026 00:49:56 -0700 Subject: [PATCH] fix: guard ssh action state resets (#3533) --- .../src/components/settings/SshTargetCard.tsx | 24 +++++++++++++++---- .../src/components/sidebar/SshTargetRow.tsx | 16 +++++++++++-- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/renderer/src/components/settings/SshTargetCard.tsx b/src/renderer/src/components/settings/SshTargetCard.tsx index d5ac86a6bbf..c1b0c139320 100644 --- a/src/renderer/src/components/settings/SshTargetCard.tsx +++ b/src/renderer/src/components/settings/SshTargetCard.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { CircleStop, Loader2, @@ -85,13 +85,27 @@ export function SshTargetCard({ const terminateInFlight = actionInFlight === 'terminate' || busyAction === 'terminate' const resetInFlight = actionInFlight === 'reset' || busyAction === 'reset' const removeInFlight = busyAction === 'remove' + const mountedRef = useRef(true) + + useEffect(() => { + mountedRef.current = true + return () => { + mountedRef.current = false + } + }, []) + + const clearActionInFlight = (): void => { + if (mountedRef.current) { + setActionInFlight(null) + } + } const handleConnect = (): void => { if (actionInFlight) { return } setActionInFlight('connect') - Promise.resolve(onConnect(target.id)).finally(() => setActionInFlight(null)) + void Promise.resolve(onConnect(target.id)).finally(clearActionInFlight) } const handleDisconnect = (): void => { @@ -99,7 +113,7 @@ export function SshTargetCard({ return } setActionInFlight('disconnect') - Promise.resolve(onDisconnect(target.id)).finally(() => setActionInFlight(null)) + void Promise.resolve(onDisconnect(target.id)).finally(clearActionInFlight) } const handleTerminateSessions = (): void => { @@ -107,7 +121,7 @@ export function SshTargetCard({ return } setActionInFlight('terminate') - Promise.resolve(onTerminateSessions(target.id)).finally(() => setActionInFlight(null)) + void Promise.resolve(onTerminateSessions(target.id)).finally(clearActionInFlight) } const handleResetRelay = (): void => { @@ -115,7 +129,7 @@ export function SshTargetCard({ return } setActionInFlight('reset') - Promise.resolve(onResetRelay(target.id)).finally(() => setActionInFlight(null)) + void Promise.resolve(onResetRelay(target.id)).finally(clearActionInFlight) } const renderEndRemoteTerminalsButton = (): React.JSX.Element => ( diff --git a/src/renderer/src/components/sidebar/SshTargetRow.tsx b/src/renderer/src/components/sidebar/SshTargetRow.tsx index 35a67303bc1..30338596f07 100644 --- a/src/renderer/src/components/sidebar/SshTargetRow.tsx +++ b/src/renderer/src/components/sidebar/SshTargetRow.tsx @@ -4,7 +4,7 @@ * Why extracted: keeps AddRepoSteps.tsx under the 400-line oxlint limit * while isolating the inline-connect interaction logic. */ -import React, { useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { Loader2 } from 'lucide-react' import type { SshTarget, SshConnectionState } from '../../../../shared/ssh-types' @@ -22,6 +22,7 @@ export function SshTargetRow({ onConnect }: Props): React.JSX.Element { const [connecting, setConnecting] = useState(false) + const mountedRef = useRef(true) const status = target.state?.status ?? 'disconnected' const isConnected = status === 'connected' const isBusy = @@ -49,9 +50,20 @@ export function SshTargetRow({ return } setConnecting(true) - void onConnect(target.id).finally(() => setConnecting(false)) + void onConnect(target.id).finally(() => { + if (mountedRef.current) { + setConnecting(false) + } + }) } + useEffect(() => { + mountedRef.current = true + return () => { + mountedRef.current = false + } + }, []) + return (