Enhance ErrorBoundary component with auto-retry functionality
- Added auto-retry feature to ErrorBoundary for handling errors during module federation loading. - Introduced props for configuring retry behavior: autoRetry, retryDelay, and maxRetries. - Implemented retry count management and UI feedback for ongoing retries. - Updated component lifecycle methods to manage retries and cleanup effectively. - Refactored handleRetry method to reset retry count upon manual retry.
This commit is contained in:
@@ -5,20 +5,61 @@ type Props = {
|
||||
fallback?: ReactNode
|
||||
onRetry?: () => void
|
||||
showRetry?: boolean
|
||||
autoRetry?: boolean
|
||||
retryDelay?: number
|
||||
maxRetries?: number
|
||||
}
|
||||
type State = { hasError: boolean }
|
||||
type State = { hasError: boolean; retryCount: number }
|
||||
|
||||
export class ErrorBoundary extends Component<Props, State> {
|
||||
state: State = { hasError: false }
|
||||
private retryTimeoutId?: NodeJS.Timeout
|
||||
|
||||
state: State = { hasError: false, retryCount: 0 }
|
||||
|
||||
static getDerivedStateFromError() {
|
||||
return { hasError: true }
|
||||
}
|
||||
|
||||
componentDidCatch() {}
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
// Auto-retry logic for module federation loading issues
|
||||
const maxRetries = this.props.maxRetries || 3
|
||||
if (this.props.autoRetry !== false && this.state.retryCount < maxRetries) {
|
||||
const delay = this.props.retryDelay || 2000
|
||||
this.retryTimeoutId = setTimeout(() => {
|
||||
this.setState(prevState => ({
|
||||
hasError: false,
|
||||
retryCount: prevState.retryCount + 1
|
||||
}))
|
||||
if (this.props.onRetry) {
|
||||
this.props.onRetry()
|
||||
}
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props, prevState: State) {
|
||||
// Reset retry count if error is cleared and component successfully rendered
|
||||
if (prevState.hasError && !this.state.hasError && this.state.retryCount > 0) {
|
||||
// Give it a moment to see if it stays error-free
|
||||
setTimeout(() => {
|
||||
if (!this.state.hasError) {
|
||||
this.setState({ retryCount: 0 })
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.retryTimeoutId) {
|
||||
clearTimeout(this.retryTimeoutId)
|
||||
}
|
||||
}
|
||||
|
||||
handleRetry = () => {
|
||||
this.setState({ hasError: false })
|
||||
if (this.retryTimeoutId) {
|
||||
clearTimeout(this.retryTimeoutId)
|
||||
}
|
||||
this.setState({ hasError: false, retryCount: 0 })
|
||||
if (this.props.onRetry) {
|
||||
this.props.onRetry()
|
||||
}
|
||||
@@ -43,6 +84,11 @@ export class ErrorBoundary extends Component<Props, State> {
|
||||
<h3 className="text-sm font-medium text-red-800">Something went wrong loading this section</h3>
|
||||
<div className="mt-2 text-sm text-red-700">
|
||||
<p>An error occurred while loading this component. Please try reloading it.</p>
|
||||
{this.props.autoRetry !== false && this.state.retryCount < (this.props.maxRetries || 3) && (
|
||||
<p className="mt-1 text-xs text-red-600">
|
||||
Retrying automatically... (Attempt {this.state.retryCount + 1} of {(this.props.maxRetries || 3) + 1})
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{(this.props.showRetry !== false) && (
|
||||
<div className="mt-4">
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"build:watch": "vite build --watch",
|
||||
"serve:dist": "serve -s dist -l 3003",
|
||||
"preview": "vite preview --port 3003",
|
||||
"dev:watch": "npm run build && (npm run build:watch &) && sleep 1 && npx http-server dist -p 3003 --cors -c-1"
|
||||
"dev:watch": "./wait-and-serve.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/supabase-js": "^2.52.0",
|
||||
|
||||
57
scheduling-remote/wait-and-serve.sh
Executable file
57
scheduling-remote/wait-and-serve.sh
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Build the project first
|
||||
echo "Building scheduling-remote..."
|
||||
npm run build
|
||||
|
||||
# Verify the initial build created remoteEntry.js
|
||||
REMOTE_ENTRY_PATH="dist/assets/remoteEntry.js"
|
||||
if [ ! -f "$REMOTE_ENTRY_PATH" ]; then
|
||||
echo "ERROR: Initial build did not create remoteEntry.js!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Initial build complete. remoteEntry.js exists."
|
||||
|
||||
# Start build:watch in the background
|
||||
echo "Starting build:watch in background..."
|
||||
npm run build:watch &
|
||||
BUILD_WATCH_PID=$!
|
||||
|
||||
# Wait a moment for build:watch to start and potentially rebuild
|
||||
echo "Waiting for build:watch to stabilize..."
|
||||
sleep 3
|
||||
|
||||
# Verify remoteEntry.js still exists (build:watch might have rebuilt it)
|
||||
MAX_WAIT=30
|
||||
WAIT_COUNT=0
|
||||
while [ ! -f "$REMOTE_ENTRY_PATH" ] && [ $WAIT_COUNT -lt $MAX_WAIT ]; do
|
||||
sleep 1
|
||||
WAIT_COUNT=$((WAIT_COUNT + 1))
|
||||
if [ $((WAIT_COUNT % 5)) -eq 0 ]; then
|
||||
echo "Waiting for remoteEntry.js after build:watch... (${WAIT_COUNT}s)"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ! -f "$REMOTE_ENTRY_PATH" ]; then
|
||||
echo "ERROR: remoteEntry.js was not available after ${MAX_WAIT} seconds!"
|
||||
kill $BUILD_WATCH_PID 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Wait a bit more to ensure build:watch has finished any initial rebuild
|
||||
echo "Ensuring build:watch has completed initial build..."
|
||||
sleep 2
|
||||
|
||||
# Check file size to ensure it's not empty or being written
|
||||
FILE_SIZE=$(stat -f%z "$REMOTE_ENTRY_PATH" 2>/dev/null || stat -c%s "$REMOTE_ENTRY_PATH" 2>/dev/null || echo "0")
|
||||
if [ "$FILE_SIZE" -lt 100 ]; then
|
||||
echo "WARNING: remoteEntry.js seems too small (${FILE_SIZE} bytes), waiting a bit more..."
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
echo "remoteEntry.js is ready (${FILE_SIZE} bytes). Starting http-server..."
|
||||
|
||||
# Start http-server and give it time to fully initialize
|
||||
# Use a simple approach: start server and wait a moment for it to be ready
|
||||
exec npx http-server dist -p 3003 --cors -c-1
|
||||
Reference in New Issue
Block a user