fix race condition

This commit is contained in:
2026-03-19 12:13:37 +08:00
parent 5d96c565c1
commit ace38a84e2
2 changed files with 38 additions and 14 deletions

View File

@@ -116,26 +116,33 @@ const AppDetail: React.FC = () => {
async (onSuccessCallback?: () => Promise<void> | void) => {
if (!currentDomain || !selectedAppId) return undefined;
// Capture at request time to detect staleness after awaits
const capturedDomainId = currentDomain.id;
const capturedAppId = selectedAppId;
setLoading(true);
try {
const result = await window.api.getAppDetail({
domainId: currentDomain.id,
appId: selectedAppId,
domainId: capturedDomainId,
appId: capturedAppId,
});
// Check if we're still on the same app and component is mounted before updating
if (result.success) {
setCurrentApp(result.data);
// Store revision after callback to avoid being cleared by clearChanges
const revision = result.data.customization?.revision;
// Call the callback if provided
// Guard: discard stale responses from previous domain/app switches
const nowDomainId = useDomainStore.getState().currentDomain?.id;
const nowAppId = useAppStore.getState().selectedAppId;
if (nowDomainId !== capturedDomainId || nowAppId !== capturedAppId) return undefined;
if (onSuccessCallback) {
await onSuccessCallback();
}
// Store the revision from Kintone API for remote change detection
// Store revision after callback to avoid being cleared by clearChanges
const revision = result.data.customization?.revision;
setCurrentApp(result.data);
// Must be AFTER callback since clearChanges() resets serverRevision to null
if (revision && currentDomain) {
fileChangeStore.setServerRevision(currentDomain.id, selectedAppId, revision);
// Only update knownRevision when user explicitly refreshes or after deployment
if (revision && shouldUpdateKnownRevision) {
fileChangeStore.setServerRevision(capturedDomainId, capturedAppId, revision);
}
return result.data;
}
@@ -144,7 +151,13 @@ const AppDetail: React.FC = () => {
console.error("Failed to load app detail:", error);
return undefined;
} finally {
setLoading(false);
// Only reset loading if still on the same context; otherwise the new
// request's loading state should not be cleared by this stale response.
const nowDomainId = useDomainStore.getState().currentDomain?.id;
const nowAppId = useAppStore.getState().selectedAppId;
if (nowDomainId === capturedDomainId && nowAppId === capturedAppId) {
setLoading(false);
}
}
},
[currentDomain, selectedAppId, setCurrentApp, setLoading]
@@ -160,6 +173,9 @@ const AppDetail: React.FC = () => {
// Initialize file change store from Kintone data
useEffect(() => {
if (!currentApp || !currentDomain || !selectedAppId) return;
// Guard against race condition: currentApp may be a stale response from a
// previous app's request that resolved after selectedAppId already changed.
if (String(currentApp.appId) !== String(selectedAppId)) return;
const customize = currentApp.customization;
if (!customize) return;

View File

@@ -88,23 +88,31 @@ const AppList: React.FC = () => {
const handleLoadApps = async () => {
if (!currentDomain) return;
const domainIdAtStart = currentDomain.id;
setLoading(true);
setError(null);
try {
const result = await window.api.getApps({
domainId: currentDomain.id,
domainId: domainIdAtStart,
});
// Discard result if domain switched during the request
if (useDomainStore.getState().currentDomain?.id !== domainIdAtStart) return;
if (result.success) {
setApps(result.data);
} else {
setError(result.error || t("loadAppsFailed"));
}
} catch (err) {
setError(err instanceof Error ? err.message : t("loadAppsFailed"));
if (useDomainStore.getState().currentDomain?.id === domainIdAtStart) {
setError(err instanceof Error ? err.message : t("loadAppsFailed"));
}
} finally {
setLoading(false);
if (useDomainStore.getState().currentDomain?.id === domainIdAtStart) {
setLoading(false);
}
}
};