1
1
/**
2
2
* Part of this code is taken from @chakra-ui/react package ❤️
3
3
*/
4
- import type { ImgHTMLAttributes , MutableRefObject , SyntheticEvent } from "react" ;
5
4
6
- import { useEffect , useRef , useState } from "react" ;
5
+ import type { ImgHTMLAttributes , SyntheticEvent } from "react" ;
6
+
7
+ import { useCallback , useEffect , useRef , useState } from "react" ;
7
8
import { useSafeLayoutEffect } from "@nextui-org/use-safe-layout-effect" ;
8
9
9
10
type NativeImageProps = ImgHTMLAttributes < HTMLImageElement > ;
@@ -46,7 +47,6 @@ type Status = "loading" | "failed" | "pending" | "loaded";
46
47
export type FallbackStrategy = "onError" | "beforeLoadOrError" ;
47
48
48
49
type ImageEvent = SyntheticEvent < HTMLImageElement , Event > ;
49
-
50
50
/**
51
51
* React hook that loads an image in the browser,
52
52
* and lets us know the `status` so we can show image
@@ -63,40 +63,44 @@ type ImageEvent = SyntheticEvent<HTMLImageElement, Event>;
63
63
* }
64
64
* ```
65
65
*/
66
+
66
67
export function useImage ( props : UseImageProps = { } ) {
67
68
const { loading, src, srcSet, onLoad, onError, crossOrigin, sizes, ignoreFallback} = props ;
68
69
70
+ const [ status , setStatus ] = useState < Status > ( "pending" ) ;
71
+
72
+ useEffect ( ( ) => {
73
+ setStatus ( src ? "loading" : "pending" ) ;
74
+ } , [ src ] ) ;
75
+
69
76
const imageRef = useRef < HTMLImageElement | null > ( ) ;
70
- const firstMount = useRef < boolean > ( true ) ;
71
- const [ status , setStatus ] = useState < Status > ( ( ) => setImageAndGetInitialStatus ( props , imageRef ) ) ;
72
77
73
- useSafeLayoutEffect ( ( ) => {
74
- if ( firstMount . current ) {
75
- firstMount . current = false ;
78
+ const load = useCallback ( ( ) => {
79
+ if ( ! src ) return ;
76
80
77
- return ;
78
- }
81
+ flush ( ) ;
79
82
80
- setStatus ( setImageAndGetInitialStatus ( props , imageRef ) ) ;
83
+ const img = new Image ( ) ;
81
84
82
- return ( ) => {
83
- flush ( ) ;
84
- } ;
85
- } , [ src , crossOrigin , srcSet , sizes , loading ] ) ;
85
+ img . src = src ;
86
+ if ( crossOrigin ) img . crossOrigin = crossOrigin ;
87
+ if ( srcSet ) img . srcset = srcSet ;
88
+ if ( sizes ) img . sizes = sizes ;
89
+ if ( loading ) img . loading = loading ;
86
90
87
- useEffect ( ( ) => {
88
- if ( ! imageRef . current ) return ;
89
- imageRef . current . onload = ( event ) => {
91
+ img . onload = ( event ) => {
90
92
flush ( ) ;
91
93
setStatus ( "loaded" ) ;
92
94
onLoad ?.( event as unknown as ImageEvent ) ;
93
95
} ;
94
- imageRef . current . onerror = ( error ) => {
96
+ img . onerror = ( error ) => {
95
97
flush ( ) ;
96
98
setStatus ( "failed" ) ;
97
99
onError ?.( error as any ) ;
98
100
} ;
99
- } , [ imageRef . current ] ) ;
101
+
102
+ imageRef . current = img ;
103
+ } , [ src , crossOrigin , srcSet , sizes , onLoad , onError , loading ] ) ;
100
104
101
105
const flush = ( ) => {
102
106
if ( imageRef . current ) {
@@ -106,40 +110,25 @@ export function useImage(props: UseImageProps = {}) {
106
110
}
107
111
} ;
108
112
113
+ useSafeLayoutEffect ( ( ) => {
114
+ /**
115
+ * If user opts out of the fallback/placeholder
116
+ * logic, let's bail out.
117
+ */
118
+ if ( ignoreFallback ) return undefined ;
119
+
120
+ if ( status === "loading" ) {
121
+ load ( ) ;
122
+ }
123
+
124
+ return ( ) => {
125
+ flush ( ) ;
126
+ } ;
127
+ } , [ status , load , ignoreFallback ] ) ;
128
+
109
129
/**
110
130
* If user opts out of the fallback/placeholder
111
131
* logic, let's just return 'loaded'
112
132
*/
113
133
return ignoreFallback ? "loaded" : status ;
114
134
}
115
-
116
- function setImageAndGetInitialStatus (
117
- props : UseImageProps ,
118
- imageRef : MutableRefObject < HTMLImageElement | null | undefined > ,
119
- ) : Status {
120
- const { loading, src, srcSet, crossOrigin, sizes, ignoreFallback} = props ;
121
-
122
- if ( ! src ) return "pending" ;
123
- if ( ignoreFallback ) return "loaded" ;
124
-
125
- const img = new Image ( ) ;
126
-
127
- img . src = src ;
128
- if ( crossOrigin ) img . crossOrigin = crossOrigin ;
129
- if ( srcSet ) img . srcset = srcSet ;
130
- if ( sizes ) img . sizes = sizes ;
131
- if ( loading ) img . loading = loading ;
132
-
133
- imageRef . current = img ;
134
- if ( img . complete && img . naturalWidth ) {
135
- return "loaded" ;
136
- }
137
-
138
- return "loading" ;
139
- }
140
-
141
- export const shouldShowFallbackImage = ( status : Status , fallbackStrategy : FallbackStrategy ) =>
142
- ( status !== "loaded" && fallbackStrategy === "beforeLoadOrError" ) ||
143
- ( status === "failed" && fallbackStrategy === "onError" ) ;
144
-
145
- export type UseImageReturn = ReturnType < typeof useImage > ;
0 commit comments