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:
Jason
2025-12-29 22:14:34 +08:00
parent 2651b65b10
commit 1be9c56ec5
2 changed files with 27 additions and 18 deletions

View File

@@ -26,6 +26,7 @@ import {
import { checkAllEnvConflicts, checkEnvConflicts } from "@/lib/api/env";
import { useProviderActions } from "@/hooks/useProviderActions";
import { useProxyStatus } from "@/hooks/useProxyStatus";
import { useLastValidValue } from "@/hooks/useLastValidValue";
import { extractErrorMessage } from "@/utils/errorUtils";
import { cn } from "@/lib/utils";
import { AppSwitcher } from "@/components/AppSwitcher";
@@ -73,21 +74,9 @@ function App() {
const [envConflicts, setEnvConflicts] = useState<EnvConflict[]>([]);
const [showEnvBanner, setShowEnvBanner] = useState(false);
// 保存最后一个有效的 provider,用于动画退出期间显示内容
const lastUsageProviderRef = useRef<Provider | null>(null);
const lastEditingProviderRef = useRef<Provider | null>(null);
useEffect(() => {
if (usageProvider) {
lastUsageProviderRef.current = usageProvider;
}
}, [usageProvider]);
useEffect(() => {
if (editingProvider) {
lastEditingProviderRef.current = editingProvider;
}
}, [editingProvider]);
// 使用 Hook 保存最后有效值,用于动画退出期间保持内容显示
const effectiveEditingProvider = useLastValidValue(editingProvider);
const effectiveUsageProvider = useLastValidValue(usageProvider);
const promptPanelRef = useRef<any>(null);
const mcpPanelRef = useRef<any>(null);
@@ -718,7 +707,7 @@ function App() {
<EditProviderDialog
open={Boolean(editingProvider)}
provider={lastEditingProviderRef.current}
provider={effectiveEditingProvider}
onOpenChange={(open) => {
if (!open) {
setEditingProvider(null);
@@ -729,9 +718,9 @@ function App() {
isProxyTakeover={isProxyRunning && isCurrentAppTakeoverActive}
/>
{lastUsageProviderRef.current && (
{effectiveUsageProvider && (
<UsageScriptModal
provider={lastUsageProviderRef.current}
provider={effectiveUsageProvider}
appId={activeApp}
isOpen={Boolean(usageProvider)}
onClose={() => setUsageProvider(null)}

View File

@@ -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;
}