mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 11:43:57 +08:00
fix(ui): resolve Dialog/Modal not opening on first click
Two bugs were caused by ref synchronization race condition in commit 7d495aa:
- EditProviderDialog: provider prop was null on first render
- UsageScriptModal: conditional render guard was false on first click
Root cause: useEffect updates ref asynchronously, but render happens before
effect runs. On first click, ref is still null causing components to fail.
Solution: Create useLastValidValue hook that updates ref synchronously during
render phase instead of in useEffect. This ensures ref is always in sync with
state, eliminating the race condition.
Changes:
- Add useLastValidValue hook for preserving last valid value during animations
- Replace manual ref + useEffect pattern with the new hook
- Remove non-null assertions (!) that were needed as workaround
This commit is contained in:
+7
-18
@@ -26,6 +26,7 @@ import {
|
|||||||
import { checkAllEnvConflicts, checkEnvConflicts } from "@/lib/api/env";
|
import { checkAllEnvConflicts, checkEnvConflicts } from "@/lib/api/env";
|
||||||
import { useProviderActions } from "@/hooks/useProviderActions";
|
import { useProviderActions } from "@/hooks/useProviderActions";
|
||||||
import { useProxyStatus } from "@/hooks/useProxyStatus";
|
import { useProxyStatus } from "@/hooks/useProxyStatus";
|
||||||
|
import { useLastValidValue } from "@/hooks/useLastValidValue";
|
||||||
import { extractErrorMessage } from "@/utils/errorUtils";
|
import { extractErrorMessage } from "@/utils/errorUtils";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { AppSwitcher } from "@/components/AppSwitcher";
|
import { AppSwitcher } from "@/components/AppSwitcher";
|
||||||
@@ -73,21 +74,9 @@ function App() {
|
|||||||
const [envConflicts, setEnvConflicts] = useState<EnvConflict[]>([]);
|
const [envConflicts, setEnvConflicts] = useState<EnvConflict[]>([]);
|
||||||
const [showEnvBanner, setShowEnvBanner] = useState(false);
|
const [showEnvBanner, setShowEnvBanner] = useState(false);
|
||||||
|
|
||||||
// 保存最后一个有效的 provider,用于动画退出期间显示内容
|
// 使用 Hook 保存最后有效值,用于动画退出期间保持内容显示
|
||||||
const lastUsageProviderRef = useRef<Provider | null>(null);
|
const effectiveEditingProvider = useLastValidValue(editingProvider);
|
||||||
const lastEditingProviderRef = useRef<Provider | null>(null);
|
const effectiveUsageProvider = useLastValidValue(usageProvider);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (usageProvider) {
|
|
||||||
lastUsageProviderRef.current = usageProvider;
|
|
||||||
}
|
|
||||||
}, [usageProvider]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (editingProvider) {
|
|
||||||
lastEditingProviderRef.current = editingProvider;
|
|
||||||
}
|
|
||||||
}, [editingProvider]);
|
|
||||||
|
|
||||||
const promptPanelRef = useRef<any>(null);
|
const promptPanelRef = useRef<any>(null);
|
||||||
const mcpPanelRef = useRef<any>(null);
|
const mcpPanelRef = useRef<any>(null);
|
||||||
@@ -718,7 +707,7 @@ function App() {
|
|||||||
|
|
||||||
<EditProviderDialog
|
<EditProviderDialog
|
||||||
open={Boolean(editingProvider)}
|
open={Boolean(editingProvider)}
|
||||||
provider={lastEditingProviderRef.current}
|
provider={effectiveEditingProvider}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setEditingProvider(null);
|
setEditingProvider(null);
|
||||||
@@ -729,9 +718,9 @@ function App() {
|
|||||||
isProxyTakeover={isProxyRunning && isCurrentAppTakeoverActive}
|
isProxyTakeover={isProxyRunning && isCurrentAppTakeoverActive}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{lastUsageProviderRef.current && (
|
{effectiveUsageProvider && (
|
||||||
<UsageScriptModal
|
<UsageScriptModal
|
||||||
provider={lastUsageProviderRef.current}
|
provider={effectiveUsageProvider}
|
||||||
appId={activeApp}
|
appId={activeApp}
|
||||||
isOpen={Boolean(usageProvider)}
|
isOpen={Boolean(usageProvider)}
|
||||||
onClose={() => setUsageProvider(null)}
|
onClose={() => setUsageProvider(null)}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { useRef } from "react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存最后一个非 null/undefined 的值
|
||||||
|
* 用于 Dialog 关闭动画期间保持内容显示
|
||||||
|
*
|
||||||
|
* @param value 当前值
|
||||||
|
* @returns 当前值(如果有效)或最后一个有效值
|
||||||
|
*/
|
||||||
|
export function useLastValidValue<T>(value: T | null | undefined): T | null {
|
||||||
|
const ref = useRef<T | null>(null);
|
||||||
|
|
||||||
|
// 同步更新 ref(在渲染期间,不在 useEffect 中)
|
||||||
|
if (value != null) {
|
||||||
|
ref.current = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回当前值或最后有效值
|
||||||
|
return value ?? ref.current;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user