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