You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: beta/src/pages/learn/synchronizing-with-effects.md
+81-11Lines changed: 81 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -765,6 +765,76 @@ Buying is not caused by rendering; it's caused by a specific interaction. It onl
765
765
766
766
## Putting it all together {/*putting-it-all-together*/}
767
767
768
+
This playground can help you "get a feel" for how Effects work in practice.
769
+
770
+
This example uses [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) to schedule a console log with the input text to appear three seconds after the Effect runs. The cleanup function cancels the pending timeout. Start by pressing "Mount the component":
771
+
772
+
<Sandpack>
773
+
774
+
```js
775
+
import { useState, useEffect } from'react';
776
+
777
+
functionPlayground() {
778
+
const [text, setText] =useState('a');
779
+
780
+
useEffect(() => {
781
+
functiononTimeout() {
782
+
console.log('⏰ '+ text);
783
+
}
784
+
785
+
console.log('🔵 Schedule "'+ text +'" log');
786
+
consttimeoutId=setTimeout(onTimeout, 3000);
787
+
788
+
return () => {
789
+
console.log('🟡 Cancel "'+ text +'" log');
790
+
clearTimeout(timeoutId);
791
+
};
792
+
}, [text]);
793
+
794
+
return (
795
+
<>
796
+
<label>
797
+
What to log:{''}
798
+
<input
799
+
value={text}
800
+
onChange={e=>setText(e.target.value)}
801
+
/>
802
+
</label>
803
+
<h1>{text}</h1>
804
+
</>
805
+
);
806
+
}
807
+
808
+
exportdefaultfunctionApp() {
809
+
const [show, setShow] =useState(false);
810
+
return (
811
+
<>
812
+
<button onClick={() =>setShow(!show)}>
813
+
{show ?'Unmount':'Mount'} the component
814
+
</button>
815
+
{show &&<hr />}
816
+
{show &&<Playground />}
817
+
</>
818
+
);
819
+
}
820
+
```
821
+
822
+
</Sandpack>
823
+
824
+
Expand the console panel in the sandbox above.
825
+
826
+
You will see three logs at first: `Schedule "a" log`, `Cancel "a" log`, and `Schedule "a" log` again. Three second later there will also be a log saying `a`. As you learned earlier on this page, the extra schedule/cancel pair is because **React remounts the component once in development to verify that you've implemented cleanup well.**
827
+
828
+
Now edit the input to say `abc`. If you do it fast enough, you'll see `Schedule "ab" log` immediately followed by `Cancel "ab" log` and `Schedule "abc" log`. **React always cleans up the previous render's Effect before the next render's Effect.** This is why you even if you type into the input fast, there is at most one timeout scheduled at a time. Edit the input a few times and watch the console to get a feel for how Effects get cleaned up.
829
+
830
+
Type something into the input and then immediately press "Unmount the component." **Notice how unmounting cleans up the last render's Effect.** In this example, it clears the last timeout before it has a chance to fire.
831
+
832
+
Finally, edit the component above and **comment out the cleanup function** so that the timeouts don't get cancelled. Try typing `abcde` fast. What do you expect to happen in three seconds? Will `console.log(text)` inside the timeout print the *latest* `text` and produce five `abcde` logs? Give it a try to check your intution!
833
+
834
+
Three seconds later, you should see a sequence of logs (`a`, `ab`, `abc`, `abcd`, and `abcde`) rather than five `abcde` logs. **Each Effect "captures" the `text` value from its corresponding render**. It doesn't matter that the `text` state changed: an Effect from the render with `text ='ab'` will always see `'ab'`. In other words, Effects from each render are isolated from each other. If you're curious how this works, you can read about [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures).
835
+
836
+
<DeepDive title="Each render has its own Effects">
837
+
768
838
You can think of `useEffect` as "attaching" a piece of behavior to the render output. Consider this Effect:
Let's see what exactly happens as the user navigates around the app.
783
853
784
-
### Initial render {/*initial-render*/}
854
+
#### Initial render {/*initial-render*/}
785
855
786
856
The user visits `<ChatRoom roomId="general"/>`. Let's [mentally substitute](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time) `roomId` with `'general'`:
787
857
@@ -805,7 +875,7 @@ The user visits `<ChatRoom roomId="general" />`. Let's [mentally substitute](/le
805
875
806
876
React runs this Effect, which connects to the `'general'` chat room.
807
877
808
-
### Re-render with same dependencies {/*re-render-with-same-dependencies*/}
878
+
#### Re-render with same dependencies {/*re-render-with-same-dependencies*/}
809
879
810
880
Let's say `<ChatRoom roomId="general"/>` re-renders. The JSX output is the same:
811
881
@@ -831,7 +901,7 @@ The Effect from the second render looks like this:
831
901
832
902
React compares `['general']` from the second render with `['general']` from the first render. **Because all dependencies are the same, React *ignores* the Effect from the second render.** It never gets called.
833
903
834
-
### Re-render with different dependencies {/*re-render-with-different-dependencies*/}
904
+
#### Re-render with different dependencies {/*re-render-with-different-dependencies*/}
835
905
836
906
Then, the user visits `<ChatRoom roomId="travel"/>`. This time, the component returns different JSX:
837
907
@@ -855,22 +925,22 @@ The Effect from the third render looks like this:
855
925
['travel']
856
926
```
857
927
858
-
React compares `['travel']` from the third render with `['general']` from the second render. This time, one dependency is different: `Object.is('travel', 'general')` is `false`. The Effect can't be skipped.
859
-
860
-
**Before React can apply the Effect from the third render, it needs to clean up the last Effect that _did_ run.** Effect from the second render was skipped, so the Effect React needs to clean up is from the first render. If you scroll up to the first render, you'll see that its cleanup calls `connection.disconnect()` on the connection that was created with `createConnection('general')`. This disconnects the app from the `'general'` chat room.
928
+
React compares `['travel']` from the third render with `['general']` from the second render. One dependency is different: `Object.is('travel', 'general')` is `false`. The Effect can't be skipped.
861
929
862
-
After the last Effect is cleaned up, React runs the third render's Effect. It connects to the `'travel'` chat room.
930
+
**Before React can apply the Effect from the third render, it needs to clean up the last Effect that _did_ run.** The second render's Effect was skipped, so React needs to clean up the first render's Effect. If you scroll up to the first render, you'll see that its cleanup calls `disconnect()` on the connection that was created with `createConnection('general')`. This disconnects the app from the `'general'` chat room.
863
931
864
-
### Unmount {/*unmount*/}
932
+
After that, React runs the third render's Effect. It connects to the `'travel'` chat room.
865
933
866
-
Finally, let's say the user navigates away, and the `ChatRoom` component unmounts.
934
+
#### Unmount {/*unmount*/}
867
935
868
-
React run the last Effect's cleanup function. The last Effect was from the third render. The third render's cleanup destroys the `createConnection('travel')` connection. So the app disconnects from the `'travel'` room.
936
+
Finally, let's say the user navigates away, and the `ChatRoom` component unmounts. React runs the last Effect's cleanup function. The last Effect was from the third render. The third render's cleanup destroys the `createConnection('travel')` connection. So the app disconnects from the `'travel'` room.
When [Strict Mode](/apis/strictmode) is on, React remounts every component once after mount (state and DOM are preserved). This [helps you find Effects that need cleanup](#step-3-add-cleanup-if-needed) and exposes bugs like race conditions early. Additionally, React will remount the Effects whenever you save a file in development. Both of these behaviors are development-only.
873
941
942
+
</DeepDive>
943
+
874
944
<Recap>
875
945
876
946
- Unlike events, Effects are caused by rendering itself rather than a particular interaction.
0 commit comments