Why React useState with functional update form is needed? - reactjs

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.

Related

How to log the updated count value in react?

Why the updated count value is not logged while clicking the button. It always logs the previous count value on button click. Here is the code -
import { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
function updateCount() {
setCount(count + 1);
console.log(count);
}
return (
<div className="App">
<div>
<p>You clicked {count} times</p>
<button onClick={() => updateCount()}>Show alert</button>
</div>
</div>
);
}
on react when your state depends on old state value you must update it like this :
function updateCount() {
setCount(oldCount=>oldCount+ 1);
console.log(count);
}
and you must remember updating the state is not instant action it is super fast, but it take few milliseconds
I am learning React recently and I was facing the exact same problem as you. The reason console.log gives you the previous value as mentioned by Mohammad is because in the same method you are logging and updating the value and they happen together. If you simply put the console.log below the function then it will show you the updated one every time as it finished updating the value before logging.
const incrementCount = (increment) => {
setCount(count + increment);
};
console.log(count);
Hope this helps!
If you want to always log the latest value, you could just do this:
import { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
console.log(count);
function updateCount() {
setCount(prevCount => prevCount + 1);
}
return (
<div className="App">
<div>
<p>You clicked {count} times</p>
<button onClick={() => updateCount()}>Show alert</button>
</div>
</div>
);
}
The reason why in your code the log function logs an outdated value is because of closure - the count value is a const, and it is set when the component is first rendered. So when you click the button, the value of count is not the new value, but the one it was when it was first rendered (i.e. -> always going to be one less than you expect).
In this code, log happens on every re-render. Since clicking the button calls setCount, the state changes, and a re-render occurs. When that happens, the log is executed, and you get the latest value :)
This is the way that the React lifecycle works. The console.log of your value will use the value of count at the time of render.
Render --> Execute ALL functions with the given state values --> Check for state changes --> Render --> Execute ALL functions with the new state values
Each render is with STATIC values.
If you want to see an output to the console of your count, you'll need to use a useEffect.

Should I use useEffect() for dom manipulation or just do it directly?

In the React docs I see this piece of code:
function Example() {
const [count, setCount] = useState(0);
//THE SUBJECT OF MY QUESTION:
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
So, my question is why not change the document.title or any other DOM without using useEffect, like this:
function Example() {
const [count, setCount] = useState(0);
//THE SUBJECT OF MY QUESTION:
document.title = `You clicked ${count} times`;
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
I know that when sending requests for example, it is asynchronous and we need to do it in useEffect. But DOM manipulation isn't asynchronous and it takes relatively 0 time, why do we then still have to use useEffect hook?
Its almost the same, see my related answer, useEffect in-depth.
The difference is a notable "gotcha", useEffect callback executed after the render phase.
const App = () => {
useEffect(() => {
console.log("executed after render phase");
});
console.log("executed at render phase");
return <></>;
};
Will result:
executed at render phase
executed after render phase
You better keep DOM manipulations and other side effects inside useEffect.
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // set your dependencies
Reason is: This approach is compliant with React Strict Mode and upcoming Concurrent Mode.
What Concurrent mode is:
[...] React may invoke render phase lifecycles more than once before committing, or it may invoke them without committing at all (because of an error or a higher priority interruption).
In your case, the document.title assignment might be repeated multiple times or React might even decide to abort the whole commit. In general, this can lead to inconsistencies:
Because the above methods might be called more than once, it’s important that they do not contain side-effects. Ignoring this rule can lead to a variety of problems, including memory leaks and invalid application state. (docs)
It's important to know how useEffect works.
Here's some clarity.
useEffect(() => {
// this runs when the component mounts
return () => {
// This runs when the component unmounts (not useful for you right now)
}
}, []); // You were missing the second argument (dependencies array)
Regarding the dependencies array. Putting values in there will cause your effect to run again when that value changes, which is very useful for your need.
What you should do is the following:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
This will update document.title whenever count changes.
I generally recommend making use of useEffect for these kinds of situations over not using it simple because it will take all the life-cycle processes into account.
TLDR: It should perform better.

Why does an effect on child component run again?

I would like some help please to understand why the code below is causing an inf render loop.
The way I see it, this should have been the cycle:
Render Container.
Render child StrRow.
Run effect for the first time and call update().
Change Container state.
Re-Render Container.
Re-Render chlid StrRow.
DO NOT run effect again as the dependency has not changed.
Yet, the effect seem to run after every render of StrRow.
function Container() {
const [count, setCount] = React.useState(0);
const update = React.useCallback(() => {
setCount(count + 1);
}, [count, setCount]);
return (
<div style={{ background: "#ccc", border: "3px solid black" }}>
Increced Length {count} Times.
<StrRow update={update} />
</div>
);
}
function StrRow({ update }) {
const [str, setStr] = React.useState(">");
React.useEffect(() => {
update();
}, [str, update]);
return (
<div style={{ background: "#27b" }}>
<div>{str}</div>
<button
onClick={() => {
setStr(str + ">");
}}
>
Add Length
</button>
</div>
);
}
Here is a sandbox link with the update() call commented out to prevent the inf loop:
https://codesandbox.io/s/compassionate-montalcini-muiod?file=/src/App.js
Please note that I am not looking for ways to fix or change this code. I would just like to understand the cause for the issue.
Thank you very much!
What you have in the demo is slight different from what you have in the post.
Note that what you write in the post will not trigger an infinite loop and the onClick on child button will go through the following steps
Update child str state
Run child useEffect and calls parents update method
update function increases count value and re-renders parent
child component is re-rendered
However in the demo you have
React.useEffect(() => {
update();
}, [str, update]);
The above useEffect however will cause an infinite loop. Reason being that when the update function is triggered it updates the state which basically leads to a new instance of update being created on re-render of Parent component as useCallback dependency has changed
The correct way to define the useCallback would have been to use setCount with functional pattern and not pass in count as a dependency of useCallback like below and everything would then work fine
const update = React.useCallback(() => {
setCount(prevCount => prevCount + 1);
}, [setCount]);
Working demo
this is because update setsState which causes a rerender, which renders your <StrRow> component to rendered, which onMount in useEffect calls that update function which setState..etc and will continue to do so forever

What is the equivalent of passing an updater to `setState` that takes `(state, props)` as an argument to update state, using React Hook?

What is the best practice to replace the usage of setState function from React.Component --
https://reactjs.org/docs/react-component.html#setstate
setState(updater, [callback])
where updater has the signature
(state, props) => stateChange
(So the new state depends on previous state and also props)
-- using React hooks?
When I searched for the useState hook's API, https://reactjs.org/docs/hooks-reference.html#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:
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>
</>
);
}
the function updating the state, setCount, does not take props as an argument.
Is the best practice for this to use useEffect hook, with props as a dependency?
Could anyone explain why this was separated in the React hooks?
The Hooks FAQ has a section on How do I implement getDerivedStateFromProps?. While it first says generally you probably won't need derived state from props, it then goes on to give an example of referencing props via closure, i.e. just references them directly.
So I believe a combination of referencing the prop directly with the set state function argument should work, like:
export default function Conuter({ incrementBy }) {
const [count, setCount] = useState(0)
return (
<>
Count: {count}
<button onClick={() => setCount(prevCount => prevCount + incrementBy)}>
Increase count by increment
</button>
<button onClick={() => setCount(0)}>Reset</button>
</>
);
}
Live example here.
In short, yes, it is perfectly safe to use setCount(count + 1), and this is the usual practice for setting of state for functional components.
If you are looking for the callback pattern, I believe this other SO post will be more suited to answer your question, as they are utilising the useEffect hook for that purpose.

