Skip to main content

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>}>
&lt;Routes&gt;
<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:

  1. Error Handling: Catches import failures without crashing the app
  2. Retry Logic: Placeholder for potential retry mechanisms
  3. Cache Management: Resets force refresh flags on successful loads
  4. 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 = () => (
&lt;Routes&gt;
<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 (
&lt;LazyComponentErrorBoundary&gt;
<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

  1. Use Suspense Boundaries: Always wrap lazy components in Suspense
  2. Meaningful Fallbacks: Provide appropriate loading states
  3. Error Boundaries: Handle lazy loading failures gracefully
  4. Preloading: Consider preloading critical routes
  5. Bundle Analysis: Monitor chunk sizes and loading performance

Future Enhancements

The current implementation provides a foundation for future improvements:

  1. Retry Mechanism: Automatic retry on failed imports
  2. Preloading: Intelligent prefetching of likely-needed components
  3. Network Awareness: Adjust loading strategies based on connection
  4. Analytics: Track lazy loading performance and failures


TypeScript Definition

export function lazyWithRetry<T extends React.ComponentType&lt;any&gt;>(
componentImport: () => Promise<{ default: T }>,
): React.LazyExoticComponent&lt;T&gt;;

Dependencies

import * as React from 'react';
// Uses: React.lazy