From fecece8803d5a3ece2718ae71cc57bb72ed39531 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 30 May 2026 04:00:44 -0700 Subject: [PATCH] fix: guard ghostty import async state (#3623) --- .../settings/useGhosttyImport.test.ts | 4 ++++ .../components/settings/useGhosttyImport.ts | 22 ++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/renderer/src/components/settings/useGhosttyImport.test.ts b/src/renderer/src/components/settings/useGhosttyImport.test.ts index 70e1ddb9ae8..cb4c21d5426 100644 --- a/src/renderer/src/components/settings/useGhosttyImport.test.ts +++ b/src/renderer/src/components/settings/useGhosttyImport.test.ts @@ -31,6 +31,10 @@ vi.mock('react', async () => { const actual = await vi.importActual('react') // eslint-disable-line @typescript-eslint/consistent-type-imports -- vi.importActual requires inline import() return { ...actual, + useEffect: (effect: () => void | (() => void)) => { + void effect() + }, + useRef: (initial: unknown) => ({ current: initial }), useState: (initial: unknown) => { const i = mockStateIndex++ if (mockStateValues[i] === undefined) { diff --git a/src/renderer/src/components/settings/useGhosttyImport.ts b/src/renderer/src/components/settings/useGhosttyImport.ts index 464b2c281bc..8031008d21a 100644 --- a/src/renderer/src/components/settings/useGhosttyImport.ts +++ b/src/renderer/src/components/settings/useGhosttyImport.ts @@ -1,5 +1,6 @@ import { useState } from 'react' import type { GhosttyImportPreview, GlobalSettings } from '../../../../shared/types' +import { useMountedRef } from '../../hooks/useMountedRef' export type UseGhosttyImportReturn = { open: boolean @@ -25,18 +26,25 @@ export function useGhosttyImport( const [loading, setLoading] = useState(false) const [applied, setApplied] = useState(false) const [applyError, setApplyError] = useState(null) + const mountedRef = useMountedRef() async function handleClick(): Promise { setOpen(true) setLoading(true) try { const result = await window.api.settings.previewGhosttyImport() - setPreview(result) + if (mountedRef.current) { + setPreview(result) + } } catch (err) { const message = err instanceof Error ? err.message : 'Unknown error' - setPreview({ found: false, diff: {}, unsupportedKeys: [], error: message }) + if (mountedRef.current) { + setPreview({ found: false, diff: {}, unsupportedKeys: [], error: message }) + } } finally { - setLoading(false) + if (mountedRef.current) { + setLoading(false) + } } } @@ -61,10 +69,14 @@ export function useGhosttyImport( // must keep the modal in its "unapplied" state and surface the error so // the user doesn't see a false success. await updateSettings(merged) - setApplied(true) + if (mountedRef.current) { + setApplied(true) + } } catch (err) { const message = err instanceof Error ? err.message : 'Failed to apply settings' - setApplyError(message) + if (mountedRef.current) { + setApplyError(message) + } } }