React Hooks: setState functionality on re-renders

This is a question regarding a possible performance hit while using hooks.
Quoting useState example from react docs:
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>
</>
);
}
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.
I have two queries regarding the usage of useState :
What does identity is stable and won’t change on re-renders mean ?
I can see that for each button an anonymous function is passed as event handler. Even if setState identity is stable as claimed by React is true, wouldn't the anonymous function be re-created at every re-render ?
Wouldn't it be more efficient if useCallback was used to define memoized functions and use them as event handlers ?
What does identity is stable and won’t change on re-renders mean ?
The function returned by useState will not change across render cycles. That is, the dispatch function returned on the first render cycle can still be called after say, the 10th render cycle to set state. This allows you to setup a hook using the dispatch function at any point without needing to refresh the hook when the function reference changes.
useEffect(() => {
setCount(initialCount);
}, [ initialCount, setCount ]); // <--- setCount is not needed here
I can see that for each button an anonymous function is passed as event handler. Even if setState identity is stable as claimed by React is true, wouldn't the anonymous function be re-created at every re-render ?
Yes it is true that the arrow functions will be rebuilt on re-render. The alternative is to use useCallback to memoize a callback, but at what cost? The cost of invoking useCallback, the cost of memoizing that callback, the cost of building references to it and the cost of retrieving that callback on every re-render heavily outweighs the benefits of simply building a function on every re-render.
The useCallback function itself is 20 lines long with 3 other nested function calls to other internal React APIs. All that to prevent making a single line function on every render? The math simply does not add up in favour of useCallback. The only useful scenario is when you want your callbacks to have a "stable identity" either by reference or some other mechanism, so that you can pass the callback as props without causing excessive re-renders.
Consider this component as an illustration for question 1.
function Counter({ initialCount }) {
const [count, setCount] = useState(initialCount);
// after each render we record the value of setCount
const ref = useRef(null);
useEffect(() => {
ref.current = setCount;
}, [setCount]);
return (
<>
<div>
Did setCount change from since render?{" "}
{(!Object.is(ref.current, setCount)).toString()}
</div>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</>
);
}
It means during different component renders useState will be the same (Object.is)
Yes anonymous functions will be re-created on every render
Wouldn't it be more efficient if useCallback was used to define memoized functions and use them as event handlers?
In this particular case, no, because useCallback does not come for free while buttons will be rendered anyway. But when we have a very heavy component to render useCallback will prevent it from unnecessary re-renders
What does identity is stable and won’t change on re-renders mean ?
your setCount method is stable and won't change on re-renders.
I can see that for each button an anonymous function is passed as event handler. Even if setState identity is stable as claimed by React is true, wouldn't the anonymous function be re-created at every re-render ?
You can but here there is no need to do so as you are not passing it to child component, you should use useCallback if you are passing it to another React component.

Resources