useHooks.iov4.1.2
DocsBlogGitHub
Hooks
No hooks found in any category.

useLazy

performance

Installation

npx usehooks-cli@latest add use-lazy

Description

A powerful React hook that enhances React.lazy with loading states, error handling, preloading capabilities, and manual control over component loading. Perfect for code splitting and performance optimization.

Parameters

NameTypeDefaultDescription
importFn() => Promise<{ default: T } | T>-Function that returns a promise resolving to the component to be lazy loaded
options?UseLazyOptions{}Configuration options for the lazy loading behavior

Parameter Properties

options properties:

NameTypeDescription
preload?booleanWhether to preload the component immediately
onLoadStart?() => voidCallback when component starts loading
onLoadSuccess?(component: ComponentType<any>) => voidCallback when component loads successfully
onLoadError?(error: Error) => voidCallback when component fails to load

Return Type

UseLazyReturn<T>
PropertyTypeDescription
ComponentT | nullThe lazy component ready to be rendered
loadingbooleanWhether the component is currently loading
errorError | nullLoading error if any occurred
load() => Promise<T | null>Manually trigger component loading
preload() => Promise<T | null>Preload the component without rendering it
reset() => voidReset the loading state and clear cached component

Examples

Basic Lazy Loading

Simple lazy loading with Suspense boundary

1import { Suspense } from 'react'; 2import { useLazy } from './use-lazy'; 3 4function App() { 5 const { Component } = useLazy(() => import('./HeavyComponent')); 6 7 return ( 8 <Suspense fallback={<div>Loading...</div>}> 9 <Component /> 10 </Suspense> 11 ); 12}

With Loading States

Manual control over loading with loading states

1const { Component, loading, error, load } = useLazy( 2 () => import('./Dashboard'), 3 { 4 onLoadStart: () => console.log('Loading started'), 5 onLoadSuccess: () => console.log('Component loaded'), 6 onLoadError: (err) => console.error('Load failed:', err) 7 } 8); 9 10if (error) return <div>Error: {error.message}</div>; 11if (loading) return <div>Loading component...</div>; 12 13return ( 14 <div> 15 <button onClick={load}>Load Dashboard</button> 16 {Component && <Component />} 17 </div> 18);

Preloading Components

Preload components for better performance

1const { Component, preload } = useLazy( 2 () => import('./ExpensiveChart'), 3 { preload: true } // Preload immediately 4); 5 6// Or preload on user interaction 7const handleMouseEnter = () => { 8 preload(); // Preload on hover 9}; 10 11return ( 12 <div onMouseEnter={handleMouseEnter}> 13 <Suspense fallback={<ChartSkeleton />}> 14 <Component /> 15 </Suspense> 16 </div> 17);

Conditional Lazy Loading

Load components based on conditions

1const { Component, load, loading } = useLazy( 2 () => import('./AdminPanel') 3); 4 5const [showAdmin, setShowAdmin] = useState(false); 6 7const handleShowAdmin = async () => { 8 setShowAdmin(true); 9 await load(); // Ensure component is loaded 10}; 11 12return ( 13 <div> 14 <button onClick={handleShowAdmin} disabled={loading}> 15 {loading ? 'Loading...' : 'Show Admin Panel'} 16 </button> 17 {showAdmin && Component && ( 18 <Suspense fallback={<div>Loading admin...</div>}> 19 <Component /> 20 </Suspense> 21 )} 22 </div> 23);

Dependencies

react

Notes

  • Always wrap lazy components with Suspense boundary for proper loading states
  • The hook caches loaded components to prevent re-loading on re-renders
  • Preloading is useful for components that will likely be needed soon
  • Error handling should be implemented both in the hook callbacks and with Error Boundaries
  • The Component returned is compatible with React.lazy and Suspense

Implementation

1"use client"; 2 3import { lazy, useState, useCallback, useRef, ComponentType } from "react"; 4 5export interface UseLazyOptions { 6 /** Preload the component immediately */ 7 preload?: boolean; 8 /** Callback when component starts loading */ 9 onLoadStart?: () => void; 10 /** Callback when component loads successfully */ 11 onLoadSuccess?: (component: ComponentType<any>) => void; 12 /** Callback when component fails to load */ 13 onLoadError?: (error: Error) => void; 14} 15 16export interface UseLazyReturn<T extends ComponentType<any>> { 17 /** The lazy component */ 18 Component: T | null; 19 /** Whether the component is currently loading */ 20 loading: boolean; 21 /** Loading error if any */ 22 error: Error | null; 23 /** Manually trigger component loading */ 24 load: () => Promise<T | null>; 25 /** Preload the component without rendering */ 26 preload: () => Promise<T | null>; 27 /** Reset the loading state */ 28 reset: () => void; 29} 30 31export function useLazy<T extends ComponentType<any>>( 32 importFn: () => Promise<{ default: T } | T>, 33 options: UseLazyOptions = {} 34): UseLazyReturn<T> { 35 const [loading, setLoading] = useState(false); 36 const [error, setError] = useState<Error | null>(null); 37 const [Component, setComponent] = useState<T | null>(null); 38 39 const loadPromiseRef = useRef<Promise<T | null> | null>(null); 40 const lazyComponentRef = useRef<T | null | React.LazyExoticComponent<T>>( 41 null 42 ); 43 const hasLoadedRef = useRef(false); 44 45 const load = useCallback(async (): Promise<T | null> => { 46 // Return existing promise if already loading 47 if (loadPromiseRef.current) { 48 return loadPromiseRef.current; 49 } 50 51 // Return cached component if already loaded 52 if (hasLoadedRef.current && lazyComponentRef.current) { 53 return lazyComponentRef.current as T; 54 } 55 56 setLoading(true); 57 setError(null); 58 options.onLoadStart?.(); 59 60 loadPromiseRef.current = (async () => { 61 try { 62 const module = await importFn(); 63 const component = "default" in module ? module.default : module; 64 65 lazyComponentRef.current = component as T; 66 hasLoadedRef.current = true; 67 setComponent(component as T); 68 setLoading(false); 69 options.onLoadSuccess?.(component as T); 70 71 return component as T; 72 } catch (err) { 73 const error = 74 err instanceof Error ? err : new Error("Failed to load component"); 75 setError(error); 76 setLoading(false); 77 options.onLoadError?.(error); 78 throw error; 79 } finally { 80 loadPromiseRef.current = null; 81 } 82 })(); 83 84 return loadPromiseRef.current; 85 }, [importFn, options]); 86 87 const preload = useCallback(async (): Promise<T | null> => { 88 try { 89 return await load(); 90 } catch (error) { 91 // Preload failures are silent by default 92 return null; 93 } 94 }, [load]); 95 96 const reset = useCallback(() => { 97 setLoading(false); 98 setError(null); 99 setComponent(null); 100 hasLoadedRef.current = false; 101 lazyComponentRef.current = null; 102 loadPromiseRef.current = null; 103 }, []); 104 105 // Create lazy component that triggers loading 106 const LazyComponent = useCallback(() => { 107 if (!lazyComponentRef.current) { 108 // Create a lazy component that will trigger our load function 109 lazyComponentRef.current = lazy(async () => { 110 const component = await load(); 111 return { default: component } as { default: T }; 112 }); 113 } 114 return lazyComponentRef.current; 115 }, [load]); 116 117 // Preload if requested 118 useState(() => { 119 if (options.preload) { 120 preload(); 121 } 122 }); 123 124 return { 125 Component: Component as T | null, 126 loading, 127 error, 128 load, 129 preload, 130 reset, 131 }; 132} 133