in React, when a component re-renders, all the functions inside that component will be recreated, resulting in a change in function references.
So that even if a child component is wrapped in memo() to prevent it from unnessecary rerenders caused by parent component. It would still rerender in case a function is passed to it as a prop from the parent component?
Here is an example:
const Parent = () => {
const [state, setState] = useState("1");
const handleIncrement = useCallback(() => {
const increment = setState(state + 1);
}, [state]);
return (
<Child handle={() => handleIncrement()} />
);
}
const Child = memo(({handle}) => {
return();
});
That's the main purpose of using useCallback() on functions passed to the child component? so that the function's reference will stay the same between rerenders that don't affect the usecallback dependency array for that function in the parent component. Which passes the reference equality check done by the child component?
Resulting in maintaining the same function reference when a function is passed as a prop to a child component? preventing rerenders from making the use of memo() uneffective?
My question: is that the most important use of usecallback(), generally?
A "component" is simply a JavaScript function. "Rendering" a component simply means "running the function".
You are correct that each time a JS function runs it recreates the variables within it, including function definitions.
The "magic" that React offers to avoid things looking like they are being recreated is
useState()
,useMemo()
,useCallback()
, etc. These are in no way bypassing the base JS behaviours mentioned above. These hooks "cache" values when they are first run, and subsequent runs (i.e. subsequent "renders"), if React sees that it has a variable/object/function in cache then it gives that value back to the "new" instance create by useState/useMemo/useFunction as was in the previous run.Now, in your above example if the
handle
property changes then it will regenerate the Child component and so it will be re-rendered. You have at least 2 ways to avoid this:useCallback()
when defininghandleIncrement()
(as you have done....but see below)memo()
-- a function that is meant to compare previous and current props to the component and returntrue
if they are the same (i.e. do not render). So you could simply set that second parameter to() => true
and it will never re-generate and never re-render.Now ...as for your current
handleIncrement()
. You have it using auseCallback()
that is dependent on the value ofstate
. But each time thathandleIncrement()
is called, it is increasing the value ofstate
. So that will causeParent
to re-render and with the new value ofstate
useCallback() will re-generatehandleIncrement()
as a new function because its dependency has a new value.And....one last thing:
setState(state + 1);
is a TERRIBLE pattern to code to. BecausesetState()
is an asynchronous function, you are telling React (well, JavaScript): "at some point later in time, please take the current value ofstate
, add1
to it, and put it back intostate
".The problem here is that if
setState()
gets called multiple times before JavaScript gets around to running its asynchronous queue....you can end up losing data!!!! If the current value ofstate
is7
and you code:then when your component finally gets around to re-rendering the value of
state
will be5
...because JS was told to run7 + 1
and7 + 100
and7 - 2
...and the last one wins.Whenever you have a state variable and you want to update its value to be relative to its current value -- whether that be an array, an object, a string, an integer, whatever -- then you want to use the "callback signature" of the "set state function":
setState(currVal => currVal + 1)
.This syntax tells JavaScript "at the timet that you are running the set state function, get its current value, add
1
, then put it back into the state variable".With this latter approach, if you call
setState()
a thousand times, it will get the correct answer every single time. Given the same conditions above,state
is7
and you code:then JS will do:
7 + 1
and8 + 100
and108 - 2
. The value on re-render will be106
. The main difference is thatsetState(currVal => ....)
gets the value of the state variable at the time thatsetState()
is running, whereassetState(state + 1)
gets the value ofstate
***at the timesetState()
is being added to the asynchronous queue.If you haven't already, I strongly encourage all of my friends (and make it a requirement of all of my coworkers) to watch the video JavaScript: Understanding the Weird Parts multiple times, especially the parts about Synchronous and Asynchronous code execution (time links are in the video description).