veza/apps/web/src/components/ui/lazy-component/createLazyComponent.tsx

67 lines
1.9 KiB
TypeScript
Raw Normal View History

import {
Suspense,
lazy,
type ComponentType,
} from 'react';
import { LoadingSpinner } from '../loading-spinner';
import { logger } from '@/utils/logger';
import { LazyErrorFallback } from './LazyErrorFallback';
import { LazyErrorBoundary } from './LazyErrorBoundary';
function createLazyWithErrorHandling<T extends ComponentType<any>>(
importFunc: () => Promise<{ default: T } | T>,
pageName: string,
) {
return importFunc()
.then((module) => module as { default: T })
.catch((err) => {
const errorMessage = err instanceof Error ? err.message : String(err);
logger.error('[LazyComponent] Failed to import lazy component', {
pageName,
error: errorMessage,
stack: err instanceof Error ? err.stack : undefined,
});
return Promise.resolve({
default: () => (
<LazyErrorFallback
pageName={pageName}
error={err instanceof Error ? err : new Error(errorMessage)}
/>
),
}) as unknown as Promise<{ default: T }>;
});
}
export interface LazyComponentProps {
fallback?: React.ReactNode;
}
export function createLazyComponent<T extends ComponentType<any>>(
importFunc: () => Promise<any>,
fallback?: React.ReactNode,
pageName?: string,
) {
const safeImportFunc = pageName
? () => createLazyWithErrorHandling(importFunc as () => Promise<{ default: T }>, pageName)
: importFunc;
const LazyComponent = lazy(safeImportFunc);
return function WrappedLazyComponent(
props: React.ComponentProps<T> & LazyComponentProps,
) {
const { fallback: _fallback, ...componentProps } = props;
const component = (
<Suspense fallback={fallback ?? <LoadingSpinner />}>
<LazyComponent {...componentProps} />
</Suspense>
);
if (pageName) {
return (
<LazyErrorBoundary pageName={pageName}>{component}</LazyErrorBoundary>
);
}
return component;
};
}