Skip to content

Commit 119177b

Browse files
authored
[Beta] useEvent -> useEffectEvent (#5373)
* [Beta] useEvent -> useEffectEvent * tweak
1 parent 84a1085 commit 119177b

File tree

8 files changed

+136
-134
lines changed

8 files changed

+136
-134
lines changed

beta/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"eslint-plugin-import": "2.x",
6868
"eslint-plugin-jsx-a11y": "6.x",
6969
"eslint-plugin-react": "7.x",
70-
"eslint-plugin-react-hooks": "experimental",
70+
"eslint-plugin-react-hooks": "^0.0.0-experimental-fabef7a6b-20221215",
7171
"fs-extra": "^9.0.1",
7272
"globby": "^11.0.1",
7373
"gray-matter": "^4.0.2",

beta/src/content/apis/react/useEffect.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,11 +1653,11 @@ function Page({ url, shoppingCart }) {
16531653
}
16541654
```
16551655
1656-
**What if you want to log a new page visit after every `url` change, but *not* if only the `shoppingCart` changes?** You can't exclude `shoppingCart` from dependencies without breaking the [reactivity rules.](#specifying-reactive-dependencies) However, you can express that you *don't want* a piece of code to "react" to changes even though it is called from inside an Effect. To do this, [declare an *Event function*](/learn/separating-events-from-effects#declaring-an-event-function) with the [`useEvent`](/apis/react/useEvent) Hook, and move the code that reads `shoppingCart` inside of it:
1656+
**What if you want to log a new page visit after every `url` change, but *not* if only the `shoppingCart` changes?** You can't exclude `shoppingCart` from dependencies without breaking the [reactivity rules.](#specifying-reactive-dependencies) However, you can express that you *don't want* a piece of code to "react" to changes even though it is called from inside an Effect. [Declare an *Effect Event*](/learn/separating-events-from-effects#declaring-an-effect-event) with the [`useEffectEvent`](/apis/react/useEffectEvent) Hook, and move the code that reads `shoppingCart` inside of it:
16571657
16581658
```js {2-4,7,8}
16591659
function Page({ url, shoppingCart }) {
1660-
const onVisit = useEvent(visitedUrl => {
1660+
const onVisit = useEffectEvent(visitedUrl => {
16611661
logVisit(visitedUrl, shoppingCart.length)
16621662
});
16631663

@@ -1668,9 +1668,9 @@ function Page({ url, shoppingCart }) {
16681668
}
16691669
```
16701670
1671-
**Event functions are not reactive and don't need to be specified as dependencies of your Effect.** This is what lets you put non-reactive code (where you can read the latest value of some props and state) inside of them. For example, by reading `shoppingCart` inside of `onVisit`, you ensure that `shoppingCart` won't re-run your Effect.
1671+
**Effect Events are not reactive and must always be omitted from dependencies of your Effect.** This is what lets you put non-reactive code (where you can read the latest value of some props and state) inside of them. For example, by reading `shoppingCart` inside of `onVisit`, you ensure that `shoppingCart` won't re-run your Effect. In the future, the linter will support `useEffectEvent` and check that you omit Effect Events from dependencies.
16721672
1673-
[Read more about how Event functions let you separate reactive and non-reactive code.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-event-functions)
1673+
[Read more about how Effect Events let you separate reactive and non-reactive code.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events)
16741674
16751675
16761676
---
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
title: useEffectEvent
3+
---
4+
5+
<Wip>
6+
7+
**This API is experimental and is not available in a stable version of React yet.**
8+
9+
See [`useEffectEvent` RFC](https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md) for details.
10+
11+
</Wip>
12+
13+
14+
<Intro>
15+
16+
`useEffectEvent` is a React Hook that lets you extract non-reactive logic into an [Effect Event.](/learn/separating-events-from-effects#declaring-an-effect-event)
17+
18+
```js
19+
const onSomething = useEffectEvent(callback)
20+
```
21+
22+
</Intro>
23+
24+
<InlineToc />

beta/src/content/apis/react/useEvent.md

Lines changed: 0 additions & 22 deletions
This file was deleted.

beta/src/content/learn/removing-effect-dependencies.md

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ Let's say that you wanted to run the Effect "only on mount". You've read that [e
350350
351351
This counter was supposed to increment every second by the amount configurable with the two buttons. However, since you "lied" to React that this Effect doesn't depend on anything, React forever keeps using the `onTick` function from the initial render. [During that render,](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time) `count` was `0` and `increment` was `1`. This is why `onTick` from that render always calls `setCount(0 + 1)` every second, and you always see `1`. Bugs like this are harder to fix when they're spread across multiple components.
352352
353-
There's always a better solution than ignoring the linter! To fix this code, you need to add `onTick` to the dependency list. (To ensure the interval is only setup once, [make `onTick` an Event function.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-event-functions))
353+
There's always a better solution than ignoring the linter! To fix this code, you need to add `onTick` to the dependency list. (To ensure the interval is only setup once, [make `onTick` an Effect Event.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events))
354354
355355
**We recommend to treat the dependency lint error as a compilation error. If you don't suppress it, you will never see bugs like this.** The rest of this page documents the alternatives for this and other cases.
356356
@@ -655,16 +655,16 @@ function ChatRoom({ roomId }) {
655655
656656
The problem is that every time `isMuted` changes (for example, when the user presses the "Muted" toggle), the Effect will re-synchronize, and reconnect to the chat server. This is not the desired user experience! (In this example, even disabling the linter would not work--if you do that, `isMuted` would get "stuck" with its old value.)
657657
658-
To solve this problem, you need to extract the logic that shouldn't be reactive out of the Effect. You don't want this Effect to "react" to the changes in `isMuted`. [Move this non-reactive piece of logic into an Event function:](/learn/separating-events-from-effects#declaring-an-event-function)
658+
To solve this problem, you need to extract the logic that shouldn't be reactive out of the Effect. You don't want this Effect to "react" to the changes in `isMuted`. [Move this non-reactive piece of logic into an Effect Event:](/learn/separating-events-from-effects#declaring-an-effect-event)
659659
660660
```js {1,7-12,18,21}
661-
import { useState, useEffect, useEvent } from 'react';
661+
import { useState, useEffect, useEffectEvent } from 'react';
662662

663663
function ChatRoom({ roomId }) {
664664
const [messages, setMessages] = useState([]);
665665
const [isMuted, setIsMuted] = useState(false);
666666

667-
const onMessage = useEvent(receivedMessage => {
667+
const onMessage = useEffectEvent(receivedMessage => {
668668
setMessages(msgs => [...msgs, receivedMessage]);
669669
if (!isMuted) {
670670
playSound();
@@ -682,7 +682,7 @@ function ChatRoom({ roomId }) {
682682
// ...
683683
```
684684
685-
Event functions let you split an Effect into reactive parts (which should "react" to reactive values like `roomId` and their changes) and non-reactive parts (which only read their latest values, like `onMessage` reads `isMuted`). **Now that you read `isMuted` inside an Event function, it doesn't need to be a dependency of your Effect.** As a result, the chat won't re-connect when you toggle the "Muted" setting on and off, solving the original issue!
685+
Effect Events let you split an Effect into reactive parts (which should "react" to reactive values like `roomId` and their changes) and non-reactive parts (which only read their latest values, like `onMessage` reads `isMuted`). **Now that you read `isMuted` inside an Effect Event, it doesn't need to be a dependency of your Effect.** As a result, the chat won't re-connect when you toggle the "Muted" setting on and off, solving the original issue!
686686
687687
#### Wrapping an event handler from the props {/*wrapping-an-event-handler-from-the-props*/}
688688
@@ -714,13 +714,13 @@ Suppose that the parent component passes a *different* `onReceiveMessage` functi
714714
/>
715715
```
716716
717-
Since `onReceiveMessage` is a dependency of your Effect, it would cause the Effect to re-synchronize after every parent re-render. This would make it re-connect to the chat. To solve this, wrap the call in an Event function:
717+
Since `onReceiveMessage` is a dependency of your Effect, it would cause the Effect to re-synchronize after every parent re-render. This would make it re-connect to the chat. To solve this, wrap the call in an Effect Event:
718718
719719
```js {4-6,12,15}
720720
function ChatRoom({ roomId, onReceiveMessage }) {
721721
const [messages, setMessages] = useState([]);
722722

723-
const onMessage = useEvent(receivedMessage => {
723+
const onMessage = useEffectEvent(receivedMessage => {
724724
onReceiveMessage(receivedMessage);
725725
});
726726

@@ -735,17 +735,17 @@ function ChatRoom({ roomId, onReceiveMessage }) {
735735
// ...
736736
```
737737
738-
Event functions aren't reactive, so you don't need to specify them as dependencies. As a result, the chat will no longer re-connect even if the parent component passes a function that's different on every re-render.
738+
Effect Events aren't reactive, so you don't need to specify them as dependencies. As a result, the chat will no longer re-connect even if the parent component passes a function that's different on every re-render.
739739
740740
#### Separating reactive and non-reactive code {/*separating-reactive-and-non-reactive-code*/}
741741
742742
In this example, you want to log a visit every time `roomId` changes. You want to include the current `notificationCount` with every log, but you *don't* want a change to `notificationCount` to trigger a log event.
743743
744-
The solution is again to split out the non-reactive code into an Event function:
744+
The solution is again to split out the non-reactive code into an Effect Event:
745745
746746
```js {2-4,7}
747747
function Chat({ roomId, notificationCount }) {
748-
const onVisit = useEvent(visitedRoomId => {
748+
const onVisit = useEffectEvent(visitedRoomId => {
749749
logVisit(visitedRoomId, notificationCount);
750750
});
751751

@@ -756,7 +756,7 @@ function Chat({ roomId, notificationCount }) {
756756
}
757757
```
758758
759-
You want your logic to be reactive with regards to `roomId`, so you read `roomId` inside of your Effect. However, you don't want a change to `notificationCount` to log an extra visit, so you read `notificationCount` inside of the Event function. [Learn more about reading the latest props and state from Effects using Event functions.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-event-functions)
759+
You want your logic to be reactive with regards to `roomId`, so you read `roomId` inside of your Effect. However, you don't want a change to `notificationCount` to log an extra visit, so you read `notificationCount` inside of the Effect Event. [Learn more about reading the latest props and state from Effects using Effect Events.](/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events)
760760
761761
### Does some reactive value change unintentionally? {/*does-some-reactive-value-change-unintentionally*/}
762762
@@ -1150,7 +1150,7 @@ function ChatRoom({ getOptions }) {
11501150
// ...
11511151
```
11521152

1153-
This only works for [pure](/learn/keeping-components-pure) functions because they are safe to call during rendering. If your function is an event handler, but you don't want its changes to re-synchronize your Effect, [wrap it into an Event function instead.](#do-you-want-to-read-a-value-without-reacting-to-its-changes)
1153+
This only works for [pure](/learn/keeping-components-pure) functions because they are safe to call during rendering. If your function is an event handler, but you don't want its changes to re-synchronize your Effect, [wrap it into an Effect Event instead.](#do-you-want-to-read-a-value-without-reacting-to-its-changes)
11541154

11551155
<Recap>
11561156

@@ -1161,7 +1161,7 @@ This only works for [pure](/learn/keeping-components-pure) functions because the
11611161
- If the code in your Effect should run in response to a specific interaction, move that code to an event handler.
11621162
- If different parts of your Effect should re-run for different reasons, split it into several Effects.
11631163
- If you want to update some state based on the previous state, pass an updater function.
1164-
- If you want to read the latest value without "reacting" it, extract an Event function from your Effect.
1164+
- If you want to read the latest value without "reacting" it, extract an Effect Event from your Effect.
11651165
- In JavaScript, objects and functions are considered different if they were created at different times.
11661166
- Try to avoid object and function dependencies. Move them outside the component or inside the Effect.
11671167

@@ -1273,7 +1273,7 @@ Is there a line of code inside the Effect that should not be reactive? How can y
12731273
12741274
```js
12751275
import { useState, useEffect, useRef } from 'react';
1276-
import { experimental_useEvent as useEvent } from 'react';
1276+
import { experimental_useEffectEvent as useEffectEvent } from 'react';
12771277
import { FadeInAnimation } from './animation.js';
12781278

12791279
function Welcome({ duration }) {
@@ -1378,7 +1378,7 @@ html, body { min-height: 300px; }
13781378
13791379
<Solution>
13801380
1381-
Your Effect needs to read the latest value of `duration`, but you don't want it to "react" to changes in `duration`. You use `duration` to start the animation, but starting animation isn't reactive. Extract the non-reactive line of code into an Event function, and call that function from your Effect.
1381+
Your Effect needs to read the latest value of `duration`, but you don't want it to "react" to changes in `duration`. You use `duration` to start the animation, but starting animation isn't reactive. Extract the non-reactive line of code into an Effect Event, and call that function from your Effect.
13821382
13831383
<Sandpack>
13841384
@@ -1401,12 +1401,12 @@ Your Effect needs to read the latest value of `duration`, but you don't want it
14011401
```js
14021402
import { useState, useEffect, useRef } from 'react';
14031403
import { FadeInAnimation } from './animation.js';
1404-
import { experimental_useEvent as useEvent } from 'react';
1404+
import { experimental_useEffectEvent as useEffectEvent } from 'react';
14051405

14061406
function Welcome({ duration }) {
14071407
const ref = useRef(null);
14081408

1409-
const onAppear = useEvent(animation => {
1409+
const onAppear = useEffectEvent(animation => {
14101410
animation.start(duration);
14111411
});
14121412

@@ -1501,7 +1501,7 @@ html, body { min-height: 300px; }
15011501
15021502
</Sandpack>
15031503
1504-
Event functions like `onAppear` are not reactive, so you can read `duration` inside without retriggering the animation.
1504+
Effect Events like `onAppear` are not reactive, so you can read `duration` inside without retriggering the animation.
15051505
15061506
</Solution>
15071507
@@ -1903,7 +1903,7 @@ export default function App() {
19031903
19041904
```js ChatRoom.js active
19051905
import { useState, useEffect } from 'react';
1906-
import { experimental_useEvent as useEvent } from 'react';
1906+
import { experimental_useEffectEvent as useEffectEvent } from 'react';
19071907

19081908
export default function ChatRoom({ roomId, createConnection, onMessage }) {
19091909
useEffect(() => {
@@ -2031,19 +2031,19 @@ There's more than one correct way to solve this, but the here is one possible so
20312031
20322032
In the original example, toggling the theme caused different `onMessage` and `createConnection` functions to be created and passed down. Since the Effect depended on these functions, the chat would re-connect every time you toggle the theme.
20332033
2034-
To fix the problem with `onMessage`, you needed to wrap it into an Event function:
2034+
To fix the problem with `onMessage`, you needed to wrap it into an Effect Event:
20352035
20362036
```js {1,2,6}
20372037
export default function ChatRoom({ roomId, createConnection, onMessage }) {
2038-
const onReceiveMessage = useEvent(onMessage);
2038+
const onReceiveMessage = useEffectEvent(onMessage);
20392039

20402040
useEffect(() => {
20412041
const connection = createConnection();
20422042
connection.on('message', (msg) => onReceiveMessage(msg));
20432043
// ...
20442044
```
20452045
2046-
Unlike the `onMessage` prop, the `onReceiveMessage` Event function is not reactive. This is why it doesn't need to be a dependency of your Effect. As a result, changes to `onMessage` won't cause the chat to re-connect.
2046+
Unlike the `onMessage` prop, the `onReceiveMessage` Effect Event is not reactive. This is why it doesn't need to be a dependency of your Effect. As a result, changes to `onMessage` won't cause the chat to re-connect.
20472047
20482048
You can't do the same with `createConnection` because it *should* be reactive. You *want* the Effect to re-trigger if the user switches between an encrypted and an unencryption connection, or if the user switches the current room. However, because `createConnection` is a function, you can't check whether the information it reads has *actually* changed or not. To solve this, instead of passing `createConnection` down from the `App` component, pass the raw `roomId` and `isEncrypted` values:
20492049
@@ -2066,7 +2066,7 @@ import {
20662066
} from './chat.js';
20672067

20682068
export default function ChatRoom({ roomId, isEncrypted, onMessage }) {
2069-
const onReceiveMessage = useEvent(onMessage);
2069+
const onReceiveMessage = useEffectEvent(onMessage);
20702070

20712071
useEffect(() => {
20722072
function createConnection() {
@@ -2087,7 +2087,7 @@ After these two changes, your Effect no longer depends on any function values:
20872087
20882088
```js {1,8,10,21}
20892089
export default function ChatRoom({ roomId, isEncrypted, onMessage }) { // Reactive values
2090-
const onReceiveMessage = useEvent(onMessage); // Not reactive
2090+
const onReceiveMessage = useEffectEvent(onMessage); // Not reactive
20912091

20922092
useEffect(() => {
20932093
function createConnection() {
@@ -2185,14 +2185,14 @@ export default function App() {
21852185
21862186
```js ChatRoom.js active
21872187
import { useState, useEffect } from 'react';
2188-
import { experimental_useEvent as useEvent } from 'react';
2188+
import { experimental_useEffectEvent as useEffectEvent } from 'react';
21892189
import {
21902190
createEncryptedConnection,
21912191
createUnencryptedConnection,
21922192
} from './chat.js';
21932193

21942194
export default function ChatRoom({ roomId, isEncrypted, onMessage }) {
2195-
const onReceiveMessage = useEvent(onMessage);
2195+
const onReceiveMessage = useEffectEvent(onMessage);
21962196

21972197
useEffect(() => {
21982198
function createConnection() {

0 commit comments

Comments
 (0)