Fix race condition in useEffect hooks and type assertion bug (#2827)

- Add active flag pattern to 3 useEffect hooks in App.tsx to prevent
  event listener leaks when component unmounts before async setup completes
- Add guard check in useSettings.ts to prevent undefined from being
  stored in localStorage when payload.language is missing

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Zylo
2026-05-18 09:36:44 +08:00
committed by GitHub
parent 4f0f103a8a
commit 8786f44c5a
2 changed files with 28 additions and 12 deletions
+26 -5
View File
@@ -333,34 +333,42 @@ function App() {
useEffect(() => {
let unsubscribe: (() => void) | undefined;
let active = true;
const setupListener = async () => {
try {
unsubscribe = await providersApi.onSwitched(
const off = await providersApi.onSwitched(
async (event: ProviderSwitchEvent) => {
if (event.appType === activeApp) {
await refetch();
}
},
);
if (!active) {
off();
return;
}
unsubscribe = off;
} catch (error) {
console.error("[App] Failed to subscribe provider switch event", error);
}
};
setupListener();
void setupListener();
return () => {
active = false;
unsubscribe?.();
};
}, [activeApp, refetch]);
useEffect(() => {
let unsubscribe: (() => void) | undefined;
let active = true;
const setupListener = async () => {
try {
const { listen } = await import("@tauri-apps/api/event");
unsubscribe = await listen("universal-provider-synced", async () => {
const off = await listen("universal-provider-synced", async () => {
await queryClient.invalidateQueries({ queryKey: ["providers"] });
try {
await providersApi.updateTrayMenu();
@@ -368,6 +376,11 @@ function App() {
console.error("[App] Failed to update tray menu", error);
}
});
if (!active) {
off();
return;
}
unsubscribe = off;
} catch (error) {
console.error(
"[App] Failed to subscribe universal-provider-synced event",
@@ -376,8 +389,9 @@ function App() {
}
};
setupListener();
void setupListener();
return () => {
active = false;
unsubscribe?.();
};
}, [queryClient]);
@@ -429,9 +443,10 @@ function App() {
// Listen for proxy-official-warning: warn when takeover is enabled with an official provider
useEffect(() => {
let unsubscribe: (() => void) | undefined;
let active = true;
const setup = async () => {
unsubscribe = await listen("proxy-official-warning", (event) => {
const off = await listen("proxy-official-warning", (event) => {
const { providerName } = event.payload as {
appType: string;
providerName: string;
@@ -444,10 +459,16 @@ function App() {
{ duration: 8000 },
);
});
if (!active) {
off();
return;
}
unsubscribe = off;
};
void setup();
return () => {
active = false;
unsubscribe?.();
};
}, [t]);
+2 -7
View File
@@ -14,8 +14,6 @@ import {
} from "./useDirectorySettings";
import { useSettingsMetadata } from "./useSettingsMetadata";
type Language = "zh" | "en" | "ja";
interface SaveResult {
requiresRestart: boolean;
}
@@ -402,11 +400,8 @@ export function useSettings(): UseSettingsResult {
);
try {
if (typeof window !== "undefined") {
window.localStorage.setItem(
"language",
payload.language as Language,
);
if (typeof window !== "undefined" && payload.language) {
window.localStorage.setItem("language", payload.language);
}
} catch (error) {
console.warn(