UNPKG

@ai-growth/nextjs

Version:

Seamlessly integrate Sanity CMS with Next.js applications for automated blog routing and rendering

145 lines (144 loc) 6.85 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, useState, useEffect, lazy } from 'react'; import { useIntersectionObserver, useDynamicImport, } from '../utils/lazy-loading'; import { ErrorBoundary } from './ErrorBoundary'; /** * 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, onError = () => { }, }) => { return (_jsx(ErrorBoundary, { fallback: errorFallback ? React.createElement(errorFallback, { error: new Error('Component error'), retry: () => window.location.reload() }) : null, onError: onError, children: _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 = null, onError = () => { }, preloadOnHover = false, hoverDelay = 300, } = options; const LazyComponent = React.lazy(loader); return React.forwardRef((props, ref) => { const [isHovered, setIsHovered] = useState(false); const [shouldPreload, setShouldPreload] = useState(false); // Handle hover preloading useEffect(() => { if (preloadOnHover && isHovered && !shouldPreload) { const timer = setTimeout(() => { setShouldPreload(true); }, hoverDelay); return () => clearTimeout(timer); } return () => { }; }, [isHovered, preloadOnHover, hoverDelay, shouldPreload]); const handleMouseEnter = preloadOnHover ? () => setIsHovered(true) : undefined; const handleMouseLeave = preloadOnHover ? () => setIsHovered(false) : undefined; return (_jsx("div", { onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, style: { display: 'contents' }, ref: ref, children: _jsx(ErrorBoundary, { fallback: errorFallback, onError: onError, children: _jsxs(Suspense, { fallback: fallback, children: [shouldPreload && _jsx(LazyComponent, { ...props, ref: ref }), !shouldPreload && _jsx(LazyComponent, { ...props, ref: ref })] }) }) })); }); } // ============================================================================ // LAZY LOADING UTILITIES // ============================================================================ /** * Create lazy component with Suspense wrapper */ export function createLazyComponentWithSuspense(loader, fallback = null) { const LazyComponent = lazy(loader); return function SuspenseWrapper(props) { return (_jsx(React.Suspense, { fallback: fallback, 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] }); } const suspenseProps = { fallback, }; if (errorFallback) { suspenseProps.errorFallback = errorFallback; } return (_jsx(SuspenseWrapper, { ...suspenseProps, 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: (node) => { if (elementRef.current !== node) { elementRef.current = node; } }, className: className, style: style, children: isIntersecting ? children : placeholder })); }; export const LazyImage = ({ src, alt, placeholder = 'blur', className, ...props }) => { const imgRef = React.useRef(null); const [isLoaded, setIsLoaded] = React.useState(false); React.useEffect(() => { const img = imgRef.current; if (img && img.complete) { setIsLoaded(true); } }, []); return (_jsxs("div", { className: "image-container", children: [!isLoaded && placeholder, _jsx("img", { ref: imgRef, src: src, alt: alt, className: className, loading: "lazy", decoding: "async", style: { display: isLoaded ? 'block' : 'none' }, onLoad: () => setIsLoaded(true), ...props })] })); };