Skip to content

Commit bbf165f

Browse files
committed
Update section on selective hydration
1 parent 771485d commit bbf165f

File tree

1 file changed

+124
-11
lines changed

1 file changed

+124
-11
lines changed

src/content/reference/react/Activity.md

Lines changed: 124 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ You can use Activity to hide part of your application:
4747

4848
When an Activity boundary becomes <CodeStep step={1}>hidden</CodeStep>, React will visually hide its <CodeStep step={2}>children</CodeStep> using the `display: "none"` CSS property. It will also destroy their Effects, cleaning up any active subscriptions.
4949

50-
While hidden, children still receive updates, albeit at a lower priority than the rest of the content.
50+
While hidden, children still re-render in response to new props, albeit at a lower priority than the rest of the content.
5151

5252
When the boundary becomes <CodeStep step={3}>visible</CodeStep> again, React will reveal the children with their previous state restored, and create their Effects.
5353

@@ -889,35 +889,148 @@ Suspense-enabled data fetching without the use of an opinionated framework is no
889889

890890
---
891891

892-
### Deferring hydration of low-priority content {/*deferring-hydration-of-low-priority-content*/}
893892

894-
You can wrap part of your UI in a <CodeStep step={3}>visible</CodeStep> Activity boundary to defer mounting it on the initial render:
893+
### Reducing the time it takes to hydrate server-rendered content {/*reducing-the-time-it-takes-to-hydrate-server-rendered-content*/}
895894

896-
```jsx [[3,6,"\\"visible\\""]]
895+
React includes an under-the-hood performance optimization called Selective Hydration. It works by hydrating your app's initial HTML _in chunks_, enabling some components to become interactive even if other components on the page haven't loaded their code or data yet.
896+
897+
Suspense boundaries participate in Selective Hydration, because they naturally divide your component tree into units that are independent from one another:
898+
899+
```jsx
897900
function Page() {
898901
return (
899902
<>
900-
<Post />
901-
902-
<Activity mode="visible">
903-
<Comments />
903+
<MessageComposer />
904+
905+
<Suspense fallback="Loading chats...">
906+
<Chats />
907+
</Suspense>
908+
</>
909+
)
910+
}
911+
```
912+
913+
Here, `MessageComposer` can be fully hydrated during the initial render of the page, even before `Chats` is mounted and starts to fetch its data.
914+
915+
So by breaking up your component tree into discrete units, Suspense allows React to hydrate your app's server-rendered HTML in chunks, enabling parts of your app to become interactive as fast as possible.
916+
917+
But what about pages that don't use Suspense?
918+
919+
Take this tabs example:
920+
921+
```jsx
922+
function Page() {
923+
const [activeTab, setActiveTab] = useState('home');
924+
925+
return (
926+
<>
927+
<TabButton onClick={() => setActiveTab('home')}>
928+
Home
929+
</TabButton>
930+
<TabButton onClick={() => setActiveTab('video')}>
931+
Video
932+
</TabButton>
933+
934+
{activeTab === 'home' && (
935+
<Home />
936+
)}
937+
{activeTab === 'video' && (
938+
<Video />
939+
)}
940+
</>
941+
)
942+
}
943+
```
944+
945+
Here, React must hydrate the entire page all at once. If `Home` or `Video` are slower to render, they could make the tab buttons feel unresponsive during hydration.
946+
947+
Adding Suspense around the active tab would solve this:
948+
949+
```jsx {13,20}
950+
function Page() {
951+
const [activeTab, setActiveTab] = useState('home');
952+
953+
return (
954+
<>
955+
<TabButton onClick={() => setActiveTab('home')}>
956+
Home
957+
</TabButton>
958+
<TabButton onClick={() => setActiveTab('video')}>
959+
Video
960+
</TabButton>
961+
962+
<Suspense fallback={<Placeholder />}>
963+
{activeTab === 'home' && (
964+
<Home />
965+
)}
966+
{activeTab === 'video' && (
967+
<Video />
968+
)}
969+
</Suspense>
970+
</>
971+
)
972+
}
973+
```
974+
975+
...but it would also change the UI, since the `Placeholder` fallback would be displayed on the initial render.
976+
977+
Instead, we can use Activity. Since Activity boundaries show and hide their children, they already naturally divide the component tree into independent units. And just like Suspense, this feature allows them to participate in Selective Hydration.
978+
979+
Let's update our example to use Activity boundaries around the active tab:
980+
981+
```jsx {13-18}
982+
function Page() {
983+
const [activeTab, setActiveTab] = useState('home');
984+
985+
return (
986+
<>
987+
<TabButton onClick={() => setActiveTab('home')}>
988+
Home
989+
</TabButton>
990+
<TabButton onClick={() => setActiveTab('video')}>
991+
Video
992+
</TabButton>
993+
994+
<Activity mode={activeTab === "home" ? "visible" : "hidden"}>
995+
<Home />
996+
</Activity>
997+
<Activity mode={activeTab === "video" ? "visible" : "hidden"}>
998+
<Video />
904999
</Activity>
9051000
</>
9061001
)
9071002
}
9081003
```
9091004

910-
During hydration, React will leave the visible Activity boundary unmounted while hydrating the rest of the page, improving the performance of higher-priority content. Once the high-priority content has fetched its code and data, and been rendered to the page, React will move on to mount any remaining visible Activity boundaries.
1005+
Now our initial server-rendered HTML looks the same as it did in the original version, but thanks to Activity, React can hydrate the tab buttons first, before it even mounts `Home` or `Video`.
9111006

912-
This feature is called Selective Hydration, and it's an under-the-hood optimization of React that's integrated with Suspense. You can read [an architectural overview](https://github.com/reactwg/react-18/discussions/37) and watch [a technical talk](https://www.youtube.com/watch?v=pj5N-Khihgc) to learn more.
1007+
---
1008+
1009+
Thus, in addition to hiding and showing content, Activity boundaries help improve your app's performance during hydration by letting React know which parts of your page can become interactive in isolation.
1010+
1011+
And even if your page doesn't ever hide part of its content, you can still add always-visible Activity boundaries to improve hydration performance:
1012+
1013+
```jsx
1014+
function Page() {
1015+
return (
1016+
<>
1017+
<Post />
1018+
1019+
<Activity>
1020+
<Comments />
1021+
</Activity>
1022+
</>
1023+
);
1024+
}
1025+
```
9131026

9141027
---
9151028

9161029
## Troubleshooting {/*troubleshooting*/}
9171030

9181031
### My hidden components have unwanted side effects {/*my-hidden-components-have-unwanted-side-effects*/}
9191032

920-
An Activity boundary hides its content by setting `display: none` on its children and cleaning up any of their [Effects](/reference/react/useEffect). So, most well-behaved React components that properly clean up their side effects will already be robust to being hidden by Activity.
1033+
An Activity boundary hides its content by setting `display: none` on its children and cleaning up any of their Effects. So, most well-behaved React components that properly clean up their side effects will already be robust to being hidden by Activity.
9211034

9221035
But there _are_ some situations where a hidden component behaves differently than an unmounted one. Most notably, since a hidden component's DOM is not destroyed, any side effects from that DOM will persist, even after the component is hidden.
9231036

0 commit comments

Comments
 (0)