@ai-growth/nextjs
Version:
Seamlessly integrate Sanity CMS with Next.js applications for automated blog routing and rendering
126 lines (125 loc) • 5.81 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/**
* @fileoverview Lazy Loading React Components
*
* This module provides React components for implementing lazy loading patterns
* with JSX support, including LazyLoadComponent and error boundaries.
*/
import React, { Suspense, Component } from 'react';
import { useIntersectionObserver, useDynamicImport } from '../utils/lazy-loading';
/**
* Lazy loading component with intersection observer
*/
export const LazyLoadComponent = ({ component: Component, props = {}, fallback = null, errorFallback = null, className, style, intersectionOptions, immediate = false, }) => {
const [elementRef, isIntersecting] = useIntersectionObserver(intersectionOptions);
const shouldLoad = immediate || isIntersecting;
const { component, loading, error } = useDynamicImport(() => Promise.resolve({ default: Component }), shouldLoad);
if (error && errorFallback) {
return _jsx("div", { ref: elementRef, className: className, style: style, children: errorFallback });
}
if (loading || !component) {
return _jsx("div", { ref: elementRef, className: className, style: style, children: fallback });
}
const LoadedComponent = component;
return (_jsx("div", { ref: elementRef, className: className, style: style, children: _jsx(LoadedComponent, { ...props }) }));
};
/**
* Enhanced Suspense wrapper with error boundary
*/
export const SuspenseWrapper = ({ children, fallback = null, errorFallback: ErrorFallback, onError, }) => {
if (ErrorFallback) {
return (_jsx(LazyErrorBoundary, { ErrorFallback: ErrorFallback, onError: onError, children: _jsx(Suspense, { fallback: fallback, children: children }) }));
}
return (_jsx(Suspense, { fallback: fallback, children: children }));
};
/**
* Error boundary specifically for lazy-loaded components
*/
export class LazyErrorBoundary extends Component {
constructor(props) {
super(props);
this.handleRetry = () => {
this.setState({ hasError: false, error: null });
};
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Lazy component loading error:', error, errorInfo);
this.props.onError?.(error);
}
render() {
if (this.state.hasError && this.state.error) {
const { ErrorFallback } = this.props;
return _jsx(ErrorFallback, { error: this.state.error, retry: this.handleRetry });
}
return this.props.children;
}
}
/**
* Create a lazy-loaded higher-order component
*/
export function withLazyLoading(loader, options = {}) {
const { fallback = null, errorFallback, onError, preloadOnHover = false, hoverDelay = 300, } = options;
const LazyComponent = React.lazy(loader);
return React.forwardRef((props, ref) => {
const [isHovered, setIsHovered] = React.useState(false);
const [shouldPreload, setShouldPreload] = React.useState(false);
// Handle hover preloading
React.useEffect(() => {
if (preloadOnHover && isHovered && !shouldPreload) {
const timer = setTimeout(() => {
setShouldPreload(true);
// Trigger preload
loader().catch(() => {
// Ignore preload errors
});
}, hoverDelay);
return () => clearTimeout(timer);
}
}, [isHovered, shouldPreload]);
const Component = (_jsx(SuspenseWrapper, { fallback: fallback, errorFallback: errorFallback, onError: onError, children: _jsx(LazyComponent, { ...props, ref: ref }) }));
if (preloadOnHover) {
return (_jsx("div", { onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), style: { display: 'contents' }, children: Component }));
}
return Component;
});
}
// ============================================================================
// LAZY LOADING UTILITIES
// ============================================================================
/**
* Create lazy component with Suspense wrapper
*/
export function createLazyComponentWithSuspense(loader, fallback = null, errorFallback) {
const LazyComponent = React.lazy(loader);
return (props) => (_jsx(SuspenseWrapper, { fallback: fallback, errorFallback: errorFallback, children: _jsx(LazyComponent, { ...props }) }));
}
/**
* Route-based lazy component loader
*/
export const RouteBasedLazy = ({ routeMap, currentRoute, componentProps = {}, fallback = null, errorFallback, }) => {
const Component = routeMap[currentRoute];
if (!Component) {
if (errorFallback) {
const ErrorComponent = errorFallback;
return (_jsx(ErrorComponent, { error: new Error(`Route not found: ${currentRoute}`), retry: () => window.location.reload() }));
}
return _jsxs("div", { children: ["Route not found: ", currentRoute] });
}
return (_jsx(SuspenseWrapper, { fallback: fallback, errorFallback: errorFallback, children: _jsx(Component, { ...componentProps }) }));
};
/**
* Wrapper component that only renders children when in viewport
*/
export const IntersectionWrapper = ({ children, placeholder = null, options, className, style, onIntersect, }) => {
const [elementRef, isIntersecting] = useIntersectionObserver(options);
React.useEffect(() => {
if (isIntersecting && onIntersect) {
onIntersect();
}
}, [isIntersecting, onIntersect]);
return (_jsx("div", { ref: elementRef, className: className, style: style, children: isIntersecting ? children : placeholder }));
};