React hooks: is `useCallback` not so needed usually? - reactjs

I am recently refactoring a web app using React Hooks. I encounter a problem regarding useCallback. Based on description of Kent: https://kentcdodds.com/blog/usememo-and-usecallback, useCallback is to pass in identical function reference to sub-components, to avoid re-render of sub-components, so that the performance is better. However, it's used together with React.memo. And as Kent said:
MOST OF THE TIME YOU SHOULD NOT BOTHER OPTIMIZING UNNECESSARY RERENDERS. React is VERY fast and there are so many things I can think of for you to do with your time that would be better than optimizing things like this. In fact, the need to optimize stuff with what I'm about to show you is so rare that I've literally never needed to do it ...
So, my question is: am I right to claim that we do not need to use useCallback usually? except when the callback is expensive to create, using useCallback avoids re-creating the callback for every render.
Say, for a onClick or onChange event handler, 2 lines or less, shall we just not use useCallback to wrap it?

I find the useCallback() is necessary when I don't want the function reference to change. For example, when I'm using React.memo on some child component that should not be re-rendered as a result of a reference change in one of its methods that comes through props.
Example:
In the example below Child1 will always re-render if Parent re-renders, because parentMethod1 will get a new reference on every render. And Child2 will not re-render, because the parentMethod2 will preserve its reference across renders (you can pass a dependency array to make it change and be re-created when new input values come).
Note: Assuming the Child components are being memoized with React.memo()
function Parent() {
const parentMethod1 = () => DO SOMETHING;
const parentMethod2 = useCallback(() => DO SOMETHING,[]);
return(
<React.Fragment>
<Child1
propA=parentMethod1
/>
<Child2
propA=parentMethod2
/>
</React.Fragment>
);
}
On the other hand, if the function is expensive to run, you can memoize its results using the useMemo hook. Then you will only run it when new values come, otherwise it will give you a memoized result from previous calculations using those same values.
https://reactjs.org/docs/hooks-reference.html#usecallback
useCallback
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).
useMemo
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.

I think you are right. From how it's designed, useCallback should be almost useless in React. It can not be used directly to prevent a child render.
What can save a child render is to wrap entire render using a useMemo.
const Title = () => {
...
const child = useMemo(() => {
return <Child a={"Hello World"} />
}, [])
return (
<>
{child}
<div onClick={onClick}>{count}</div>
</>
)
}
The above approach is a bit different than React.memo because it acts directly on the parent Title, instead of Child. But it's more or less answer your question why it's useless, except when you use it as the shortcut for useMemo.
Article explaining this, https://javascript.plainenglish.io/can-usememo-skip-a-child-render-94e61f5ad981
back to useCallback
Now let's go back to see if a callback wrapped with or without useCallback is useful.
<div onClick={onClick}>kk</div>
The only thing it might save is that when it's under reconciliation, onClick (with useCallback) points to the same function instance.
However I don't know if React actually does any optimization at that step. Because assigning a different callback to the attribute might take additional memory and time. But adding a new variable in general takes additional memory as well.
So this type of optimization is more like a coding optimization, more or less subjective. Not objective enough to be applied in a solid case.
Of course, if you want to fix a function instance for any third party function, ex. debounce. That might be a good use, but still smell fishy, because useMemo seems much more versatile to cover this case as well.
All in all, I'm only pointing out, useCallback isn't doing what's the public believe it can do, such as to bailout child component.

Related

Do I understand React memoization correctly?

