React useState hook, calling setState with function - javascript

There is a concept in React (when using hooks) that confuses me.
I made a component for explanation (that increases a counter):
const [counter, setCounter] = useState(0); // counter hook
// code will follow
// render
return (
<div>
<button onClick={handleClick}>+</button>
<h3>{counter}</h3>
</div>
);
For the handler function, I have seen different options to set the state.
First method (using setState() normally):
const handleClick = () => {
setCounter(counter + 1);
};
Second method (creating a function inside setState() and returning the new value):
const handleClick = () => {
setCounter((counter) => {
return counter + 1;
});
};
I thought the difference would be that with the second method, you could immediately do a callback after setting the state, like this:
const handleClick = () => {
setCounter((counter) => {
return counter + 1;
}, () => {
console.log(counter); // trying callback after state is set
});
};
But when trying this (with both methods), the console displays the following error message:
Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
So I think in both cases, using useEffect() as a callback on setState() is the right way.
My question is: what is the difference between the two methods and what method is best to set the state. I have read about state immutability but can not immediately see how it would make a difference in this example.

In your case it's the same.
Basically when your state is computed with your previous state you can use the second approach which gets the previous value.
Have a look in React docs about this:
Functional updates

Since this question is gaining some attention I will add this example.
<button onClick={() => setCount(0)}>Reset</button>
<button onClick={() => setCount((prevCount) => prevCount - 1)}>-</button>
<button onClick={() => setCount((prevCount) => prevCount + 1)}>+</button>
You can see that for + and - the functional setStateis being used, this is because the new state value is computed using the previous state (you are adding/subtracting from the previous count value).
The reset button is using the normal form, because it doesn't compute the new state value based on a computation on the old value, it always just sets it to a fixed number (for example 0).
So in my case, the best thing to do would have been using the functional setState.

Related

What is the difference in the below code when using useState() in React [duplicate]

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
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>
</>
);
}
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.
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.

Why is my React app not pushing all elements to an array state? [duplicate]

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.

React: When does not the count value changes in setInterval?

I know this code will work and the count value will be updated after every 2 seconds:
import React from 'react';
function App() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
setInterval(() => {
setCount(prev => prev + 1);
}, 2000);
}, []);
return <h1>{count}</h1>;
}
I am trying to understand why the below code doesn't work:
React.useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1);
console.log('hello');
}, 2000);
return () => clearInterval(interval);
}, []);
In this case, count stays at 1 but the setInterval is called after every 2 seconds because hello is being logged every 2 seconds.
How setInterval is being called when component is not being rendered? Does it operate outside of React lifecycle or something?
When I pass callback to setCount(prev => prev + 1), then it works fine? Why? How the prev in callback getting the updated value?
I have read similar answers but still it is not making sense to me so if someone could please clear the doubt.
Thanks
How setInterval is being called when component is not being rendered? Does it operate outside of React lifecycle or something?
Yes, setInterval is a function that's part of the core javascript language. By calling setInterval, you ask the browser to call the specified function every 2000ms. React is not involved in this.
When I pass callback to setCount(prev => prev + 1), then it works fine? Why? How the prev in callback getting the updated value?
The useState hook is designed to allow this interaction. React promises that if you pass a function into setCount, then react will call your function and pass in the latest value.
The exact code with which they implement this can be tricky to step through since they reuse a lot of general purpose functions to implement several different hooks, but if you want to poke around their codebase you can look here: https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.new.js#L1525
When you create the interval function, count will be the value at the moment of creation (so 0).
When it gets called later, it will still hold that value, since it won't get changed in that context. So it will always set it to 0 + 1, so 1.
When you pass a callback function, it will use the current, correct value.
Hope this could clear things up!
This version: setCount(prev => prev + 1) uses the previous state value every-time the interval gets run.
This version: setCount(count + 1) uses count when the useEffect was las run (in this case 0, so 0+1). Because there is an empty dependency array this hook is only run once.
If count was included in the dependency array of useEffect then it would get re-run when the value of count changes.
Running this may help to see what's going on:
React.useEffect(() => {
console.log('running useEffect')
const interval = setInterval(() => {
setCount(count + 1);
console.log('hello ', count);
}, 500);
return () => {
console.log('clear interval')
clearInterval(interval);
}
}, [count]);

Setting state correctly using the State Hook

