fix: update spit convert
This commit is contained in:
parent
b7679cbcb1
commit
c67984d32c
|
|
@ -11,19 +11,58 @@ export default function ChunkLoadErrorHandler() {
|
|||
useEffect(() => {
|
||||
// Key to track if we've already refreshed for this error
|
||||
const REFRESH_KEY = "chunk-load-error-refreshed";
|
||||
const REFRESH_TIMESTAMP_KEY = "chunk-load-error-timestamp";
|
||||
|
||||
// Flag to prevent multiple simultaneous refresh attempts
|
||||
let isRefreshing = false;
|
||||
|
||||
/**
|
||||
* Check if error is actually a ChunkLoadError
|
||||
* This is more specific to avoid false positives
|
||||
*/
|
||||
const isChunkLoadError = (error: any, message?: string): boolean => {
|
||||
// Direct ChunkLoadError check
|
||||
if (error?.name === "ChunkLoadError") return true;
|
||||
|
||||
// Check for specific chunk loading patterns (for webpack/next.js)
|
||||
const errorMessage = error?.message || message || "";
|
||||
const isChunkError =
|
||||
errorMessage.includes("Loading chunk") ||
|
||||
errorMessage.includes("ChunkLoadError") ||
|
||||
(errorMessage.includes("Failed to fetch") && errorMessage.includes("_next/static")) ||
|
||||
(errorMessage.includes("dynamically imported module") && errorMessage.includes("_next"));
|
||||
|
||||
// Exclude normal network errors or API errors
|
||||
const isNotApiError =
|
||||
!errorMessage.includes("/api/") &&
|
||||
!errorMessage.includes("http") &&
|
||||
!errorMessage.includes("https") &&
|
||||
!errorMessage.includes("service/") &&
|
||||
!errorMessage.includes("endpoint");
|
||||
|
||||
return isChunkError && isNotApiError;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if we should refresh based on time
|
||||
* Prevent refresh if last refresh was less than 5 seconds ago
|
||||
*/
|
||||
const shouldRefresh = (): boolean => {
|
||||
const lastRefreshTime = sessionStorage.getItem(REFRESH_TIMESTAMP_KEY);
|
||||
if (!lastRefreshTime) return true;
|
||||
|
||||
const timeSinceLastRefresh = Date.now() - parseInt(lastRefreshTime, 10);
|
||||
return timeSinceLastRefresh > 5000; // 5 seconds
|
||||
};
|
||||
|
||||
// Handle unhandled promise rejections (for dynamic imports)
|
||||
const handlePromiseRejection = (event: PromiseRejectionEvent) => {
|
||||
const error = event.reason;
|
||||
|
||||
// Check if it's a ChunkLoadError
|
||||
if (
|
||||
error?.name === "ChunkLoadError" ||
|
||||
error?.message?.includes("Loading chunk") ||
|
||||
error?.message?.includes("ChunkLoadError") ||
|
||||
error?.message?.includes("Failed to fetch dynamically imported module") ||
|
||||
error?.message?.includes("failed to load resource")
|
||||
) {
|
||||
// Only handle if it's actually a ChunkLoadError
|
||||
if (isChunkLoadError(error)) {
|
||||
console.warn("ChunkLoadError detected in promise rejection:", error);
|
||||
event.preventDefault(); // Prevent default error handling
|
||||
handleChunkLoadError();
|
||||
}
|
||||
};
|
||||
|
|
@ -32,46 +71,72 @@ export default function ChunkLoadErrorHandler() {
|
|||
const handleError = (event: ErrorEvent) => {
|
||||
const error = event.error;
|
||||
|
||||
// Check if it's a ChunkLoadError
|
||||
if (
|
||||
error?.name === "ChunkLoadError" ||
|
||||
event.message?.includes("Loading chunk") ||
|
||||
event.message?.includes("ChunkLoadError") ||
|
||||
event.message?.includes("Failed to fetch dynamically imported module")
|
||||
) {
|
||||
// Only handle if it's actually a ChunkLoadError
|
||||
if (isChunkLoadError(error, event.message)) {
|
||||
console.warn("ChunkLoadError detected in error event:", error);
|
||||
event.preventDefault(); // Prevent default error handling
|
||||
handleChunkLoadError();
|
||||
}
|
||||
};
|
||||
|
||||
const handleChunkLoadError = () => {
|
||||
// Check if we've already refreshed
|
||||
// Prevent multiple simultaneous refresh attempts
|
||||
if (isRefreshing) {
|
||||
console.log("Refresh already in progress, skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we've already refreshed recently
|
||||
const hasRefreshed = sessionStorage.getItem(REFRESH_KEY);
|
||||
|
||||
if (!hasRefreshed) {
|
||||
console.log("ChunkLoadError detected. Refreshing page...");
|
||||
if (!hasRefreshed && shouldRefresh()) {
|
||||
isRefreshing = true;
|
||||
|
||||
console.log("ChunkLoadError confirmed. Refreshing page in 100ms...");
|
||||
|
||||
// Mark that we've refreshed
|
||||
sessionStorage.setItem(REFRESH_KEY, "true");
|
||||
sessionStorage.setItem(REFRESH_TIMESTAMP_KEY, Date.now().toString());
|
||||
|
||||
// Force reload the page (bypassing cache)
|
||||
window.location.reload();
|
||||
} else {
|
||||
// Small delay to allow logging and prevent immediate re-trigger
|
||||
setTimeout(() => {
|
||||
// Force reload the page (bypassing cache)
|
||||
window.location.reload();
|
||||
}, 100);
|
||||
} else if (hasRefreshed) {
|
||||
console.error("ChunkLoadError persists after refresh. There might be a deeper issue.");
|
||||
console.error("Please try clearing your browser cache or contact support.");
|
||||
|
||||
// Clear the flag after 30 seconds in case user navigates away and comes back
|
||||
// Clear the flag after 1 minute in case user navigates away and comes back
|
||||
setTimeout(() => {
|
||||
sessionStorage.removeItem(REFRESH_KEY);
|
||||
}, 30000);
|
||||
sessionStorage.removeItem(REFRESH_TIMESTAMP_KEY);
|
||||
}, 60000);
|
||||
}
|
||||
};
|
||||
|
||||
// Clear old refresh flags on component mount (for new session)
|
||||
const clearOldFlags = () => {
|
||||
const lastRefreshTime = sessionStorage.getItem(REFRESH_TIMESTAMP_KEY);
|
||||
if (lastRefreshTime) {
|
||||
const timeSinceLastRefresh = Date.now() - parseInt(lastRefreshTime, 10);
|
||||
// Clear if last refresh was more than 5 minutes ago
|
||||
if (timeSinceLastRefresh > 300000) {
|
||||
sessionStorage.removeItem(REFRESH_KEY);
|
||||
sessionStorage.removeItem(REFRESH_TIMESTAMP_KEY);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
clearOldFlags();
|
||||
|
||||
// Add event listeners
|
||||
window.addEventListener("error", handleError);
|
||||
window.addEventListener("error", handleError, true); // Use capture phase
|
||||
window.addEventListener("unhandledrejection", handlePromiseRejection);
|
||||
|
||||
// Cleanup event listeners
|
||||
return () => {
|
||||
window.removeEventListener("error", handleError);
|
||||
window.removeEventListener("error", handleError, true);
|
||||
window.removeEventListener("unhandledrejection", handlePromiseRejection);
|
||||
};
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -211,7 +211,20 @@ export default function FormConvertSPIT() {
|
|||
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
initializeComponent();
|
||||
let isMounted = true;
|
||||
|
||||
const init = async () => {
|
||||
if (isMounted) {
|
||||
await initializeComponent();
|
||||
}
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
// Cleanup function to prevent state updates after unmount
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -232,8 +245,15 @@ export default function FormConvertSPIT() {
|
|||
loadCategories(),
|
||||
id ? loadDetail() : Promise.resolve(),
|
||||
]);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error("Failed to initialize component:", error);
|
||||
|
||||
// Don't show error alert if it's an AbortError (user navigated away or page refreshed)
|
||||
if (error?.name === 'AbortError' || error?.message?.includes('abort')) {
|
||||
console.log("Component initialization aborted (user navigation or refresh)");
|
||||
return;
|
||||
}
|
||||
|
||||
MySwal.fire({
|
||||
title: "Error",
|
||||
text: "Failed to load data. Please try again.",
|
||||
|
|
|
|||
|
|
@ -38,19 +38,34 @@ User opens app → Deployment happens → User navigates → ChunkLoadError occu
|
|||
↓
|
||||
Handler detects error
|
||||
↓
|
||||
Validate it's actual ChunkLoadError
|
||||
(not API error or normal network error)
|
||||
↓
|
||||
Check sessionStorage
|
||||
↓
|
||||
No refresh yet? → Yes → Set flag & Reload
|
||||
No refresh yet? → Yes → Set flag & Reload (100ms delay)
|
||||
↓
|
||||
Already refreshed? → Show error in console
|
||||
```
|
||||
|
||||
### Error Detection Logic
|
||||
Handler hanya mendeteksi ChunkLoadError yang spesifik:
|
||||
- Error name adalah "ChunkLoadError"
|
||||
- Error message mengandung "Loading chunk" atau "ChunkLoadError"
|
||||
- Error terkait `_next/static` atau `_next` (Next.js chunks)
|
||||
- **Mengecualikan** error API, HTTP requests, atau service calls
|
||||
|
||||
Ini mencegah false positive dari Promise rejection biasa.
|
||||
|
||||
## Benefits
|
||||
✅ User experience lebih baik - no more broken page
|
||||
✅ Automatic recovery dari ChunkLoadError
|
||||
✅ Aman - hanya refresh sekali, tidak ada infinite loop
|
||||
✅ Bekerja di background tanpa mengganggu user
|
||||
✅ Works untuk semua tipe lazy-loaded components
|
||||
✅ Tidak konflik dengan Promise rejection biasa (API calls, etc)
|
||||
✅ Prevention untuk multiple simultaneous refresh
|
||||
✅ Time-based protection (tidak refresh jika < 5 detik dari refresh terakhir)
|
||||
|
||||
## Testing
|
||||
Untuk test handler ini:
|
||||
|
|
|
|||
Loading…
Reference in New Issue