vkashti / components / DynamicImport.tsx
DynamicImport.tsx
Raw
'use client';

import { useEffect, useState, ComponentType, ReactNode } from 'react';
import { useInView } from 'framer-motion';
import { useRef } from 'react';

interface DynamicImportProps {
  children: ReactNode;
  fallback?: ReactNode;
  threshold?: number;
  loadingStrategy?: 'on-visible' | 'on-interaction' | 'on-idle';
}

/**
 * DynamicImport - A component that defers importing and rendering of children until certain conditions are met
 * 
 * This can be used to:
 * 1. Defer loading of heavy components until they're in view
 * 2. Defer loading until user interaction (like clicking)
 * 3. Defer loading until browser idle time
 * 
 * Usage examples:
 * 
 * 1. Load when visible:
 * <DynamicImport loadingStrategy="on-visible">
 *   <HeavyComponent />
 * </DynamicImport>
 * 
 * 2. Load on idle time:
 * <DynamicImport loadingStrategy="on-idle">
 *   <HeavyComponent />
 * </DynamicImport>
 */
export function DynamicImport({
  children,
  fallback = <div className="min-h-[100px] animate-pulse bg-gray-100 rounded-md"></div>,
  threshold = 0.1,
  loadingStrategy = 'on-visible'
}: DynamicImportProps) {
  const [shouldRender, setShouldRender] = useState(false);
  const ref = useRef(null);
  const isInView = useInView(ref, { 
    once: true, 
    amount: threshold,
    margin: "200px"
  });

  useEffect(() => {
    if (loadingStrategy === 'on-idle') {
      // Use requestIdleCallback or fallback to setTimeout
      const idleCallback = 'requestIdleCallback' in window 
        ? window.requestIdleCallback 
        : (cb: IdleRequestCallback) => setTimeout(cb, 1);
      
      idleCallback(() => {
        setShouldRender(true);
      });
    } else if (loadingStrategy === 'on-visible' && isInView) {
      setShouldRender(true);
    } else if (loadingStrategy === 'on-interaction') {
      // Will be set by interaction
      const handleInteraction = () => {
        setShouldRender(true);
        document.removeEventListener('click', handleInteraction);
        document.removeEventListener('scroll', handleInteraction);
      };
      
      document.addEventListener('click', handleInteraction);
      document.addEventListener('scroll', handleInteraction);
      
      return () => {
        document.removeEventListener('click', handleInteraction);
        document.removeEventListener('scroll', handleInteraction);
      };
    }
  }, [loadingStrategy, isInView]);

  return (
    <div ref={ref}>
      {shouldRender ? children : fallback}
    </div>
  );
}

/**
 * withDynamicImport - HOC that wraps a component with DynamicImport
 * 
 * @param Component - The component to wrap
 * @param options - DynamicImport options
 * @returns A component that will be dynamically imported
 * 
 * Usage example:
 * 
 * const LazyHeavyComponent = withDynamicImport(HeavyComponent, {
 *   loadingStrategy: 'on-visible',
 *   fallback: <CustomLoader />
 * });
 */
export function withDynamicImport<P extends object>(
  Component: ComponentType<P>,
  options: Omit<DynamicImportProps, 'children'> = {}
) {
  return function WithDynamicImport(props: P) {
    return (
      <DynamicImport {...options}>
        <Component {...props} />
      </DynamicImport>
    );
  };
}