I'm currently learning React and trying to understand how to update the state correctly in a function component.
I've learned from the "State and Lifecycle" section in the React docs that you should update the state if it's using the previous state like the following:
// Correct
this.setState((state, props) => ({
counter: state.counter + 1
}));
instead of
// Wrong
this.setState({
counter: this.state.counter + 1,
});
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state. (Reference)
Now I want to achieve the same with a function component using the State Hook useState.
As an example how to update the state in a function component the React docs has the following code snippet:
<button onClick={() => setCount(count + 1)}>
Click me
</button>
which is equivalent to the following code in a class component:
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
But isn't this the "wrong" solution because it relies directly on the state?
But isn't this the "wrong" solution because it relies directly on the state?
setState merges the new state into the old one. And does it in a 'piecemeal' fashion e.g. new state can contain a small subset of old state's data. So it's beneficial to have the old state at hand inside the setState body and explicitly merge it using this.setState(oldState => ({ ...oldState, someProp: true }) )
'Explicitly' is better because it shows you don't have illusions about what setState will do anyway: merging even if you didn't.
useState replaces the old state with the new one so the need to have access to oldState is less acute. Even though I wouldn't mind it.
As for updated asynchronously, both setState and useState can batch updates. Although useState won't do batching when dealing with pending Promises, as opposed to ready-to-use values like count + 1 from your sample.
Coming back to setState, there is another reason why the flavor setState(oldState => ({...}) is better. Remember, setState always do merging no matter what. Combined with batching, it can lead to the following piece code executed by React if the flavor setState(newValue) was used:
Object.assign(oldState, newValue1, newValue2, newValue3)
where the values newValueX are the batched updates. Object.assign ensures that 'last update wins'. If the series of newValueX represents successive attempts to increment the same counter then it might not work as expected. Therefore the other flavor of setState is better. With useState and batching it looks like this kind of danger persists.
You can do something like following.
import React, { userState } from 'react';
function TestHook() {
const [count, setCount] = useState(0);
function updateCount() {
setCount(count + 1);
}
return <button onClick={handleCount}>{count}</button>
}
Check it here. https://codesandbox.io/s/one-with-the-hook-0yig1
You can follow it in my Medium post (https://blog.usejournal.com/getting-started-with-react-hooks-f0b5c1e3e0e7)

What does useCallback/useMemo do in React?

As said in docs, useCallback
Returns a memoized 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).
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
But how does it work and where is the best to use it in React?
P.S. I think visualisation with codepen example will help everyone to understand it better. Explained in docs.
This is best used when you want to prevent unnecessary re-renders for better performance.
Compare these two ways of passing callbacks to child components taken from React Docs:
1. Arrow Function in Render
class Foo extends Component {
handleClick() {
console.log('Click happened');
}
render() {
return <Button onClick={() => this.handleClick()}>Click Me</Button>;
}
}
2. Bind in Constructor (ES2015)
class Foo extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('Click happened');
}
render() {
return <Button onClick={this.handleClick}>Click Me</Button>;
}
}
Assuming <Button> is implemented as a PureComponent, the first way will cause <Button> to re-render every time <Foo> re-renders because a new function is created in every render() call. In the second way, the handleClick method is only created once in <Foo>'s constructor and reused across renders.
If we translate both approaches to functional components using hooks, these are the equivalents (sort of):
1. Arrow Function in Render -> Un-memoized callback
function Foo() {
const handleClick = () => {
console.log('Click happened');
}
return <Button onClick={handleClick}>Click Me</Button>;
}
2. Bind in Constructor (ES2015) -> Memoized callbacks
function Foo() {
const memoizedHandleClick = useCallback(
() => console.log('Click happened'), [],
); // Tells React to memoize regardless of arguments.
return <Button onClick={memoizedHandleClick}>Click Me</Button>;
}
The first way creates callbacks on every call of the functional component but in the second way, React memoizes the callback function for you and the callback is not created multiple times.
Hence in the first case if Button is implemented using React.memo it will always re render (unless you have some custom comparison function) because the onClick prop is different each time, in the second case, it won't.
In most cases, it's fine to do the first way. As the React docs state:
Is it OK to use arrow functions in render methods? Generally speaking,
yes, it is OK, and it is often the easiest way to pass parameters to
callback functions.
If you do have performance issues, by all means, optimize!
useCallback and useMemo are an attempt to bypass weak spots that come with the functional programming approach chosen with React hooks. In Javascript, each entity, no matter if it is a function, variable, or whatever, is created into the memory when the execution will enter the function's code block. This is a big issue for a React that will try to detect if the component needs to be rendered. The need for rerendering is deducted based on input props and contexts. Let's see a simple example without useCallback.
const Component = () => {
const [counter, setCounter] = useState(0);
const handleClick = () => {
setCounter(counter + 1);
}
return <div>
Counter:{counter}<br/>
<button onClick={handleClick}>+1</button>
</div>
}
Note that the handleClick -function instance will be created on each function call inside the block, so the event handler's address on each call will be different. The React framework will always see the event handler as changed because of this. In the example above, React will think handleClick as a new value on each call. It simply has no tools to identify it as the same call.
What useCallback does, it internally stores the first introduced version of the function and returns it to the caller, if the listed variables have not changed.
const Component = () => {
const [counter, setCounter] = useState(0);
const handleClick = useCallback(() => {
setCounter(counter + 1);
}, [])
return <div>
Counter:{counter}<br/>
<button onClick={handleClick}>+1</button>
</div>
}
Now, with the code above, React will identify the handleClick -event handler as the same, thanks to useCallback -function call. It will always return the same instance of function and React component rendering mechanism will be happy.
Storing the function internally by the useCallback will end up with a new problem. The stored instance of the function call will not have direct access to the variables of the current function call. Instead, it will see variables introduced in the initial closure call where the stored function was created. So the call will not work for updated variables. Thats why you need need tell if some used variables have changed. So that the useCallback will store the current function call instance as a new stored instance. The list of variables as the second argument of the useCallback is listing variables for this functionality. In our example, we need to tell to useCallback -function that we need to have a fresh version of counter -variable on each call. If we will not do that, the counter value after the call will be always 1, which comes from the original value 0 plus 1.
const Component = () => {
const [counter, setCounter] = useState(0);
const handleClick = useCallback(() => {
setCounter(counter + 1);
}, [counter])
return <div>
Counter:{counter}<br/>
<button onClick={handleClick}>+1</button>
</div>
}
Now we have a working version of the code that will not rerender on every call.
It is good to notice that the useState -call is here just for the same reason. Function block does not have an internal state, so hooks are using useState, useCallback and useMemo to mimic the basic functionality of classes. In this sense, functional programming is a big step back in history closer to procedural programming.
useMemo is the same kind of mechanism as useCallback but for other objects and variables. With it, you can limit the need for component rerender, as the useMemo -function will return the same values on each function call if the listed fields have not changed.
This part of the new React hooks -approach is definitely the weakest spot of the system. useCallback is pretty much counterintuitive and really error-prone. With useCallback-calls and dependencies, it is too easy to end up chasing internal loops. This caveat we did not have with the React Class approach.
The original approach with classes was more efficient after all. The useCallback will reduce the need to rerender, but it regenerates the function again every time when some of its dependant variables will change, and matching if the variables have changes itself will make overhead. This may cause more rerenders than necessary. This is not the case with React classes.
I've made a small example to help others understand better how it behaves. You can run the demo here or read the code bellow:
import React, { useState, useCallback, useMemo } from 'react';
import { render } from 'react-dom';
const App = () => {
const [state, changeState] = useState({});
const memoizedValue = useMemo(() => Math.random(), []);
const memoizedCallback = useCallback(() => console.log(memoizedValue), []);
const unMemoizedCallback = () => console.log(memoizedValue);
const {prevMemoizedCallback, prevUnMemoizedCallback} = state;
return (
<>
<p>Memoized value: {memoizedValue}</p>
<p>New update {Math.random()}</p>
<p>is prevMemoizedCallback === to memoizedCallback: { String(prevMemoizedCallback === memoizedCallback)}</p>
<p>is prevUnMemoizedCallback === to unMemoizedCallback: { String(prevUnMemoizedCallback === unMemoizedCallback) }</p>
<p><button onClick={memoizedCallback}>memoizedCallback</button></p>
<p><button onClick={unMemoizedCallback}>unMemoizedCallback</button></p>
<p><button onClick={() => changeState({ prevMemoizedCallback: memoizedCallback, prevUnMemoizedCallback: unMemoizedCallback })}>update State</button></p>
</>
);
};
render(<App />, document.getElementById('root'));
An event handler gets recreated and assigned a different address on every render by default, resulting in a changed ‘props’ object. Below, button 2 is not repeatedly rendered as the ‘props’ object has not changed. Notice how the entire Example() function runs till completion on every render.
const MyButton = React.memo(props=>{
console.log('firing from '+props.id);
return (<button onClick={props.eh}>{props.id}</button>);
});
function Example(){
const [a,setA] = React.useState(0);
const unmemoizedCallback = () => {};
const memoizedCallback = React.useCallback(()=>{},[]); // don’t forget []!
setTimeout(()=>{setA(a=>(a+1));},3000);
return (<React.Fragment>
<MyButton id="1" eh={unmemoizedCallback}/>
<MyButton id="2" eh={memoizedCallback}/>
<MyButton id="3" eh={()=>memoizedCallback}/>
</React.Fragment>);
}
ReactDOM.render(<Example/>,document.querySelector("div"));

Categories

Resources