I’ve read the documentation about memoization techniques in React and have a feeling that I understand them correctly.
However, I see projects where useMemo and useCallback are applied in different ways.
I would like to confirm that I’m not missing anything, so I will describe my understanding below. Please, tell me if this makes sense or correct me if I’m wrong :)
useMemo
This hook is used to cache the result of some calculation between re-renders.
const cachedValue = useMemo(() => calculateValue(), dependencies)
It means that after the first run, the cachedValue is saved in memory and returned on subsequent renders without any calculations if dependencies remain the same. The cachedValue object on the will be the same (same reference) and would return true on Object.is().
Memoizing results of expensive computations improves performance because they are not calculated even if the component is re-rendered, but at the cost of running the hook itself. (Not sure about that)
Additionally, it is used to prevent re-renders when passing these values as props to a component memoized with React.memo(Component) or as dependencies to useEffect hook.
useCallback
This hook caches the function definition (not the result of its work) between re-renders.
const cachedHandler = useCallback(() => { doSomething(); }, dependencies);
The function is defined and saved (but not called) in the variable on the first run, and the same definition is returned on subsequent re-renders if dependencies remain the same.
Since the function is not called when this hook is used and no results are cached, there are no immediate performance gains. (Not sure about that)
The benefit is only when this cached function is passed as a prop to a component memoized with React.memo(Component) or as a dependency to useEffect.
When a memoized function is passed as a prop inside an inline function (<Component onSomething={() => cachedHandler()} /> there are no performance gains since a new anonymous function is created and passed every time. (Saw this usage a few times).
React.memo
A component wrapped in React.memo is cached and skips re-renders as long as its props remain the same. There is no need to memoize primitive values (strings, for example), but for objects and functions, you need to apply either useMemo or useCallback hooks.
General notes
A lot of projects and developers wrap values and functions in these hooks prematurely either by convention or as a precaution ("if we memoize everything, there is no need to think about it in the future and the cost is not so high").
The documentation and React developers advise against using these hooks until it is necessary (gives measurable performance gains) as using them is not free and adds cognitive complexity (harder to read and follow the code).
This description does not cover all the cases, but can be applied to most simple cases.

In React, when a parent component re-renders, isn't it true that the children with unchanged props should not need to re-render?

I think the fact is that, when a parent component re-renders in React, in general, all the children components re-render as well.
I did one experiment to confirm:
https://codesandbox.io/s/currying-pine-r16rzi
return (
<div>
<div>Time now is {timeNow.toLocaleTimeString()}</div>
<TheTimeNow /><TheTimeNow />
</div>
);
This parent component re-renders, and <TheTimeNow /> has no change in props value (it has none), but it re-renders anyway and shows the updated time.
I think it is not the same to say, that React actually change the DOM, as I think the mechanism is that React use the previous virtual DOM to compare with the new virtual DOM, and update only the minimal actual DOM node as on document.documentElement as needed.
This can be seen if you use Google Chrome and do Inspect Element on the time statement: only the time portion changes and the other English words stay the same. Compare to this where all the nodes changes if it is plain JavaScript: https://jsfiddle.net/8sj2ugae/1/ when you do Inspect Element.
However, can't we argue that, if a child is <Foo /> and since there is no props passed to it, then <Foo /> really should not have changed, and it is wasteful to call Foo()? (if Foo is a functional component, or call the render() method if it is a class component.)
Now since all children re-renders, that means all their children also get re-rendered. Repeat this rule recursively, that would mean the whole subtree gets re-rendered.
My question is: is it true that they should not need to re-render if their props didn't change, or are there other factors that make them needing a re-render really?
The answer is yes, there is no need to rerender. But in order to know this we need to compare a new props with a previous props. And this operation is not free.
Also if we do a shallow comparison we are increasing the probability of bugs. Someone might be expecting rerender after a deep props update.
Should React.memo be the deault behaviour? Will it improve the performance in most cases? We don't know. There is no evidence for that. More on this in Mark Erikson's post.
Here is a great article by Dan Abramov: Before You memo(). I consider two points regarding your question:
In the future compiler might decide when to memoize a component.
Component rendering doesn't mean that all it's children will be rerendered.
Here is an example:
const ParentComponent = ({ children }) => {
const [state, setState] = useState(0);
return <div>{children}</div>
}
If we update the state children will not be rerendered. React just uses a previous value. Here is an article by Kent C. Dobbs about this optimization technique.
One last thing. React.memo and useMemo are different. The first one is for component memoization and the second one is a hook for expensive calculations. There are also shouldComponentUpdate for class components and PureComponent which compare not only props but also a state.
Hopefully the answer and links will shed some light on this topic.

What's the idea behind useState functionality in ReactJS?

I am learning ReactJS and I understand the benefits of functional programming. However, I am a little concerned about the actual useState approach. If we assume a variable to be changed, then why declaring it const in the first place?
I see that I can simply use let number = 4; and then render it like this <p>{number}</p>. What I cannot do however is to re-render it automatically just by changing it, for example using onClick event like this <p onClick={() => ++number }></p>. Why is it that so? Is there a specific reason I am missing why it was implemented the way it is? I mean why the developers have decided that if the value needs to be re-rendered upon change, then it must be a const value declared with the help of useState functionality?
I am asking this because I am suspecting I am missing some good points behind this and I would like to understand them.
The variable is declared as a const because you are not allowed to set it directly. In React the state itself is immutable. You are just allowed to change it over setState (or setNumber in your case) or with actions if you use redux.
But why is that? It may seem unnecessary cumbersome in the beginning
First of all, if your variable number changes, react has to trigger a rerender.
If the state is mutable, it requires data-binding because if the number is set, it has to update the view.
In javascript, data-binding works for simple objects, but not well for arrays. Vue.js for example, as an alternative that uses two-way data binding, had a lot of trouble in its early versions when dealing with arrays. That's why there are now only seven predefined methods to interact with arrays (which they added later to solve that problem). Vue Js Array Methods
So a simple reason to declare the state as const is that it works better with arrays. And if you watch the example you gave, setNumber(number + 1) is not that much more to write than number++. But setState(newArray) works, and newArray[i] = newElement would not work, because due to javascript limitations this cannot trigger a rerender.
Secondly, it is a nicer design concept. Think of your component as a function, that returns a view to a state. And if the state changes, you get a new view. It simplifies relationships between properties in your component. If you were allowed to change your state while rendering your component, it would create a mess.
The problem is that you're thinking of a functional component as if it was stateful. But it isn't. It's a function and once you run it, that's it.
Take this example:
function useState() {
let value = 1
function setValue(v) {
value = v
}
return [value, setValue]
}
function myFunction () {
const [value, setValue] = useState(); // <----- we use const here
return null
}
Even though we're using const, the value variable only exists within the function, once the function returns that's it. It's the same for components.
The actual value of value is stored in a whole different scope, where useEffect has access to.
Here's a deep dive on how react works internally if you're interested
React works in render cycles, i.e. some state is declared, the DOM (UI) is computed during the "render phase", and then flushed to the actual DOM during the "commit phase".
Within each cycle state is considered constant (const in JS simply means a variable can't be assigned a new value, you could just as easily declare it with let or var instead and react would work the same) but for react's purpose, state is constant during a render cycle. When it is updated via one of the state update methods, react then kicks off another render cycle (update state, compute diff, commit to DOM) and re-renders when necessary.
This process is important and the reason why external state mutations are considered anti-pattern, it goes against the react workflow and leads to buggy code or worse, UI that doesn't update as expected.
React component lifecycle
I cannot do however is to re-render it automatically just by changing
it, for example using onClick event like this <p onClick={() => ++number }></p>. Why is it that so?
React state updates use a process called reconciliation to figure out what changed and what to re-render. In really simplistic terms, when react state is updated it is updated with a new object reference so a shallow object comparison can more quickly detect that the component state updated.
Declaring state and doing ++number simply changes the value but not the reference, and would be considered a state mutation, an anti-pattern in react.

Performance difference from putting function outside of React component?

I know that it's going to be small, but is there a performance difference if you put functions outside of a React component?
Eg version 1:
const handleFetch = () => {
// Make API call
}
const MyComponent = () => {
useEffect(()=>{
handleFetch();
})
return(<p>hi</p>)
}
Vs version 2:
const MyComponent = () => {
const handleFetch = () => {
// Make API call
}
useEffect(()=>{
handleFetch();
})
return(<p>hi</p>)
}
In version 1 would handleFetch not be recreated when MyComponent re-renders?
is there a performance difference if you put functions outside of a React component?
Yes but you will likely never run into a noticeable performance downgrade of deciding to define a function inside a component. Almost always, the rest of your application's performance will overshadow the cost of putting functions in your components. Quoted from React's FAQ regarding functions defined in functional components in general:
Are Hooks slow because of creating functions in render?
No. In modern browsers, the raw performance of closures compared to classes doesn’t differ significantly except in extreme scenarios.
https://reactjs.org/docs/hooks-faq.html#are-hooks-slow-because-of-creating-functions-in-render
In version 1 would handleFetch not be recreated when MyComponent re-renders?
No, it will not be recreated because handleFetch is defined outside of the functional component. It's only in version 2 handleFetch will be recreated when MyComponent re-renders.
Another note: useCallback will not avoid recreating functions (you still pass a new function into useCallback too).
As a general rule for me, define the function inside the component first then extract it if it's not a hassle or it can be reused among multiple components. Sometimes, when I do extract it, I find I need to add 2 or more extra parameters to pass variables from my component to the function. But if I left it inside the functional component, I wouldn't need any parameters.
The approved answer isn't fully correct. There is one really important distinction about this sentence:
Another note: useCallback will not avoid recreating functions (you still pass a new function into useCallback too).
The function you pass to useCallback is returned every render. This means you have the same function, assuming the dependencies you defined haven't changed.
As an exercise to the reader define a function inside of a useCallback and store that result into a variable and then in a useEffect have that variable be the sole dependency. The useEffect will not re-run bc it's the same reference (strict equality.)

useMemo vs useState for React hooks constants

Defining a calculated (initialized) constant using React hooks can be performed in two ways that seem functionally equivalent. I don't want to get into the use cases for this, but suffice to say that there are cases where a constant value can be derived from initial props or state that isn't expected to change (think route data, bound dispatch, etc).
First, useState
const [calculatedConstant] = useState(calculateConstantFactory);
Second, useMemo
const calculatedConstant = useMemo(calculateConstantFactory, []);
Both of these seem functionally equivalent, but without reading the source code, I'm not sure which is better in terms of performance or other considerations.
Has anyone done the leg work on this? Which would you use and why?
Also, I know some people will recoil at the assumption that state can be "considered constant". I'm not sure what to tell you there. But even without state, I may want to define a constant within a component that has no state at all, for example, creating a block of JSX that doesn't change.
I could define this outside of the component, but then it's consuming memory, even when the component in question is not instantiated anywhere in the app. To fix this, I would have to create a memoization function and then manually release the internal memoized state. That's an awful lot of hassle for something hooks give us for free.
Edit: Added examples of the approaches talked about in this discussion.
https://codesandbox.io/s/cranky-platform-2b15l
You may rely on useMemo as a performance optimization, not as a semantic guarantee
Meaning, semantically useMemo is not the correct approach; you are using it for the wrong reason. So even though it works as intended now, you are using it incorrectly and it could lead to unpredictable behavior in the future.
useState is only the correct choice if you don't want your rendering to be blocked while the value is being computed.
If the value isn't needed in the first render of the component, you could use useRef and useEffect together:
const calculatedConstant = useRef(null);
useEffect(() => {
calculatedConstant.current = calculateConstantFactory()
}, [])
// use the value in calcaulatedConstant.current
This is the same as initializing an instance field in componentDidMount. And it doesn't block your layout / paint while the factory function is being run. Performance-wise, I doubt any benchmark would show a significant difference.
The problem is that after initializing the ref, the component won't update to reflect this value (which is the whole purpose of a ref).
If you absolutely need the value to be used on the component's first render, you can do this:
const calculatedConstant = useRef(null);
if (!calculatedConstant.current) {
calculatedConstant.current = calculateConstantFactory();
}
// use the value in calculatedConstant.current;
This one will block your component from rendering before the value is set up.
If you don't want rendering to be blocked, you need useState together with useEffect:
const [calculated, setCalculated] = useState();
useEffect(() => {
setCalculated(calculateConstantFactory())
}, [])
// use the value in calculated
Basically if you need the component to re-render itself: use state. If that's not necessary, use a ref.

Resources