Hooks
No hooks found in any category.
useLazy
performanceInstallation
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
Name | Type | Default | Description |
---|---|---|---|
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:
Name | Type | Description |
---|---|---|
preload? | boolean | Whether to preload the component immediately |
onLoadStart? | () => void | Callback when component starts loading |
onLoadSuccess? | (component: ComponentType<any>) => void | Callback when component loads successfully |
onLoadError? | (error: Error) => void | Callback when component fails to load |
Return Type
UseLazyReturn<T>
Property | Type | Description |
---|---|---|
Component | T | null | The lazy component ready to be rendered |
loading | boolean | Whether the component is currently loading |
error | Error | null | Loading error if any occurred |
load | () => Promise<T | null> | Manually trigger component loading |
preload | () => Promise<T | null> | Preload the component without rendering it |
reset | () => void | Reset 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