UNPKG

@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
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 })); };