Lazy Loading
Lazy loading utilities with retry functionality for robust component loading in the WorkPayCore frontend application.
Core Function
lazyWithRetry(componentImport)
Creates a lazy-loaded React component with built-in retry functionality for handling loading failures.
Parameters:
componentImport(function): Function that returns a dynamic import promise
Returns:
React.LazyExoticComponent: Lazy component that can be used with React.Suspense
Example:
import { lazyWithRetry } from '@/utils/lazyWithRetry';
// Create lazy component with retry
const Dashboard = lazyWithRetry(() => import('@/pages/Dashboard'));
const UserProfile = lazyWithRetry(() => import('@/components/UserProfile'));
// Use with Suspense
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path='/dashboard' element={<Dashboard />} />
<Route path='/profile' element={<UserProfile />} />
</Routes>
</Suspense>
</Router>
);
Use Cases:
- Code splitting for performance optimization
- Handling network failures gracefully
- Progressive loading of features
- Reducing initial bundle size
How It Works
The lazyWithRetry function enhances React's lazy loading with:
- Error Handling: Catches import failures without crashing the app
- Retry Logic: Placeholder for potential retry mechanisms
- Cache Management: Resets force refresh flags on successful loads
- Fallback Behavior: Graceful degradation when imports fail
Current Implementation
export const lazyWithRetry = componentImport =>
React.lazy(async () => {
try {
const component = await componentImport();
// Reset refresh flag on successful load
window.localStorage.setItem('page-has-been-force-refreshed', 'false');
return component;
} catch (error) {
console.error('Error Loading Page', error);
// Future: Could implement retry logic here
// Currently: Graceful error handling without crash
}
});
Usage Patterns
Basic Lazy Loading
import { lazyWithRetry } from '@/utils/lazyWithRetry';
// Lazy load heavy components
const DataTable = lazyWithRetry(() => import('@/components/DataTable'));
const Charts = lazyWithRetry(() => import('@/components/Charts'));
const Reports = lazyWithRetry(() => import('@/pages/Reports'));
const Dashboard = () => (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<ComponentSkeleton />}>
<DataTable />
</Suspense>
<Suspense fallback={<ChartSkeleton />}>
<Charts />
</Suspense>
</div>
);
Route-Based Code Splitting
import { lazyWithRetry } from '@/utils/lazyWithRetry';
// Lazy load entire page components
const HomePage = lazyWithRetry(() => import('@/pages/Home'));
const PayrollPage = lazyWithRetry(() => import('@/pages/Payroll'));
const EmployeesPage = lazyWithRetry(() => import('@/pages/Employees'));
const SettingsPage = lazyWithRetry(() => import('@/pages/Settings'));
const AppRoutes = () => (
<Routes>
<Route
path='/'
element={
<Suspense fallback={<PageLoader />}>
<HomePage />
</Suspense>
}
/>
<Route
path='/payroll'
element={
<Suspense fallback={<PageLoader />}>
<PayrollPage />
</Suspense>
}
/>
<Route
path='/employees'
element={
<Suspense fallback={<PageLoader />}>
<EmployeesPage />
</Suspense>
}
/>
<Route
path='/settings'
element={
<Suspense fallback={<PageLoader />}>
<SettingsPage />
</Suspense>
}
/>
</Routes>
);
Feature-Based Lazy Loading
import { lazyWithRetry } from '@/utils/lazyWithRetry';
// Lazy load feature components
const PayrollCalculator = lazyWithRetry(
() => import('@/features/payroll/PayrollCalculator'),
);
const TaxCalculator = lazyWithRetry(
() => import('@/features/tax/TaxCalculator'),
);
const LeaveManager = lazyWithRetry(
() => import('@/features/leave/LeaveManager'),
);
const FeatureProvider = ({ feature, ...props }) => {
const componentMap = {
payroll: PayrollCalculator,
tax: TaxCalculator,
leave: LeaveManager,
};
const Component = componentMap[feature];
if (!Component) {
return <div>Feature not found</div>;
}
return (
<Suspense fallback={<FeatureLoader feature={feature} />}>
<Component {...props} />
</Suspense>
);
};
Advanced Error Boundaries
import { lazyWithRetry } from '@/utils/lazyWithRetry';
class LazyComponentErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Lazy component error:', error, errorInfo);
// Log to error reporting service
this.logErrorToService(error, errorInfo);
}
logErrorToService = (error, errorInfo) => {
// Send error to logging service
};
retry = () => {
this.setState({ hasError: false, error: null });
};
render() {
if (this.state.hasError) {
return (
<div className='lazy-error'>
<h3>Failed to load component</h3>
<p>{this.state.error?.message}</p>
<button onClick={this.retry}>Retry</button>
</div>
);
}
return this.props.children;
}
}
// Usage with error boundary
const SafeLazyComponent = () => {
const LazyComponent = lazyWithRetry(() => import('./SomeComponent'));
return (
<LazyComponentErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</LazyComponentErrorBoundary>
);
};
Loading States
Custom Fallback Components
const ComponentSkeleton = () => (
<div className='skeleton'>
<div className='skeleton-header'></div>
<div className='skeleton-content'></div>
<div className='skeleton-footer'></div>
</div>
);
const PageLoader = () => (
<div className='page-loader'>
<div className='spinner'></div>
<p>Loading page...</p>
</div>
);
const FeatureLoader = ({ feature }) => (
<div className='feature-loader'>
<div className='feature-icon'></div>
<p>Loading {feature} feature...</p>
</div>
);
Performance Benefits
Bundle Size Reduction
- Initial Bundle: Smaller initial JavaScript bundle
- Code Splitting: Automatic chunking by Webpack/Vite
- On-Demand Loading: Components loaded only when needed
- Caching: Browser caches individual chunks
User Experience
- Faster Initial Load: Quicker time to interactive
- Progressive Enhancement: Features load as needed
- Better Perceived Performance: Users see content sooner
- Graceful Degradation: Handles failures without crashes
Best Practices
- Use Suspense Boundaries: Always wrap lazy components in Suspense
- Meaningful Fallbacks: Provide appropriate loading states
- Error Boundaries: Handle lazy loading failures gracefully
- Preloading: Consider preloading critical routes
- Bundle Analysis: Monitor chunk sizes and loading performance
Future Enhancements
The current implementation provides a foundation for future improvements:
- Retry Mechanism: Automatic retry on failed imports
- Preloading: Intelligent prefetching of likely-needed components
- Network Awareness: Adjust loading strategies based on connection
- Analytics: Track lazy loading performance and failures
Related Utilities
- Browser Utils - For environment-specific loading
- Hooks - For managing loading states
TypeScript Definition
export function lazyWithRetry<T extends React.ComponentType<any>>(
componentImport: () => Promise<{ default: T }>,
): React.LazyExoticComponent<T>;
Dependencies
import * as React from 'react';
// Uses: React.lazy