From 2a74b007011ae9cd6a0bdcf7f75e0bce7bbcb126 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 12 Mar 2019 21:17:55 +0000 Subject: [PATCH 01/10] Add more info to FAQ about deps --- content/docs/hooks-custom.md | 24 ++-- content/docs/hooks-effect.md | 37 ++++-- content/docs/hooks-faq.md | 229 +++++++++++++++++++++++++++++++- content/docs/hooks-reference.md | 29 ++-- content/docs/hooks-rules.md | 3 +- 5 files changed, 285 insertions(+), 37 deletions(-) diff --git a/content/docs/hooks-custom.md b/content/docs/hooks-custom.md index ac9dad6daa1..a252ac3f3db 100644 --- a/content/docs/hooks-custom.md +++ b/content/docs/hooks-custom.md @@ -18,11 +18,11 @@ import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); - function handleStatusChange(status) { - setIsOnline(status.isOnline); - } - useEffect(() => { + function handleStatusChange(status) { + setIsOnline(status.isOnline); + } + ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); @@ -44,11 +44,11 @@ import React, { useState, useEffect } from 'react'; function FriendListItem(props) { const [isOnline, setIsOnline] = useState(null); - function handleStatusChange(status) { - setIsOnline(status.isOnline); - } - useEffect(() => { + function handleStatusChange(status) { + setIsOnline(status.isOnline); + } + ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); @@ -79,11 +79,11 @@ import React, { useState, useEffect } from 'react'; function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); - function handleStatusChange(status) { - setIsOnline(status.isOnline); - } - useEffect(() => { + function handleStatusChange(status) { + setIsOnline(status.isOnline); + } + ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); diff --git a/content/docs/hooks-effect.md b/content/docs/hooks-effect.md index 64b32476ef2..9790400cc42 100644 --- a/content/docs/hooks-effect.md +++ b/content/docs/hooks-effect.md @@ -198,17 +198,17 @@ Let's see how we could write this component with Hooks. You might be thinking that we'd need a separate effect to perform the cleanup. But code for adding and removing a subscription is so tightly related that `useEffect` is designed to keep it together. If your effect returns a function, React will run it when it is time to clean up: -```js{10-16} +```js{6-16} import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); - function handleStatusChange(status) { - setIsOnline(status.isOnline); - } - useEffect(() => { + function handleStatusChange(status) { + setIsOnline(status.isOnline); + } + ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // Specify how to clean up after this effect: return function cleanup() { @@ -237,6 +237,10 @@ We've learned that `useEffect` lets us express different kinds of side effects a ```js useEffect(() => { + function handleStatusChange(status) { + setIsOnline(status.isOnline); + } + ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); @@ -316,15 +320,15 @@ function FriendStatusWithCounter(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { + function handleStatusChange(status) { + setIsOnline(status.isOnline); + } + ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); - - function handleStatusChange(status) { - setIsOnline(status.isOnline); - } // ... } ``` @@ -394,6 +398,7 @@ Now consider the version of this component that uses Hooks: function FriendStatus(props) { // ... useEffect(() => { + // ... ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); @@ -449,8 +454,12 @@ When we render with `count` updated to `6`, React will compare the items in the This also works for effects that have a cleanup phase: -```js{6} +```js{10} useEffect(() => { + function handleStatusChange(status) { + setIsOnline(status.isOnline); + } + ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); @@ -462,9 +471,13 @@ In the future, the second argument might get added automatically by a build-time >Note > ->If you use this optimization, make sure the array includes **any values from the outer scope that change over time and that are used by the effect**. Otherwise, your code will reference stale values from previous renders. We'll also discuss other optimization options in the [Hooks API reference](/docs/hooks-reference.html). +>If you use this optimization, make sure the array includes **any values from the component scope (such as props and state) that change over time and that are used by the effect**. Otherwise, your code will reference stale values from previous renders. Learn more about how to [how to deal with functions](/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) and what to do when the [array values change too often](/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often). +> +>If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array (`[]`) as a second argument. This tells React that your effect doesn't depend on *any* values from props or state, so it never needs to re-run. This isn't handled as a special case -- it follows directly from how the inputs array always works. +> +>If you pass an empty array (`[]`), the props and state inside the effect will always have their initial values. While passing `[]` as the second argument is closer to the familiar `componentDidMount` and `componentWillUnmount` mental model, there are usually [better](/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) [solutions](/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often) to avoid re-running effects too often. Also, don't forget that React defers running `useEffect` until after the browser has painted, so doing extra work is less of a problem. > ->If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array (`[]`) as a second argument. This tells React that your effect doesn't depend on *any* values from props or state, so it never needs to re-run. This isn't handled as a special case -- it follows directly from how the inputs array always works. While passing `[]` is closer to the familiar `componentDidMount` and `componentWillUnmount` mental model, we suggest not making it a habit because it often leads to bugs, [as discussed above](#explanation-why-effects-run-on-each-update). Don't forget that React defers running `useEffect` until after the browser has painted, so doing extra work is less of a problem. +>We provide an [`exhaustive-deps`](https://github.com/facebook/react/issues/14920) ESLint rule as a part of the [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation) package. It warns when dependencies are specified incorrectly and suggests a fix. ## Next Steps {#next-steps} diff --git a/content/docs/hooks-faq.md b/content/docs/hooks-faq.md index b3b99de9021..468bcf49c6d 100644 --- a/content/docs/hooks-faq.md +++ b/content/docs/hooks-faq.md @@ -36,12 +36,15 @@ This page answers some of the frequently asked questions about [Hooks](/docs/hoo * [Should I use one or many state variables?](#should-i-use-one-or-many-state-variables) * [Can I run an effect only on updates?](#can-i-run-an-effect-only-on-updates) * [How to get the previous props or state?](#how-to-get-the-previous-props-or-state) + * [Why am I seeing stale props or state inside my function?](#why-am-i-seeing-stale-props-or-state-inside-my-function) * [How do I implement getDerivedStateFromProps?](#how-do-i-implement-getderivedstatefromprops) * [Is there something like forceUpdate?](#is-there-something-like-forceupdate) * [Can I make a ref to a function component?](#can-i-make-a-ref-to-a-function-component) * [What does const [thing, setThing] = useState() mean?](#what-does-const-thing-setthing--usestate-mean) * **[Performance Optimizations](#performance-optimizations)** * [Can I skip an effect on updates?](#can-i-skip-an-effect-on-updates) + * [Is it safe to omit functions from the list of dependencies?](#is-it-safe-to-omit-functions-from-the-list-of-dependencies) + * [What can I do if my effect dependencies change too often?](#what-can-i-do-if-my-effect-dependencies-change-too-often) * [How do I implement shouldComponentUpdate?](#how-do-i-implement-shouldcomponentupdate) * [How to memoize calculations?](#how-to-memoize-calculations) * [How to create expensive objects lazily?](#how-to-create-expensive-objects-lazily) @@ -362,6 +365,44 @@ It's possible that in the future React will provide a `usePrevious` Hook out of See also [the recommended pattern for derived state](#how-do-i-implement-getderivedstatefromprops). +### Why am I seeing stale props or state inside my function? {#why-am-i-seeing-stale-props-or-state-inside-my-function} + +Any function inside a component, including event handlers and effects, β€œsees” the props and state from the render it was defined in. For example, consider code like this: + +```js +function Example() { + const [count, setCount] = useState(0); + + function handleAlertClick() { + setTimeout(() => { + alert('You clicked on: ' + count); + }, 3000); + } + + return ( +
+

You clicked {count} times

+ + +
+ ); +} +``` + +If you first click "Show alert" and then increment the counter, the alert will show the `count` variable **at the time you clicked the buton**. This helps prevent bugs caused by the code assuming props and state don't change. + +If you intentionally want to read the *latest* state from some asynchronous callback, you could keep it in [a ref](/docs/hooks-faq.html#is-there-something-like-instance-variables), mutate it, and read from it. + +Finally, another possible reason you're seeing stale props or state is if you use the "dependency array" optimization but didn't correctly specify all the dependencies. For example, if an effect specifies `[]` as the second argument but reads `someProp` inside, it will keep "seeing" the initial value of `someProp`. The solution is to either remove the dependency array, or to fix it. Here's [how you can deal with functions](#is-it-safe-to-omit-functions-from-the-list-of-dependencies), and here's [other common strategies](#what-can-i-do-if-my-effect-dependencies-change-too-often) to run effects less often without incorrectly skipping dependencies. + +>Note +> +>We provide an [`exhaustive-deps`](https://github.com/facebook/react/issues/14920) ESLint rule as a part of the [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation) package. It warns when dependencies are specified incorrectly and suggests a fix. + ### How do I implement `getDerivedStateFromProps`? {#how-do-i-implement-getderivedstatefromprops} While you probably [don't need it](/blog/2018/06/07/you-probably-dont-need-derived-state.html), in rare cases that you do (such as implementing a `` component), you can update the state right during rendering. React will re-run the component with updated state immediately after exiting the first render so it wouldn't be expensive. @@ -416,6 +457,193 @@ If you're not familiar with this syntax, check out the [explanation](/docs/hooks Yes. See [conditionally firing an effect](/docs/hooks-reference.html#conditionally-firing-an-effect). Note that forgetting to handle updates often [introduces bugs](/docs/hooks-effect.html#explanation-why-effects-run-on-each-update), which is why this isn't the default behavior. +### Is it safe to omit functions from the list of dependencies? {#is-it-safe-to-omit-functions-from-the-list-of-dependencies} + +Generally speaking, no. + +```js +useEffect(() => { + doSomething(); +}, []); // πŸ”΄ This is not safe +``` + +Instead, usually you'll want to move the function definition *inside* an effect. That makes it easy to see what values from the component scope it depends on: + +```js{3,7} +useEffect(() => { + function doSomething() { + console.log(someProp); + } + + doSomething(); +}, [someProp]); // βœ… OK (our effect only uses `someProp`) +``` + +If after that we still don't use any values from the component scope, it's safe to specify `[]`: + +```js{6} +useEffect(() => { + function doSomething() { + } + + doSomething(); +}, []); // βœ… OK in this example because we don't use *any* values from component scope +``` + +>Note +> +>We provide the [`exhaustive-deps`](https://github.com/facebook/react/issues/14920) ESLint rule as a part of the [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation) package. It help you find components that don't handle updates consistently. + +Let's see why this matters. + +If you specify a [list of dependencies](/docs/hooks-reference.html#conditionally-firing-an-effect) as the last argument to `useEffect`, `useMemo`, `useCallback`, or `useImperativeHandle`, it must include all values used inside that participate in the React data flow. That includes props, state, or anything derived from them. + +It is **only** safe to omit a function from the dependency list if nothing in it (or the functions called by it) references props, state, or values derived from them. This example has a bug: + +```js{5,12} +function ProductPage({ productId }) { + const [product, setProduct] = useState(null); + + async function fetchProduct() { + const response = await fetch('http://myapi/product' + productId); // Uses productId prop + const json = await response.json(); + setProduct(json); + } + + useEffect(() => { + fetchProduct(); + }, []); // πŸ”΄ Invalid because `fetchProduct` uses `productId` + // ... +} +``` + +**The recommended fix is to move that function _inside_ of your effect**. That makes it easy to see which props or state your effect uses, and to ensure they're all declared: + +```js{5-10,13} +function ProductPage({ productId }) { + const [product, setProduct] = useState(null); + + useEffect(() => { + // By moving this function inside the effect, we can clearly see the values it uses. + async function fetchProduct() { + const response = await fetch('http://myapi/product' + productId); + const json = await response.json(); + setProduct(json); + } + + fetchProduct(); + }, [productId]); // βœ… Valid because our effect only uses productId + // ... +} +``` + +This also allows you to handle out-of-order responses with a local variable inside the effect: + +```js{2,6,8} + useEffect(() => { + let ignore = false; + async function fetchProduct() { + const response = await fetch('http://myapi/product/' + productId); + const json = await response.json(); + if (!ignore) setProduct(json); + } + return () => { ignore = true }; + }, [productId]); +``` + +(Check out [this article](https://www.robinwieruch.de/react-hooks-fetch-data/) to learn more about data fetching with Hooks.) + +We moved the function inside the effect so it doesn't need to be in its dependency list. + +**If for some reason you _can't_ move a function inside an effect, there'a few more options:** + +* **You can try moving that function outside of your component**. In that case, the function guaranteed to not reference any props or state, and also doesn't need to be in the list of dependencies. +* As a last resort, you can **add a function to effect dependencies but _wrap its definition_** into the [`useCallback`](/docs/hooks-reference.html#usecallback) Hook. This ensures it doesn't change on every render unless *its own* dependencies also change: + +```js{2-5} +function ProductPage({ productId }) { + // βœ… Wrap with useCallback to avoid change on every render + const fetchProduct = useCallback(() => { + // ... Does something with productId ... + }, [productId]); // βœ… All useCallback dependencies are specified + + return ; +} + +function ProductDetails({ fetchProduct }) + useEffect(() => { + fetchProduct(); + }, [fetchProduct]); // βœ… All useEffect dependencies are specified + // ... +} +``` + +Note that in the above example we **need** to keep the function in the dependencies list. This ensures that a change in the `productId` prop of `ProductPage` automatically triggers a refetch in the `ProductDetails` component. + +### What can I do if my effect dependencies change too often? + +Sometimes, your effect may be using reading state that changes too often. You might be tempted to omit that state from a list of dependencies, but that usually leads to bugs: + +```js{6,9} +function Counter() { + const [count, setCount] = useState(0); + + useEffect(() => { + const id = setInterval(() => { + setCount(count + 1); // This effect depends on the `count` state + }, 1000); + return () => clearInterval(id); + }, []); // πŸ”΄ Bug: `count` is not specified as a dependency + + return

{count}

; +} +``` + +Specifying `[count]` as a list of dependencies would fix the bug, but would cause the interval to be reset on every change. That may not be desirable. To fix this, we can use the [functional update form of `setState`](/docs/hooks-reference.html#functional-updates). It lets us specify *how* the state needs to change without referencing the *current* state: + +```js{6,9} +function Counter() { + const [count, setCount] = useState(0); + + useEffect(() => { + const id = setInterval(() => { + setCount(c => c + 1); // βœ… This doesn't depend on `count` variable outside + }, 1000); + return () => clearInterval(id); + }, []); // βœ… Our effect doesn't use any variables in the component scope + + return

{count}

; +} +``` + +(The identify of the `setCount` function is guaranteed to be stable so it's safe to omit.) + +In more complex cases (such as if one state depends on another state), try moving the state update logic outside the effect with the [`useReducer` Hook](/docs/hooks-reference.html#usereducer). [This article](https://adamrackis.dev/state-and-use-reducer/) offers an example of how you can do this. **The identity of the `dispatch` function from `useReducer` is also guaranteed to be stable** β€” even if the reducer is inside the component and reads its props. + +As a last resort, if you want to something like `this` in a class, you can [use a ref](/docs/hooks-faq.html#is-there-something-like-instance-variables) to hold a mutable variable. Then you can write and read to it. For example: + +```js{2-6,10-11,16} +function Example(props) { + // Keep latest props in a ref. + let latestProps = useRef(props); + useEffect(() => { + latestProps.current = props; + }); + + useEffect(() => { + function tick() { + // Read latest props at any time + console.log(latestProps.current); + } + + const id = setInterval(tick, 1000); + return () => clearInterval(id); + }, []); // This effect never re-runs +} +``` + +Only do this if you couldn't find a better alternative, as relying on mutation makes components less predictable. If there's a specific pattern that doesn't translate well, [file an issue](https://github.com/facebook/react/issues/new) with a runnable example code and we can try to help. + ### How do I implement `shouldComponentUpdate`? {#how-do-i-implement-shouldcomponentupdate} You can wrap a function component with `React.memo` to shallowly compare its props: @@ -430,7 +658,6 @@ It's not a Hook because it doesn't compose like Hooks do. `React.memo` is equiva `React.memo` doesn't compare state because there is no single state object to compare. But you can make children pure too, or even [optimize individual children with `useMemo`](/docs/hooks-faq.html#how-to-memoize-calculations). - ### How to memoize calculations? {#how-to-memoize-calculations} The [`useMemo`](/docs/hooks-reference.html#usememo) Hook lets you cache calculations between multiple renders by "remembering" the previous computation: diff --git a/content/docs/hooks-reference.md b/content/docs/hooks-reference.md index deeef54cf20..20d18b79188 100644 --- a/content/docs/hooks-reference.md +++ b/content/docs/hooks-reference.md @@ -133,7 +133,7 @@ Although `useEffect` is deferred until after the browser has painted, it's guara #### Conditionally firing an effect {#conditionally-firing-an-effect} -The default behavior for effects is to fire the effect after every completed render. That way an effect is always recreated if one of its inputs changes. +The default behavior for effects is to fire the effect after every completed render. That way an effect is always recreated if one of its dependencies changes. However, this may be overkill in some cases, like the subscription example from the previous section. We don't need to create a new subscription on every update, only if the `source` props has changed. @@ -153,11 +153,18 @@ useEffect( Now the subscription will only be recreated when `props.source` changes. -Passing in an empty array `[]` of inputs tells React that your effect doesn't depend on any values from the component, so that effect would run only on mount and clean up on unmount; it won't run on updates. - -> Note +>Note +> +>If you use this optimization, make sure the array includes **any values from the component scope (such as props and state) that change over time and that are used by the effect**. Otherwise, your code will reference stale values from previous renders. Learn more about how to [how to deal with functions](/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) and what to do when the [array values change too often](/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often). > -> The array of inputs is not passed as arguments to the effect function. Conceptually, though, that's what they represent: every value referenced inside the effect function should also appear in the inputs array. In the future, a sufficiently advanced compiler could create this array automatically. +>If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array (`[]`) as a second argument. This tells React that your effect doesn't depend on *any* values from props or state, so it never needs to re-run. This isn't handled as a special case -- it follows directly from how the dependencies array always works. +> +>If you pass an empty array (`[]`), the props and state as inside the effect will always have their initial values. While passing `[]` as the second argument is closer to the familiar `componentDidMount` and `componentWillUnmount` mental model, there are usually [better](/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) [solutions](/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often) to avoid re-running effects too often. Also, don't forget that React defers running `useEffect` until after the browser has painted, so doing extra work is less of a problem. +> +> +>We provide an [`exhaustive-deps`](https://github.com/facebook/react/issues/14920) ESLint rule as a part of the [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation) package. It warns when dependencies are specified incorrectly and suggests a fix. + +The array of dependencies is not passed as arguments to the effect function. Conceptually, though, that's what they represent: every value referenced inside the effect function should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically. ### `useContext` {#usecontext} @@ -283,13 +290,13 @@ const memoizedCallback = useCallback( Returns a [memoized](https://en.wikipedia.org/wiki/Memoization) callback. -Pass an inline callback and an array of inputs. `useCallback` will return a memoized version of the callback that only changes if one of the inputs has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. `shouldComponentUpdate`). +Pass an inline callback and an array of dependencies. `useCallback` will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. `shouldComponentUpdate`). -`useCallback(fn, inputs)` is equivalent to `useMemo(() => fn, inputs)`. +`useCallback(fn, deps)` is equivalent to `useMemo(() => fn, deps)`. > Note > -> The array of inputs is not passed as arguments to the callback. Conceptually, though, that's what they represent: every value referenced inside the callback should also appear in the inputs array. In the future, a sufficiently advanced compiler could create this array automatically. +> The array of dependencies is not passed as arguments to the callback. Conceptually, though, that's what they represent: every value referenced inside the callback should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically. ### `useMemo` {#usememo} @@ -299,7 +306,7 @@ const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); Returns a [memoized](https://en.wikipedia.org/wiki/Memoization) value. -Pass a "create" function and an array of inputs. `useMemo` will only recompute the memoized value when one of the inputs has changed. This optimization helps to avoid expensive calculations on every render. +Pass a "create" function and an array of dependencies. `useMemo` will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render. Remember that the function passed to `useMemo` runs during rendering. Don't do anything there that you wouldn't normally do while rendering. For example, side effects belong in `useEffect`, not `useMemo`. @@ -309,7 +316,7 @@ If no array is provided, a new value will be computed whenever a new function in > Note > -> The array of inputs is not passed as arguments to the function. Conceptually, though, that's what they represent: every value referenced inside the function should also appear in the inputs array. In the future, a sufficiently advanced compiler could create this array automatically. +> The array of dependencies is not passed as arguments to the function. Conceptually, though, that's what they represent: every value referenced inside the function should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically. ### `useRef` {#useref} @@ -342,7 +349,7 @@ Note that `useRef()` is useful for more than the `ref` attribute. It's [handy fo ### `useImperativeHandle` {#useimperativehandle} ```js -useImperativeHandle(ref, createHandle, [inputs]) +useImperativeHandle(ref, createHandle, [deps]) ``` `useImperativeHandle` customizes the instance value that is exposed to parent components when using `ref`. As always, imperative code using refs should be avoided in most cases. `useImperativeHandle` should be used with `forwardRef`: diff --git a/content/docs/hooks-rules.md b/content/docs/hooks-rules.md index 698d1c7417e..8c332578c21 100644 --- a/content/docs/hooks-rules.md +++ b/content/docs/hooks-rules.md @@ -40,7 +40,8 @@ npm install eslint-plugin-react-hooks ], "rules": { // ... - "react-hooks/rules-of-hooks": "error" + "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks + "react-hooks/exhaustive-deps": "warning" // Checks effect dependencies } } ``` From 1c69623326653bcdb843051822ea6b316ffebab8 Mon Sep 17 00:00:00 2001 From: Rick Beerendonk Date: Wed, 13 Mar 2019 13:24:06 +0000 Subject: [PATCH 02/10] Update content/docs/hooks-faq.md Co-Authored-By: gaearon --- content/docs/hooks-faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/hooks-faq.md b/content/docs/hooks-faq.md index 468bcf49c6d..ba6f122a3f9 100644 --- a/content/docs/hooks-faq.md +++ b/content/docs/hooks-faq.md @@ -393,7 +393,7 @@ function Example() { } ``` -If you first click "Show alert" and then increment the counter, the alert will show the `count` variable **at the time you clicked the buton**. This helps prevent bugs caused by the code assuming props and state don't change. +If you first click "Show alert" and then increment the counter, the alert will show the `count` variable **at the time you clicked the "Show alert" button**. This prevents bugs caused by the code assuming props and state don't change. If you intentionally want to read the *latest* state from some asynchronous callback, you could keep it in [a ref](/docs/hooks-faq.html#is-there-something-like-instance-variables), mutate it, and read from it. From c8a769db539ab4f27574fed2016327d9ae55d0fd Mon Sep 17 00:00:00 2001 From: Rick Beerendonk Date: Wed, 13 Mar 2019 13:24:16 +0000 Subject: [PATCH 03/10] Update content/docs/hooks-faq.md Co-Authored-By: gaearon --- content/docs/hooks-faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/hooks-faq.md b/content/docs/hooks-faq.md index ba6f122a3f9..5398fb68827 100644 --- a/content/docs/hooks-faq.md +++ b/content/docs/hooks-faq.md @@ -555,7 +555,7 @@ This also allows you to handle out-of-order responses with a local variable insi We moved the function inside the effect so it doesn't need to be in its dependency list. -**If for some reason you _can't_ move a function inside an effect, there'a few more options:** +**If for some reason you _can't_ move a function inside an effect, there are a few more options:** * **You can try moving that function outside of your component**. In that case, the function guaranteed to not reference any props or state, and also doesn't need to be in the list of dependencies. * As a last resort, you can **add a function to effect dependencies but _wrap its definition_** into the [`useCallback`](/docs/hooks-reference.html#usecallback) Hook. This ensures it doesn't change on every render unless *its own* dependencies also change: From b5962acaea48d749b522b90db4957c324515418c Mon Sep 17 00:00:00 2001 From: Rick Beerendonk Date: Wed, 13 Mar 2019 13:24:37 +0000 Subject: [PATCH 04/10] Update content/docs/hooks-faq.md Co-Authored-By: gaearon --- content/docs/hooks-faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/hooks-faq.md b/content/docs/hooks-faq.md index 5398fb68827..b3a8423cdfd 100644 --- a/content/docs/hooks-faq.md +++ b/content/docs/hooks-faq.md @@ -557,7 +557,7 @@ We moved the function inside the effect so it doesn't need to be in its dependen **If for some reason you _can't_ move a function inside an effect, there are a few more options:** -* **You can try moving that function outside of your component**. In that case, the function guaranteed to not reference any props or state, and also doesn't need to be in the list of dependencies. +* **You can try moving that function outside of your component**. In that case, the function is guaranteed to not reference any props or state, and also doesn't need to be in the list of dependencies. * As a last resort, you can **add a function to effect dependencies but _wrap its definition_** into the [`useCallback`](/docs/hooks-reference.html#usecallback) Hook. This ensures it doesn't change on every render unless *its own* dependencies also change: ```js{2-5} From e01b5025b30c0dcf471dff395c013838130f7fc4 Mon Sep 17 00:00:00 2001 From: Rick Beerendonk Date: Wed, 13 Mar 2019 13:24:49 +0000 Subject: [PATCH 05/10] Update content/docs/hooks-faq.md Co-Authored-By: gaearon --- content/docs/hooks-faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/hooks-faq.md b/content/docs/hooks-faq.md index b3a8423cdfd..62d2d4be875 100644 --- a/content/docs/hooks-faq.md +++ b/content/docs/hooks-faq.md @@ -616,7 +616,7 @@ function Counter() { } ``` -(The identify of the `setCount` function is guaranteed to be stable so it's safe to omit.) +(The identity of the `setCount` function is guaranteed to be stable so it's safe to omit.) In more complex cases (such as if one state depends on another state), try moving the state update logic outside the effect with the [`useReducer` Hook](/docs/hooks-reference.html#usereducer). [This article](https://adamrackis.dev/state-and-use-reducer/) offers an example of how you can do this. **The identity of the `dispatch` function from `useReducer` is also guaranteed to be stable** β€” even if the reducer is inside the component and reads its props. From f15f99cbcd05314bc11bdfe6e8bd5b87ea86f4fe Mon Sep 17 00:00:00 2001 From: Sophie Alpert Date: Wed, 13 Mar 2019 13:25:04 +0000 Subject: [PATCH 06/10] Update content/docs/hooks-effect.md Co-Authored-By: gaearon --- content/docs/hooks-effect.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/hooks-effect.md b/content/docs/hooks-effect.md index 9790400cc42..21325a11aba 100644 --- a/content/docs/hooks-effect.md +++ b/content/docs/hooks-effect.md @@ -471,7 +471,7 @@ In the future, the second argument might get added automatically by a build-time >Note > ->If you use this optimization, make sure the array includes **any values from the component scope (such as props and state) that change over time and that are used by the effect**. Otherwise, your code will reference stale values from previous renders. Learn more about how to [how to deal with functions](/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) and what to do when the [array values change too often](/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often). +>If you use this optimization, make sure the array includes **any values from the component scope (such as props and state) that change over time and that are used by the effect**. Otherwise, your code will reference stale values from previous renders. Learn more about [how to deal with functions](/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) and [what to do when the array changes too often](/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often). > >If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array (`[]`) as a second argument. This tells React that your effect doesn't depend on *any* values from props or state, so it never needs to re-run. This isn't handled as a special case -- it follows directly from how the inputs array always works. > From 6fc6757e2be45504363970a102e4e3ac3e60e1b3 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 13 Mar 2019 13:52:45 +0000 Subject: [PATCH 07/10] tweaks --- content/docs/hooks-effect.md | 4 +-- content/docs/hooks-faq.md | 53 ++++++++++++++++++++++----------- content/docs/hooks-reference.md | 16 ++++++++-- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/content/docs/hooks-effect.md b/content/docs/hooks-effect.md index 21325a11aba..fd7d895920d 100644 --- a/content/docs/hooks-effect.md +++ b/content/docs/hooks-effect.md @@ -471,13 +471,13 @@ In the future, the second argument might get added automatically by a build-time >Note > ->If you use this optimization, make sure the array includes **any values from the component scope (such as props and state) that change over time and that are used by the effect**. Otherwise, your code will reference stale values from previous renders. Learn more about [how to deal with functions](/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) and [what to do when the array changes too often](/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often). +>If you use this optimization, make sure the array includes **all values from the component scope (such as props and state) that change over time and that are used by the effect**. Otherwise, your code will reference stale values from previous renders. Learn more about [how to deal with functions](/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) and [what to do when the array changes too often](/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often). > >If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array (`[]`) as a second argument. This tells React that your effect doesn't depend on *any* values from props or state, so it never needs to re-run. This isn't handled as a special case -- it follows directly from how the inputs array always works. > >If you pass an empty array (`[]`), the props and state inside the effect will always have their initial values. While passing `[]` as the second argument is closer to the familiar `componentDidMount` and `componentWillUnmount` mental model, there are usually [better](/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) [solutions](/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often) to avoid re-running effects too often. Also, don't forget that React defers running `useEffect` until after the browser has painted, so doing extra work is less of a problem. > ->We provide an [`exhaustive-deps`](https://github.com/facebook/react/issues/14920) ESLint rule as a part of the [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation) package. It warns when dependencies are specified incorrectly and suggests a fix. +>We recommend using the [`exhaustive-deps`](https://github.com/facebook/react/issues/14920) rule as part of our [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation) package. It warns when dependencies are specified incorrectly and suggests a fix. ## Next Steps {#next-steps} diff --git a/content/docs/hooks-faq.md b/content/docs/hooks-faq.md index 62d2d4be875..8e5f5bd5a3d 100644 --- a/content/docs/hooks-faq.md +++ b/content/docs/hooks-faq.md @@ -32,6 +32,7 @@ This page answers some of the frequently asked questions about [Hooks](/docs/hoo * [What exactly do the lint rules enforce?](#what-exactly-do-the-lint-rules-enforce) * **[From Classes to Hooks](#from-classes-to-hooks)** * [How do lifecycle methods correspond to Hooks?](#how-do-lifecycle-methods-correspond-to-hooks) + * [How can I do data fetching with Hooks?](#how-can-i-do-data-fetching-with-hooks) * [Is there something like instance variables?](#is-there-something-like-instance-variables) * [Should I use one or many state variables?](#should-i-use-one-or-many-state-variables) * [Can I run an effect only on updates?](#can-i-run-an-effect-only-on-updates) @@ -207,6 +208,10 @@ There are a few more heuristics, and they might change over time as we fine-tune * `componentDidCatch` and `getDerivedStateFromError`: There are no Hook equivalents for these methods yet, but they will be added soon. +### How can I do data fetching with Hooks? + +Check out [this article](https://www.robinwieruch.de/react-hooks-fetch-data/) to learn more about data fetching with Hooks. + ### Is there something like instance variables? {#is-there-something-like-instance-variables} Yes! The [`useRef()`](/docs/hooks-reference.html#useref) Hook isn't just for DOM refs. The "ref" object is a generic container whose `current` property is mutable and can hold any value, similar to an instance property on a class. @@ -461,42 +466,53 @@ Yes. See [conditionally firing an effect](/docs/hooks-reference.html#conditional Generally speaking, no. -```js -useEffect(() => { - doSomething(); -}, []); // πŸ”΄ This is not safe -``` - -Instead, usually you'll want to move the function definition *inside* an effect. That makes it easy to see what values from the component scope it depends on: - -```js{3,7} -useEffect(() => { +```js{3,8} +function Example() { function doSomething() { console.log(someProp); } - doSomething(); -}, [someProp]); // βœ… OK (our effect only uses `someProp`) + useEffect(() => { + doSomething(); + }, []); // πŸ”΄ This is not safe (it calls `doSomething` which uses `someProp`) +} +``` + +It's difficult to remember which props or state are used by functions outside of the effect. This is why **usually you'll want to declare functions needed by an effect *inside* of it.** Then it's easy to see what values from the component scope that effect depends on: + +```js{4,8} +function Example() { + useEffect(() => { + function doSomething() { + console.log(someProp); + } + + doSomething(); + }, [someProp]); // βœ… OK (our effect only uses `someProp`) +} ``` If after that we still don't use any values from the component scope, it's safe to specify `[]`: -```js{6} +```js{7} useEffect(() => { function doSomething() { + console.log('hello'); } doSomething(); }, []); // βœ… OK in this example because we don't use *any* values from component scope ``` +Depending on your use case, there are a few more options described below. + >Note > >We provide the [`exhaustive-deps`](https://github.com/facebook/react/issues/14920) ESLint rule as a part of the [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation) package. It help you find components that don't handle updates consistently. Let's see why this matters. -If you specify a [list of dependencies](/docs/hooks-reference.html#conditionally-firing-an-effect) as the last argument to `useEffect`, `useMemo`, `useCallback`, or `useImperativeHandle`, it must include all values used inside that participate in the React data flow. That includes props, state, or anything derived from them. +If you specify a [list of dependencies](/docs/hooks-reference.html#conditionally-firing-an-effect) as the last argument to `useEffect`, `useMemo`, `useCallback`, or `useImperativeHandle`, it must include all values used inside that participate in the React data flow. That includes props, state, and anything derived from them. It is **only** safe to omit a function from the dependency list if nothing in it (or the functions called by it) references props, state, or values derived from them. This example has a bug: @@ -551,13 +567,16 @@ This also allows you to handle out-of-order responses with a local variable insi }, [productId]); ``` -(Check out [this article](https://www.robinwieruch.de/react-hooks-fetch-data/) to learn more about data fetching with Hooks.) - We moved the function inside the effect so it doesn't need to be in its dependency list. +>Tip +> +>Check out [this article](https://www.robinwieruch.de/react-hooks-fetch-data/) to learn more about data fetching with Hooks. + **If for some reason you _can't_ move a function inside an effect, there are a few more options:** * **You can try moving that function outside of your component**. In that case, the function is guaranteed to not reference any props or state, and also doesn't need to be in the list of dependencies. +* If the function you're calling is a pure computation and is safe to call while rendering, you may **call it outside of the effect instead,** and make the effect depend on the returned value. * As a last resort, you can **add a function to effect dependencies but _wrap its definition_** into the [`useCallback`](/docs/hooks-reference.html#usecallback) Hook. This ensures it doesn't change on every render unless *its own* dependencies also change: ```js{2-5} @@ -618,7 +637,7 @@ function Counter() { (The identity of the `setCount` function is guaranteed to be stable so it's safe to omit.) -In more complex cases (such as if one state depends on another state), try moving the state update logic outside the effect with the [`useReducer` Hook](/docs/hooks-reference.html#usereducer). [This article](https://adamrackis.dev/state-and-use-reducer/) offers an example of how you can do this. **The identity of the `dispatch` function from `useReducer` is also guaranteed to be stable** β€” even if the reducer is inside the component and reads its props. +In more complex cases (such as if one state depends on another state), try moving the state update logic outside the effect with the [`useReducer` Hook](/docs/hooks-reference.html#usereducer). [This article](https://adamrackis.dev/state-and-use-reducer/) offers an example of how you can do this. **The identity of the `dispatch` function from `useReducer` is always stable** β€” even if the reducer function is declared inside the component and reads its props. As a last resort, if you want to something like `this` in a class, you can [use a ref](/docs/hooks-faq.html#is-there-something-like-instance-variables) to hold a mutable variable. Then you can write and read to it. For example: diff --git a/content/docs/hooks-reference.md b/content/docs/hooks-reference.md index 20d18b79188..4466bc83b62 100644 --- a/content/docs/hooks-reference.md +++ b/content/docs/hooks-reference.md @@ -45,6 +45,10 @@ setState(newState); During subsequent re-renders, the first value returned by `useState` will always be the most recent state after applying updates. +>Note +> +>React guarantees that `setState` function identity is stable and won't change on re-renders. This is why it's safe to omit from the `useEffect` or `useCallback` dependency list. + #### Functional updates {#functional-updates} If the new state is computed using the previous state, you can pass a function to `setState`. The function will receive the previous value, and return an updated value. Here's an example of a counter component that uses both forms of `setState`: @@ -155,14 +159,14 @@ Now the subscription will only be recreated when `props.source` changes. >Note > ->If you use this optimization, make sure the array includes **any values from the component scope (such as props and state) that change over time and that are used by the effect**. Otherwise, your code will reference stale values from previous renders. Learn more about how to [how to deal with functions](/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) and what to do when the [array values change too often](/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often). +>If you use this optimization, make sure the array includes **all values from the component scope (such as props and state) that change over time and that are used by the effect**. Otherwise, your code will reference stale values from previous renders. Learn more about [how to deal with functions](/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) and what to do when the [array values change too often](/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often). > >If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array (`[]`) as a second argument. This tells React that your effect doesn't depend on *any* values from props or state, so it never needs to re-run. This isn't handled as a special case -- it follows directly from how the dependencies array always works. > >If you pass an empty array (`[]`), the props and state as inside the effect will always have their initial values. While passing `[]` as the second argument is closer to the familiar `componentDidMount` and `componentWillUnmount` mental model, there are usually [better](/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) [solutions](/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often) to avoid re-running effects too often. Also, don't forget that React defers running `useEffect` until after the browser has painted, so doing extra work is less of a problem. > > ->We provide an [`exhaustive-deps`](https://github.com/facebook/react/issues/14920) ESLint rule as a part of the [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation) package. It warns when dependencies are specified incorrectly and suggests a fix. +>We recommend using the [`exhaustive-deps`](https://github.com/facebook/react/issues/14920) rule as part of our [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation) package. It warns when dependencies are specified incorrectly and suggests a fix. The array of dependencies is not passed as arguments to the effect function. Conceptually, though, that's what they represent: every value referenced inside the effect function should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically. @@ -218,6 +222,10 @@ function Counter({initialState}) { } ``` +>Note +> +>React guarantees that `dispatch` function identity is stable and won't change on re-renders. This is why it's safe to omit from the `useEffect` or `useCallback` dependency list. + #### Specifying the initial state {#specifying-the-initial-state} There’s two different ways to initialize `useReducer` state. You may choose either one depending on the use case. The simplest way to pass the initial state as a second argument: @@ -297,6 +305,8 @@ Pass an inline callback and an array of dependencies. `useCallback` will return > Note > > The array of dependencies is not passed as arguments to the callback. Conceptually, though, that's what they represent: every value referenced inside the callback should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically. +> +> We recommend using the [`exhaustive-deps`](https://github.com/facebook/react/issues/14920) rule as part of our [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation) package. It warns when dependencies are specified incorrectly and suggests a fix. ### `useMemo` {#usememo} @@ -317,6 +327,8 @@ If no array is provided, a new value will be computed whenever a new function in > Note > > The array of dependencies is not passed as arguments to the function. Conceptually, though, that's what they represent: every value referenced inside the function should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically. +> +> We recommend using the [`exhaustive-deps`](https://github.com/facebook/react/issues/14920) rule as part of our [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks#installation) package. It warns when dependencies are specified incorrectly and suggests a fix. ### `useRef` {#useref} From 3160c1acfe79fd07565d5752ca516d7a57088af1 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 13 Mar 2019 13:55:02 +0000 Subject: [PATCH 08/10] tweak --- content/docs/hooks-faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/hooks-faq.md b/content/docs/hooks-faq.md index 8e5f5bd5a3d..6145b968d73 100644 --- a/content/docs/hooks-faq.md +++ b/content/docs/hooks-faq.md @@ -372,7 +372,7 @@ See also [the recommended pattern for derived state](#how-do-i-implement-getderi ### Why am I seeing stale props or state inside my function? {#why-am-i-seeing-stale-props-or-state-inside-my-function} -Any function inside a component, including event handlers and effects, β€œsees” the props and state from the render it was defined in. For example, consider code like this: +Any function inside a component, including event handlers and effects, "sees" the props and state from the render it was created in. For example, consider code like this: ```js function Example() { From ccd99750dd19946eee2d9327ddecafa678db8ae5 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 13 Mar 2019 13:59:05 +0000 Subject: [PATCH 09/10] tweaks --- content/docs/hooks-reference.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/content/docs/hooks-reference.md b/content/docs/hooks-reference.md index 4466bc83b62..acd9c3fda8a 100644 --- a/content/docs/hooks-reference.md +++ b/content/docs/hooks-reference.md @@ -45,7 +45,7 @@ setState(newState); During subsequent re-renders, the first value returned by `useState` will always be the most recent state after applying updates. ->Note +>Tip > >React guarantees that `setState` function identity is stable and won't change on re-renders. This is why it's safe to omit from the `useEffect` or `useCallback` dependency list. @@ -222,7 +222,7 @@ function Counter({initialState}) { } ``` ->Note +>Tip > >React guarantees that `dispatch` function identity is stable and won't change on re-renders. This is why it's safe to omit from the `useEffect` or `useCallback` dependency list. @@ -358,6 +358,12 @@ function TextInputWithFocusButton() { Note that `useRef()` is useful for more than the `ref` attribute. It's [handy for keeping any mutable value around](/docs/hooks-faq.html#is-there-something-like-instance-variables) similar to how you'd use instance fields in classes. +>Tip +> +>React guarantees that ref object identity (not its `current` prop) is stable and won't change on re-renders. This is why it's safe to omit from the `useEffect` or `useCallback` dependency list. +> +>Also keep in mind that *including* `ref.current` list in the dependency list is usually a mistake because `ref.current` changes *after* rendering. If you want to re-run some code whenever a ref changes, you might want to use the function `ref={callback}` API instead. + ### `useImperativeHandle` {#useimperativehandle} ```js From 7639a38df8768e1a2191b9a94298cb8c03a3b65c Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 13 Mar 2019 13:59:27 +0000 Subject: [PATCH 10/10] tweaks --- content/docs/hooks-reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/hooks-reference.md b/content/docs/hooks-reference.md index acd9c3fda8a..5246e5f8383 100644 --- a/content/docs/hooks-reference.md +++ b/content/docs/hooks-reference.md @@ -360,7 +360,7 @@ Note that `useRef()` is useful for more than the `ref` attribute. It's [handy fo >Tip > ->React guarantees that ref object identity (not its `current` prop) is stable and won't change on re-renders. This is why it's safe to omit from the `useEffect` or `useCallback` dependency list. +>React guarantees that ref object identity (but not its `current` property) is stable and won't change on re-renders. This is why it's safe to omit from the `useEffect` or `useCallback` dependency list. > >Also keep in mind that *including* `ref.current` list in the dependency list is usually a mistake because `ref.current` changes *after* rendering. If you want to re-run some code whenever a ref changes, you might want to use the function `ref={callback}` API instead.