Skip to content

Commit 5041f27

Browse files
committed
useLayoutEffect + Tidying up
1 parent 599d172 commit 5041f27

File tree

1 file changed

+49
-47
lines changed

1 file changed

+49
-47
lines changed

src/content/reference/react/Activity.md

Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,8 @@ In this example, the Contact tab has a `<textarea>` where the user can enter a m
340340
```js src/App.js
341341
import { useState } from 'react';
342342
import TabButton from './TabButton.js';
343-
import HomeTab from './HomeTab.js';
344-
import ContactTab from './ContactTab.js';
343+
import Home from './Home.js';
344+
import Contact from './Contact.js';
345345

346346
export default function App() {
347347
const [activeTab, setActiveTab] = useState('contact');
@@ -363,8 +363,8 @@ export default function App() {
363363

364364
<hr />
365365

366-
{activeTab === 'home' && <HomeTab />}
367-
{activeTab === 'contact' && <ContactTab />}
366+
{activeTab === 'home' && <Home />}
367+
{activeTab === 'contact' && <Contact />}
368368
</>
369369
);
370370
}
@@ -384,16 +384,16 @@ export default function TabButton({ onClick, children, isActive }) {
384384
}
385385
```
386386

387-
```js src/HomeTab.js
388-
export default function HomeTab() {
387+
```js src/Home.js
388+
export default function Home() {
389389
return (
390390
<p>Welcome to my profile!</p>
391391
);
392392
}
393393
```
394394

395-
```js src/ContactTab.js active
396-
export default function ContactTab() {
395+
```js src/Contact.js active
396+
export default function Contact() {
397397
return (
398398
<div>
399399
<p>Send me a message!</p>
@@ -436,7 +436,7 @@ b { display: inline-block; margin-right: 10px; }
436436

437437
</Sandpack>
438438

439-
This is because we're fully unmounting `ContactTab` in `App`. When the Contact tab unmounts, the `<textarea>` element's internal DOM state is lost.
439+
This is because we're fully unmounting `Contact` in `App`. When the Contact tab unmounts, the `<textarea>` element's internal DOM state is lost.
440440

441441
If we switch to using an Activity boundary to show and hide the active tab, we can preserve the state of each tab's DOM. Try entering text and switching tabs again, and you'll see the draft message is no longer reset:
442442

@@ -445,8 +445,8 @@ If we switch to using an Activity boundary to show and hide the active tab, we c
445445
```js src/App.js active
446446
import { useState, unstable_Activity as Activity } from 'react';
447447
import TabButton from './TabButton.js';
448-
import HomeTab from './HomeTab.js';
449-
import ContactTab from './ContactTab.js';
448+
import Home from './Home.js';
449+
import Contact from './Contact.js';
450450

451451
export default function App() {
452452
const [activeTab, setActiveTab] = useState('contact');
@@ -469,10 +469,10 @@ export default function App() {
469469
<hr />
470470

471471
<Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
472-
<HomeTab />
472+
<Home />
473473
</Activity>
474474
<Activity mode={activeTab === 'contact' ? 'visible' : 'hidden'}>
475-
<ContactTab />
475+
<Contact />
476476
</Activity>
477477
</>
478478
);
@@ -493,16 +493,16 @@ export default function TabButton({ onClick, children, isActive }) {
493493
}
494494
```
495495

496-
```js src/HomeTab.js
497-
export default function HomeTab() {
496+
```js src/Home.js
497+
export default function Home() {
498498
return (
499499
<p>Welcome to my profile!</p>
500500
);
501501
}
502502
```
503503

504-
```js src/ContactTab.js
505-
export default function ContactTab() {
504+
```js src/Contact.js
505+
export default function Contact() {
506506
return (
507507
<div>
508508
<p>Send me a message!</p>
@@ -915,7 +915,7 @@ This feature is called Selective Hydration, and it's an under-the-hood optimizat
915915

916916
## Troubleshooting {/*troubleshooting*/}
917917

918-
### Preventing hidden content from having unwanted side effects {/*preventing-hidden-content-from-having-unwanted-side-effects*/}
918+
### My hidden components have unwanted side effects {/*my-hidden-components-have-unwanted-side-effects*/}
919919

920920
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.
921921

@@ -928,8 +928,8 @@ As an example, consider a `<video>` tag. Typically it doesn't require any cleanu
928928
```js src/App.js active
929929
import { useState } from 'react';
930930
import TabButton from './TabButton.js';
931-
import HomeTab from './HomeTab.js';
932-
import VideoTab from './VideoTab.js';
931+
import Home from './Home.js';
932+
import Video from './Video.js';
933933

934934
export default function App() {
935935
const [activeTab, setActiveTab] = useState('video');
@@ -951,8 +951,8 @@ export default function App() {
951951

952952
<hr />
953953

954-
{activeTab === 'home' && <HomeTab />}
955-
{activeTab === 'video' && <VideoTab />}
954+
{activeTab === 'home' && <Home />}
955+
{activeTab === 'video' && <Video />}
956956
</>
957957
);
958958
}
@@ -972,16 +972,16 @@ export default function TabButton({ onClick, children, isActive }) {
972972
}
973973
```
974974

975-
```js src/HomeTab.js
976-
export default function HomeTab() {
975+
```js src/Home.js
976+
export default function Home() {
977977
return (
978978
<p>Welcome to my profile!</p>
979979
);
980980
}
981981
```
982982

983-
```js src/VideoTab.js
984-
export default function VideoTab() {
983+
```js src/Video.js
984+
export default function Video() {
985985
return (
986986
<video
987987
// 'Big Buck Bunny' licensed under CC 3.0 by the Blender foundation. Hosted by archive.org
@@ -1034,8 +1034,8 @@ Let's update `App` to hide the inactive tab with a hidden Activity boundary inst
10341034
```js src/App.js active
10351035
import { useState, unstable_Activity as Activity } from 'react';
10361036
import TabButton from './TabButton.js';
1037-
import HomeTab from './HomeTab.js';
1038-
import VideoTab from './VideoTab.js';
1037+
import Home from './Home.js';
1038+
import Video from './Video.js';
10391039

10401040
export default function App() {
10411041
const [activeTab, setActiveTab] = useState('video');
@@ -1058,10 +1058,10 @@ export default function App() {
10581058
<hr />
10591059

10601060
<Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
1061-
<HomeTab />
1061+
<Home />
10621062
</Activity>
10631063
<Activity mode={activeTab === 'video' ? 'visible' : 'hidden'}>
1064-
<VideoTab />
1064+
<Video />
10651065
</Activity>
10661066
</>
10671067
);
@@ -1082,16 +1082,16 @@ export default function TabButton({ onClick, children, isActive }) {
10821082
}
10831083
```
10841084

1085-
```js src/HomeTab.js
1086-
export default function HomeTab() {
1085+
```js src/Home.js
1086+
export default function Home() {
10871087
return (
10881088
<p>Welcome to my profile!</p>
10891089
);
10901090
}
10911091
```
10921092

1093-
```js src/VideoTab.js
1094-
export default function VideoTab() {
1093+
```js src/Video.js
1094+
export default function Video() {
10951095
return (
10961096
<video
10971097
controls
@@ -1139,7 +1139,7 @@ To fix this, we can add an Effect with a cleanup function that pauses the video:
11391139
export default function VideoTab() {
11401140
const ref = useRef();
11411141

1142-
useEffect(() => {
1142+
useLayoutEffect(() => {
11431143
const videoRef = ref.current;
11441144

11451145
return () => {
@@ -1159,15 +1159,17 @@ export default function VideoTab() {
11591159
}
11601160
```
11611161

1162+
We call `useLayoutEffect` instead of `useEffect` because conceptually the clean-up code is tied to the component's UI being visually hidden. If we used a regular effect, the code could be delayed by (say) a re-suspending Suspense boundary or a View Transition.
1163+
11621164
Let's see the new behavior:
11631165

11641166
<Sandpack>
11651167

11661168
```js src/App.js active
11671169
import { useState, unstable_Activity as Activity } from 'react';
11681170
import TabButton from './TabButton.js';
1169-
import HomeTab from './HomeTab.js';
1170-
import VideoTab from './VideoTab.js';
1171+
import Home from './Home.js';
1172+
import Video from './Video.js';
11711173

11721174
export default function App() {
11731175
const [activeTab, setActiveTab] = useState('video');
@@ -1190,10 +1192,10 @@ export default function App() {
11901192
<hr />
11911193

11921194
<Activity mode={activeTab === 'home' ? 'visible' : 'hidden'}>
1193-
<HomeTab />
1195+
<Home />
11941196
</Activity>
11951197
<Activity mode={activeTab === 'video' ? 'visible' : 'hidden'}>
1196-
<VideoTab />
1198+
<Video />
11971199
</Activity>
11981200
</>
11991201
);
@@ -1214,21 +1216,21 @@ export default function TabButton({ onClick, children, isActive }) {
12141216
}
12151217
```
12161218

1217-
```js src/HomeTab.js
1218-
export default function HomeTab() {
1219+
```js src/Home.js
1220+
export default function Home() {
12191221
return (
12201222
<p>Welcome to my profile!</p>
12211223
);
12221224
}
12231225
```
12241226

1225-
```js src/VideoTab.js
1226-
import { useRef, useEffect } from 'react';
1227+
```js src/Video.js
1228+
import { useRef, useLayoutEffect } from 'react';
12271229

1228-
export default function VideoTab() {
1230+
export default function Video() {
12291231
const ref = useRef();
12301232

1231-
useEffect(() => {
1233+
useLayoutEffect(() => {
12321234
const videoRef = ref.current
12331235

12341236
return () => {
@@ -1297,9 +1299,9 @@ To eagerly discover other Effects that don't have proper cleanup, which is impor
12971299
---
12981300

12991301

1300-
### Effects don't mount when an Activity is hidden {/*effects-dont-mount-when-an-activity-is-hidden*/}
1302+
### My hidden components have Effects that aren't running {/*my-hidden-components-have-effects-that-arent-running*/}
13011303

1302-
When an `<Activity>` is "hidden", all Effects are cleaned up. Conceptually, the children are unmounted, but React saves their state for later. This is a feature of Activity because it means subscriptions won't be active for hidden parts of the UI, reducing the amount of work needed for hidden content.
1304+
When an `<Activity>` is "hidden", all its children's Effects are cleaned up. Conceptually, the children are unmounted, but React saves their state for later. This is a feature of Activity because it means subscriptions won't be active for hidden parts of the UI, reducing the amount of work needed for hidden content.
13031305

13041306
If you're relying on an Effect mounting to clean up a component's side effects, refactor the Effect to do the work in the returned cleanup function instead.
13051307

0 commit comments

Comments
 (0)