Skip to content

Commit a7e74ea

Browse files
committed
Use a Suspense load in the image example
1 parent c2bc706 commit a7e74ea

1 file changed

Lines changed: 30 additions & 33 deletions

File tree

‎src/content/reference/react/Suspense.md‎

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3053,24 +3053,25 @@ hr {
30533053
30543054
When a [`<ViewTransition>`](/reference/react/ViewTransition) animates a Suspense boundary's reveal, React waits for visible images to load, up to a timeout, so the animation doesn't start with a half-loaded image. This only happens during a `<ViewTransition>` update. Adding an `onLoad` handler opts a specific image out, even inside a `<ViewTransition>`.
30553055
3056-
In this example, [`startTransition`](/reference/react/startTransition) marks both state updates as Transitions. Compare what happens when React renders the image inside and outside a `<ViewTransition>`:
3056+
The two buttons load a profile inside and outside a `<ViewTransition>`:
30573057
30583058
<Sandpack>
30593059
30603060
```js
30613061
import {
30623062
ViewTransition,
30633063
Suspense,
3064+
use,
30643065
useState,
3065-
startTransition,
30663066
} from 'react';
3067-
import { freshImageUrl } from './image.js';
3067+
import { fetchProfile } from './data.js';
30683068

3069-
function Profile({ src }) {
3069+
function Profile({ profilePromise }) {
3070+
const profile = use(profilePromise);
30703071
return (
30713072
<div className="card">
3072-
<img src={src} alt="" width={80} height={80} />
3073-
<p>Jack Pope</p>
3073+
<img src={profile.image} alt="" width={80} height={80} />
3074+
<p>{profile.name}</p>
30743075
</div>
30753076
);
30763077
}
@@ -3084,48 +3085,40 @@ function ProfilePlaceholder() {
30843085
);
30853086
}
30863087

3087-
function ProfileInViewTransition({ src }) {
3088+
function ProfileInViewTransition({ profilePromise }) {
30883089
return (
30893090
<ViewTransition>
30903091
<Suspense fallback={<ProfilePlaceholder />}>
3091-
<Profile src={src} />
3092+
<Profile profilePromise={profilePromise} />
30923093
</Suspense>
30933094
</ViewTransition>
30943095
);
30953096
}
30963097

30973098
function ProfileWithViewTransition() {
3098-
const [src, setSrc] = useState(null);
3099+
const [profilePromise, setProfilePromise] = useState(null);
30993100
return (
31003101
<>
3101-
<button
3102-
onClick={() => {
3103-
startTransition(() => {
3104-
setSrc(freshImageUrl());
3105-
});
3106-
}}>
3102+
<button onClick={() => setProfilePromise(fetchProfile())}>
31073103
Show profile with View Transition
31083104
</button>
3109-
{src && <ProfileInViewTransition src={src} />}
3105+
{profilePromise && (
3106+
<ProfileInViewTransition profilePromise={profilePromise} />
3107+
)}
31103108
</>
31113109
);
31123110
}
31133111

31143112
function ProfileWithoutViewTransition() {
3115-
const [src, setSrc] = useState(null);
3113+
const [profilePromise, setProfilePromise] = useState(null);
31163114
return (
31173115
<>
3118-
<button
3119-
onClick={() => {
3120-
startTransition(() => {
3121-
setSrc(freshImageUrl());
3122-
});
3123-
}}>
3116+
<button onClick={() => setProfilePromise(fetchProfile())}>
31243117
Show profile without View Transition
31253118
</button>
3126-
{src && (
3119+
{profilePromise && (
31273120
<Suspense fallback={<ProfilePlaceholder />}>
3128-
<Profile src={src} />
3121+
<Profile profilePromise={profilePromise} />
31293122
</Suspense>
31303123
)}
31313124
</>
@@ -3143,11 +3136,15 @@ export default function App() {
31433136
}
31443137
```
31453138
3146-
```js src/image.js hidden
3147-
// Add a unique parameter so the image isn't cached,
3148-
// and every run shows the loading state.
3149-
export function freshImageUrl() {
3150-
return 'https://react.dev/images/team/jack-pope.jpg?t=' + Date.now();
3139+
```js src/data.js hidden
3140+
export async function fetchProfile() {
3141+
// Add a fake delay so the Suspense fallback is visible.
3142+
await new Promise(resolve => setTimeout(resolve, 1000));
3143+
return {
3144+
name: 'Jack Pope',
3145+
// Add a unique parameter so the image isn't cached.
3146+
image: 'https://react.dev/images/team/jack-pope.jpg?t=' + Date.now(),
3147+
};
31513148
}
31523149
```
31533150
@@ -3185,16 +3182,16 @@ hr {
31853182
```json package.json hidden
31863183
{
31873184
"dependencies": {
3188-
"react": "19.3.0-canary-f1f7ed2a-20260904",
3189-
"react-dom": "19.3.0-canary-f1f7ed2a-20260904",
3185+
"react": "19.3.0",
3186+
"react-dom": "19.3.0",
31903187
"react-scripts": "latest"
31913188
}
31923189
}
31933190
```
31943191
31953192
</Sandpack>
31963193
3197-
Inside `<ViewTransition>`, React keeps the skeleton visible while it waits for the image, up to a timeout. Outside it, React does not wait for the image before committing the card.
3194+
When the Promise resolves, React retries rendering the suspended content. This retry activates the enclosing `<ViewTransition>`, so this example does not need `startTransition`. Inside `<ViewTransition>`, React keeps the skeleton visible while it waits for the image, up to a timeout. Outside it, React does not wait for the image before committing the card.
31983195
31993196
---
32003197

0 commit comments

Comments
 (0)