I am trying to understand more about the dependency requirements when using a React hook. Using useMemo as an example, I can increment the counter and the value of dog is rendered as the same value as count. This is odd to me because foo is not referenced as a dependency and from my understanding, this closure should be stale.
From the React docs in regards to useMemo:
every value referenced inside the function should also appear in the dependencies array
It doesn't seem like this^ is a requirement. If someone could help me better understand how the closure works in this particular case, I'd appreciate it.
function App() {
const [count, setCount] = useState(0);
const foo = useMemo(() => count, [count]);
const dog = useMemo(() => foo, [count]); // incrementing count updates this value
return (
<div className="App">
{dog}
<h1>Hello CodeSandbox</h1>
<h2>You clicked {count} times!</h2>
<button onClick={() => setCount(count - 1)}>Decrement</button>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Because of the way you've set things up, it happens to be that every time count changes, foo also changes. So even though you're checking the wrong variable, there's a 1:1 correspondance between times you want the memoization to break and times it actually breaks.
You shouldn't rely on this though. It is rare that things will be as simple as your example, and so you will easily make mistakes. To reliably get it to work, you should populate the dependency array with the values you actually depend on, not things which are merely synchronized with the values you depend on.
Related
I have sample code below:
function App() {
console.log("render");
const [val, setVal] = React.useState(0);
return (
<div className="App">
<h1>{val}</h1>
<button onClick={() => setVal(12)}>Update with same value</button>
</div>
);
}
When I click a button multiple times, the console log 3 times with 'render' message. For me, it should be 2 times only:
1 for first render
2 for the update from val 0 to 12 (when click button)
and since this time, it should not re-render because the same value (12) is updated to val.
But why it appears 3 times? That mean it still re-render one more time despite the same value was updated.
Anyone who know please explain this, thanks in advance.
P/S: I've figured out that it's only cause an extra re-render when the value changed then has been updated with the same
function App() {
console.log("render");
const [val, setVal] = useState(4);
return (
<div className="App">
<h1>{val}</h1>
<button onClick={() => {
setVal(val => val + 1)
}}>Update</button>
<button onClick={() => {
setVal(val => val)
}}>Update with same value</button>
</div>
);
}
When first click on 2nd button, no re-render call, but if you click the 1st button then 2nd button, 2nd button cause 1 extra re-render
This thread may help you : React: Re-Rendering on Setting State - Hooks vs. this.setState
Also, you can check the second paragraph over here which says:
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.
React can’t guess the ouput of render() won’t change: it has to render() again and compare the results with the previous render().
Then the magic happens: if there are no differences, the DOM is not updated; if there are differences, it tries to only create/destroy elements as needed, because that’s the expensive part, not running render() — well it should not be.
Changing the state normally triggers a call to render() (not necessarily DOM modifications) — but if you want control over that behavior, define shouldComponentUpdate.
Note: That goes for non-hook components. However, I didn’t know the behavior of hooks was slightly different from that of a regular component: it seems that you’re right in expecting setState not to trigger a render when the value is unchanged — see Yash Joshi's answer.
are these two method of component construction the same?
const myComp=(props)=><h1>props.text</h1>
vs
const myComp=useMemo((props)=><h1>props.text</h1>)
they both renders on props change
Short answer
Using useMemo without dependency array will do nothing.
Generally speaking, don't use useMemo to define components.
To make sure you are using useMemo correctly, set up your linter like here
Long answer:
A couple of things first:
Note that useMemo can only be used within another component or hook (see here) so I'll assume that for the rest of the answer.
I'd say your usage of useMemo is incorrect. If you want to define a component with it (however you probably shouldn't) it could be like this:
const MyComp = useMemo(
() => (props) => (
<h1>
{props.text}
</h1>
),[]
);
This defines a functional component and redefines it whenever the dependencies change. (Here the dependencies are [], so it will be defined only once). Note that using useMemo without a dependency array will result in a linting error (react-hooks/exhaustive-deps) if you set up your linting correctly as suggested here.
If you insist on leaving out the dependency array, useMemo will do nothing.
To play around a little bit with different scenarios, take a look at this codesandbox
const MyCompA = (props) => (
<h1>
{props.text}
{counter}
</h1>
);
// Equivalent to MyCompA. Gives linting error
const MyCompB = useMemo(() => (props) => (
<h1>
{props.text}
{counter}
</h1>
));
// Will only be defined once and not notice changes to counter. Gives linting error
const MyCompC = useMemo(
() => (props) => (
<h1>
{props.text}
{counter}
</h1>
),
[]
);
// Will notice changes to counter. But generally, this is not
// much better than MyCompA
const MyCompD = useMemo(
() => (props) => (
<h1>
{props.text}
{counter}
</h1>
),
[counter]
);
MyCompA and MyCompB do the same thing, because MyCompB uses useMemo without dependencies. MyCompC uses that state counter but doesn't recognize changes, because it's not in the dependency array. MyCompC recognizes that changes, but overall is not much better than MyCompA.
Note the whole example is for "playing-around purpose" only so we can inspect how useMemo behaves. Generally speaking, defining components with useMemo is probably not the way to go. For that example, better use a regular component that takes both text and counter as props.
useMemo doesn't affect function calls and the way a component reacts to prop changes in this case. This could be achieved with useCallback but it's not recommended; this is what memo component is for.
Considering that both are defined inside component function, the one without useMemo will be a new component on each render and so will be remounted, while the one with useMemo will behave as expected.
Since the component doesn't rely on the scope in which it's defined (it's not a good practice), it shouldn't be defined inside another component and therefore won't benefit from the use of useMemo.
I have sample code below:
function App() {
console.log("render");
const [val, setVal] = React.useState(0);
return (
<div className="App">
<h1>{val}</h1>
<button onClick={() => setVal(12)}>Update with same value</button>
</div>
);
}
When I click a button multiple times, the console log 3 times with 'render' message. For me, it should be 2 times only:
1 for first render
2 for the update from val 0 to 12 (when click button)
and since this time, it should not re-render because the same value (12) is updated to val.
But why it appears 3 times? That mean it still re-render one more time despite the same value was updated.
Anyone who know please explain this, thanks in advance.
P/S: I've figured out that it's only cause an extra re-render when the value changed then has been updated with the same
function App() {
console.log("render");
const [val, setVal] = useState(4);
return (
<div className="App">
<h1>{val}</h1>
<button onClick={() => {
setVal(val => val + 1)
}}>Update</button>
<button onClick={() => {
setVal(val => val)
}}>Update with same value</button>
</div>
);
}
When first click on 2nd button, no re-render call, but if you click the 1st button then 2nd button, 2nd button cause 1 extra re-render
This thread may help you : React: Re-Rendering on Setting State - Hooks vs. this.setState
Also, you can check the second paragraph over here which says:
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.
React can’t guess the ouput of render() won’t change: it has to render() again and compare the results with the previous render().
Then the magic happens: if there are no differences, the DOM is not updated; if there are differences, it tries to only create/destroy elements as needed, because that’s the expensive part, not running render() — well it should not be.
Changing the state normally triggers a call to render() (not necessarily DOM modifications) — but if you want control over that behavior, define shouldComponentUpdate.
Note: That goes for non-hook components. However, I didn’t know the behavior of hooks was slightly different from that of a regular component: it seems that you’re right in expecting setState not to trigger a render when the value is unchanged — see Yash Joshi's answer.
In most react examples I have seen, people seem to avoid placing code directly into a functional component's body but instead are wrapping it into useEffect(() => {...}).
E.g. in the official docs:
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Why is this better than simply writing:
function Example() {
const [count, setCount] = useState(0);
document.title = `You clicked ${count} times`;
return (...);
}
In both cases the document title will be set on each render. (I believe with useEffect() the code is executed after render, but that doesn't seem to be relevant in this example, right)
I understand the value of useEffect() if:
States are passed as a second parameter so the code won't be executed on every render but with respect to the specified state changes.
We take advantage of the cleanup mechanics.
But without that? Is there still a reason to wrap your code into useEffect()?
Answering my own question. As far as I can tell now, there is no reason or justification to have a useEffect() without the second parameter in your code.
The reason why tutorials like the one on reactjs.org use it that way seems to be for educational reasons only: They probably want you to focus on something else and not confuse you with that second parameter. However this can lead to the (false) impression that the second parameter is not always necessary.
Once again, this was a lesson for me that tutorial code is not always ready to be used in real projects. Also in a real project, lint rules like react-hooks/exhaustive-deps (by default included in create-react-app) would immediately tell you that useEffect() without a second parameter is not ok.
useEffect(() => {
document.title = `You clicked ${count} times`;
},[count]);
The code should be like that because now useEffect will only call when count state will change
See the official doc here about useEffect hook. The useEffect is similar to componentDidMount, componentDidUpdate, and componentWillUnmount combined. Does it give you any hint?
I'm reading React Hook documentation about functional updates and see this quote:
The ”+” and ”-” buttons use the functional form, because the updated
value is based on the previous value
But I can't see for what purposes functional updates are required and what's the difference between them and directly using old state in computing new state.
Why functional update form is needed at all for updater functions of React useState Hook? What are examples where we can clearly see a difference (so using direct update will lead to bugs)?
For example, if I change this example from documentation
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</>
);
}
to updating count directly:
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</>
);
}
I can't see any difference in behaviour and can't imagine case when count will not be updated (or will not be the most recent). Because whenever count is changing, new closure for onClick will be called, capturing the most recent count.
State update is asynchronous in React. So it is possible that there would be old value in count when you're updating it next time. Compare, for example, result of these two code samples:
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => {
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1)}
}>+</button>
</>
);
}
and
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => {
setCount(count + 1);
setCount(count + 1)}
}>+</button>
</>
);
}
I stumbled into a need for this recently. For example let's say you have a component that fills up an array with some amount of elements and is able to append to that array depending on some user action (like in my case, I was loading a feed 10 items at a time as the user kept scrolling down the screen. the code looked kind of like this:
function Stream() {
const [feedItems, setFeedItems] = useState([]);
const { fetching, error, data, run } = useQuery(SOME_QUERY, vars);
useEffect(() => {
if (data) {
setFeedItems([...feedItems, ...data.items]);
}
}, [data]); // <---- this breaks the rules of hooks, missing feedItems
...
<button onClick={()=>run()}>get more</button>
...
Obviously, you can't just add feedItems to the dependency list in the useEffect hook because you're invoking setFeedItems in it, so you'd get in a loop.
functional update to the rescue:
useEffect(() => {
if (data) {
setFeedItems(prevItems => [...prevItems, ...data.items]);
}
}, [data]); // <--- all good now
The “state update is asynchronous in React” answer is misleading, as are some comments below it. My thinking was also wrong until I dug into this further. You are right, this is rarely needed.
The key idea behind functional state updates is that state you depend on for the new state might be stale. How does state get stale? Let’s dispel some myths about it:
Myth: State can be changed under you during event handling.
Fact: The ECMAScript event loop only runs one thing at a time. If you are running a handler, nothing else is running alongside it.
Myth: Clicking twice fast (or any other user action happening quickly) can cause state updates from both handler calls to be batched.
Fact: React is guaranteed to not batch updates across more than one user-initiated event. This is true even in React 18, which does more batching than previous versions. You can rely on having a render in between event handlers.
From the React Working Group:
Note: React only batches updates when it’s generally safe to do. For example, React ensures that for each user-initiated event like a click or a keypress, the DOM is fully updated before the next event. This ensures, for example, that a form that disables on submit can’t be submitted twice.
So when do you get stale state?
Here are the main 3 cases I can think of:
Multiple state updates in the same handler
This is the case already mentioned where you set the same state multiple times in the same handler, and depend on the previous state. As you pointed out, this case is pretty contrived, because this clearly looks wrong:
<button
onClick={() => {
setCount(count + 1);
setCount(count + 1);
}}
>+</button>
A more plausible case is calling multiple functions that each do updates on the same state and depend on the previous state. But that’s still weird, it’d make more sense to do all the calculations then set the state once.
Async state updates in a handler
For example:
<button
onClick={() => {
doSomeApiCall().then(() => setCount(count + 1));
}}
>+</button>
This is not so obviously wrong. The state can be changed in between you calling doSomeApiCall and when it resolves. In this case, the state update really is async, but you made it that way, not React!
The functional form fixes this:
<button
onClick={() => {
doSomeApiCall().then(() => setCount((currCount) => currCount + 1));
}}
>+</button>
Updating state in useEffect
G Gallegos's answer pointed this out for useEffect in general, and letvar's answer pointed this out for useEffect with requestAnimationFrame. If you're updating state based on previous state in useEffect, putting that state in the dependency array (or not using a dependency array) is a recipe for infinite loops. Use the functional form instead.
Summary
You don’t need the functional form for state updates based on previous state, as long as you do it 1. in a user-triggered-event handler 2. once per handler per state and 3. synchronously. If you break any of those conditions, you need functional updates.
Some people might prefer to always use functional updates, so you don’t have to worry about those conditions. Others might prefer the shorter form for clarity when it’s safe to do so, which is true for many handlers. At that point it’s personal preference / code style.
Historical note
I learned React before Hooks, when only class components had state. In class components, “multiple state updates in the same handler” doesn’t look so obviously wrong:
<button
onClick={() => {
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
}}
>+</button>
Since state is an instance variable instead of a function parameter, this looks fine, unless you know that setState batches calls when in the same handler.
In fact, in React <= 17, this would work fine:
setTimeout(() => {
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
}, 1000);
Since it’s not an event handler, React re-renders after each setState call.
React 18 introduces batching for this and similar cases. This is a useful performance improvement. There is the downside that it breaks class components that rely on the above behavior.
References
React Working Group discussion
ehab’s answer, which also mentions the two cases where functional updates are needed.
I have answered a similar question like this and it was closed because this was the canonical question - that i did not know of, upon looking the answers i decided to repost my answer here since i think it adds some value.
If your update depends on a previous value found in the state, then you should use the functional form. If you don't use the functional form in this case then your code will break sometime.
Why does it break and when
React functional components are just closures, the state value that you have in the closure might be outdated - what does this mean is that the value inside the closure does not match the value that is in React state for that component, this could happen in the following cases:
1- async operations (In this example click slow add, and then click multiple times on the add button, you will later see that the state was reseted to what was inside the closure when the slow add button was clicked)
const App = () => {
const [counter, setCounter] = useState(0);
return (
<>
<p>counter {counter} </p>
<button
onClick={() => {
setCounter(counter + 1);
}}
>
immediately add
</button>
<button
onClick={() => {
setTimeout(() => setCounter(counter + 1), 1000);
}}
>
Add
</button>
</>
);
};
2- When you call the update function multiple times in the same closure
const App = () => {
const [counter, setCounter] = useState(0);
return (
<>
<p>counter {counter} </p>
<button
onClick={() => {
setCounter(counter + 1);
setCounter(counter + 1);
}}
>
Add twice
</button>
</>
);
}
Another use case for using functional updates with setState - requestAnimationFrame with react hooks. Detailed information is available here - https://css-tricks.com/using-requestanimationframe-with-react-hooks/
In summary, handler for requestAnimationFrame gets called frequently resulting in incorrect count value, when you do setCount(count+delta). On the other hand, using setCount(prevCount => prevCount + delta) yields correct value.