Skip to content

Commit 7d8cc62

Browse files
committed
fix(ui-avatar): avatar now renders images correctly when using SSR
Also add regression tests for Avatar, Alert. Change regression testing suite to use NextJS 14 because NextJS 15 uses React 19 which InstUI does not support yet
1 parent 528e2ee commit 7d8cc62

File tree

14 files changed

+522
-519
lines changed

14 files changed

+522
-519
lines changed

packages/ui-avatar/src/Avatar/index.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import {
2828
SyntheticEvent,
2929
useEffect,
3030
forwardRef,
31-
ForwardedRef
31+
ForwardedRef,
32+
useRef
3233
} from 'react'
3334

3435
import { View } from '@instructure/ui-view'
@@ -52,7 +53,7 @@ const Avatar = forwardRef(
5253
showBorder = 'auto',
5354
shape = 'circle',
5455
display = 'inline-block',
55-
onImageLoaded = (_event: SyntheticEvent) => {},
56+
onImageLoaded,
5657
src,
5758
name,
5859
renderIcon,
@@ -65,6 +66,7 @@ const Avatar = forwardRef(
6566
}: AvatarProps,
6667
ref: ForwardedRef<View>
6768
) => {
69+
const imgRef = useRef<HTMLImageElement>(null)
6870
const [loaded, setLoaded] = useState(false)
6971

7072
const styles = useStyle({
@@ -89,6 +91,11 @@ const Avatar = forwardRef(
8991
if (loaded && !src) {
9092
setLoaded(false)
9193
}
94+
// Image already loaded (common in SSR)
95+
if (src && !loaded && imgRef.current && imgRef.current.complete) {
96+
setLoaded(true)
97+
onImageLoaded?.()
98+
}
9299
}, [loaded, src])
93100

94101
const makeInitialsFromName = () => {
@@ -110,7 +117,7 @@ const Avatar = forwardRef(
110117

111118
const handleImageLoaded = (event: SyntheticEvent) => {
112119
setLoaded(true)
113-
onImageLoaded(event)
120+
onImageLoaded?.(event)
114121
}
115122

116123
const renderInitials = () => {
@@ -157,6 +164,7 @@ const Avatar = forwardRef(
157164
>
158165
<img // This is visually hidden and is here for loading purposes only
159166
src={src}
167+
ref={imgRef}
160168
css={styles?.loadImage}
161169
alt={alt}
162170
onLoad={handleImageLoaded}
@@ -167,6 +175,7 @@ const Avatar = forwardRef(
167175
)
168176
}
169177
)
178+
Avatar.displayName = 'Avatar'
170179

171180
export default Avatar
172181
export { Avatar }

packages/ui-avatar/src/Avatar/props.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,11 @@ type AvatarOwnProps = {
8686
*/
8787
margin?: Spacing
8888
/**
89-
* Callback fired when the avatar image has loaded
89+
* Callback fired when the avatar image has loaded.
90+
* `event` can be `undefined`, if its already loaded when the page renders
91+
* (can happen in SSR)
9092
*/
91-
onImageLoaded?: (event: SyntheticEvent) => void
93+
onImageLoaded?: (event?: SyntheticEvent) => void
9294
/**
9395
* The element type to render as
9496
*/

regression-test/cypress/e2e/spec.cy.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,16 @@ describe('visual regression test', () => {
7575
cy.injectAxe()
7676
cy.checkA11y('.axe-test', axeOptions, terminalLog)
7777
})
78+
79+
it('check alert', () => {
80+
cy.visit('http://localhost:3000/alert')
81+
cy.injectAxe()
82+
cy.checkA11y('.axe-test', axeOptions, terminalLog)
83+
})
84+
85+
it('check avatar', () => {
86+
cy.visit('http://localhost:3000/avatar')
87+
cy.injectAxe()
88+
cy.checkA11y('.axe-test', axeOptions, terminalLog)
89+
})
7890
})

0 commit comments

Comments
 (0)