Skip to content

Commit bb9bbeb

Browse files
Guard blurrable image DOM lookup
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
1 parent 95f354b commit bb9bbeb

File tree

1 file changed

+19
-21
lines changed

1 file changed

+19
-21
lines changed

app/components/blurrable-image.tsx

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ import { clsx } from 'clsx'
22
import * as React from 'react'
33

44
const isServer = typeof document === 'undefined'
5-
const useIsomorphicLayoutEffect = isServer
6-
? React.useEffect
7-
: React.useLayoutEffect
85

96
function BlurrableImage({
107
img,
@@ -15,40 +12,41 @@ function BlurrableImage({
1512
blurDataUrl?: string
1613
} & React.HTMLAttributes<HTMLDivElement>) {
1714
const id = React.useId()
18-
const [visible, setVisible] = React.useState(false)
19-
const jsImgElRef = React.useRef<HTMLImageElement>(null)
15+
const [visible, setVisible] = React.useState(() => {
16+
if (isServer) return false
2017

21-
useIsomorphicLayoutEffect(() => {
22-
const imageEl = jsImgElRef.current
23-
if (!imageEl) return
18+
// During hydration the element might not be in the DOM yet, so guard
19+
// against null to avoid crashing and fall back to the blurred state.
20+
const el = document.getElementById(id)
21+
if (!(el instanceof HTMLImageElement)) return false
2422

25-
// On the client, the image might have already loaded before hydration,
26-
// which removes the opacity class via the server-rendered onload handler.
27-
if (imageEl.complete || !imageEl.classList.contains('opacity-0')) {
28-
setVisible(true)
29-
}
30-
}, [])
23+
// on the client, it's possible the images has already finished loading.
24+
// we've got the data-evt-onload attribute on the image
25+
// (which our entry.server replaces with simply "onload") which will remove
26+
// the class "opacity-0" from the image once it's loaded. So we'll check
27+
// if the image is already loaded and if so, we know that visible should
28+
// initialize to true.
29+
return !el.classList.contains('opacity-0')
30+
})
31+
const jsImgElRef = React.useRef<HTMLImageElement>(null)
3132

3233
React.useEffect(() => {
33-
const imageEl = jsImgElRef.current
34-
if (!imageEl) return
35-
if (imageEl.complete) {
34+
if (!jsImgElRef.current) return
35+
if (jsImgElRef.current.complete) {
3636
setVisible(true)
3737
return
3838
}
3939

4040
let current = true
41-
const handleLoad = () => {
41+
jsImgElRef.current.addEventListener('load', () => {
4242
if (!jsImgElRef.current || !current) return
4343
setTimeout(() => {
4444
setVisible(true)
4545
}, 0)
46-
}
47-
imageEl.addEventListener('load', handleLoad)
46+
})
4847

4948
return () => {
5049
current = false
51-
imageEl.removeEventListener('load', handleLoad)
5250
}
5351
}, [])
5452

0 commit comments

Comments
 (0)