fix race condition
This commit is contained in:
@@ -116,26 +116,33 @@ const AppDetail: React.FC = () => {
|
|||||||
async (onSuccessCallback?: () => Promise<void> | void) => {
|
async (onSuccessCallback?: () => Promise<void> | void) => {
|
||||||
if (!currentDomain || !selectedAppId) return undefined;
|
if (!currentDomain || !selectedAppId) return undefined;
|
||||||
|
|
||||||
|
// Capture at request time to detect staleness after awaits
|
||||||
|
const capturedDomainId = currentDomain.id;
|
||||||
|
const capturedAppId = selectedAppId;
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const result = await window.api.getAppDetail({
|
const result = await window.api.getAppDetail({
|
||||||
domainId: currentDomain.id,
|
domainId: capturedDomainId,
|
||||||
appId: selectedAppId,
|
appId: capturedAppId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if we're still on the same app and component is mounted before updating
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
setCurrentApp(result.data);
|
// Guard: discard stale responses from previous domain/app switches
|
||||||
// Store revision after callback to avoid being cleared by clearChanges
|
const nowDomainId = useDomainStore.getState().currentDomain?.id;
|
||||||
const revision = result.data.customization?.revision;
|
const nowAppId = useAppStore.getState().selectedAppId;
|
||||||
// Call the callback if provided
|
if (nowDomainId !== capturedDomainId || nowAppId !== capturedAppId) return undefined;
|
||||||
|
|
||||||
if (onSuccessCallback) {
|
if (onSuccessCallback) {
|
||||||
await 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
|
// Must be AFTER callback since clearChanges() resets serverRevision to null
|
||||||
if (revision && currentDomain) {
|
// Only update knownRevision when user explicitly refreshes or after deployment
|
||||||
fileChangeStore.setServerRevision(currentDomain.id, selectedAppId, revision);
|
if (revision && shouldUpdateKnownRevision) {
|
||||||
|
fileChangeStore.setServerRevision(capturedDomainId, capturedAppId, revision);
|
||||||
}
|
}
|
||||||
return result.data;
|
return result.data;
|
||||||
}
|
}
|
||||||
@@ -144,7 +151,13 @@ const AppDetail: React.FC = () => {
|
|||||||
console.error("Failed to load app detail:", error);
|
console.error("Failed to load app detail:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
} finally {
|
} 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]
|
[currentDomain, selectedAppId, setCurrentApp, setLoading]
|
||||||
@@ -160,6 +173,9 @@ const AppDetail: React.FC = () => {
|
|||||||
// Initialize file change store from Kintone data
|
// Initialize file change store from Kintone data
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!currentApp || !currentDomain || !selectedAppId) return;
|
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;
|
const customize = currentApp.customization;
|
||||||
if (!customize) return;
|
if (!customize) return;
|
||||||
|
|||||||
@@ -88,23 +88,31 @@ const AppList: React.FC = () => {
|
|||||||
const handleLoadApps = async () => {
|
const handleLoadApps = async () => {
|
||||||
if (!currentDomain) return;
|
if (!currentDomain) return;
|
||||||
|
|
||||||
|
const domainIdAtStart = currentDomain.id;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await window.api.getApps({
|
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) {
|
if (result.success) {
|
||||||
setApps(result.data);
|
setApps(result.data);
|
||||||
} else {
|
} else {
|
||||||
setError(result.error || t("loadAppsFailed"));
|
setError(result.error || t("loadAppsFailed"));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} 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 {
|
} finally {
|
||||||
setLoading(false);
|
if (useDomainStore.getState().currentDomain?.id === domainIdAtStart) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user