Performance penalty of creating handlers on every render with react-hooks - javascript

I'm currently very amazed about the use cases of the new react hooks API and what you can possibly do with it.
A question that came up while experimenting was how expensive it is to always create a new handler function just to throw it away when using useCallback.
Considering this example:
const MyCounter = ({initial}) => {
const [count, setCount] = useState(initial);
const increase = useCallback(() => setCount(count => count + 1), [setCount]);
const decrease = useCallback(() => setCount(count => count > 0 ? count - 1 : 0), [setCount]);
return (
<div className="counter">
<p>The count is {count}.</p>
<button onClick={decrease} disabled={count === 0}> - </button>
<button onClick={increase}> + </button>
</div>
);
};
Although I'm wrapping the handler into a useCallback to avoid passing down a new handler every time it renders the inline arrow function still has to be created only to be thrown away in the majority of times.
Probably not a big deal if I only render a few components. But how big is the impact on performance if I do that 1000s of times? Is there a noticeable performance penalty? And what would be a way to avoid it? Probably a static handler factory that only gets called when a new handler has to be created?

The React FAQs provide an explanation to it
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.
In addition, consider that the design of Hooks is more efficient in a
couple ways:
Hooks avoid a lot of the overhead that classes require, like the cost
of creating class instances and binding event handlers in the
constructor.
Idiomatic code using Hooks doesn’t need the deep component tree
nesting that is prevalent in codebases that use higher-order
components, render props, and context. With smaller component trees,
React has less work to do.
Traditionally, performance concerns around inline functions in React
have been related to how passing new callbacks on each render breaks
shouldComponentUpdate optimizations in child components. Hooks
approach this problem from three sides.
So overall benefits that hooks provide are much greater than the penalty of creating new functions
Moreover for functional components, you can optimize by making use of useMemo so that the components are re-rendering when there is not change in their props.

I did a simple test with the below example, which using 10k(and 100k) usingCallback hooks and re-rendering every 100ms. It seems that the number of useCallback could effect when it is really a lot. See the result below.
Function component with 10k hooks:
Each rendering took 8~12ms.
Function component with 100k hooks:
Each rendering took 25~80ms.
Class component with 10k methods:
Each rendering took 4~5ms.
Class component with 100k methods:
Each rendering took 4~6ms.
I've tested with 1k example too. But the profile result looks almost same as the one with 10k.
So the penalty was noticeable in my browser when my component using 100k hooks while class component didn't show noticeable difference. So I guess It should be fine as long as you don't have a component using more than 10k hooks. The number probably depends on client's runtime resource though.
Test component code:
import React, { useState, useCallback, useEffect } from 'react';
const callbackCount = 10000
const useCrazyCounter = () => {
const callbacks = []
const [count, setCount] = useState(0)
for (let i = 1; i < callbackCount + 1; i++) {
// eslint-disable-next-line
callbacks.push(useCallback(() => {
setCount(prev => prev + i)
// eslint-disable-next-line
}, []))
}
return [count, ...callbacks]
}
const Counter = () => {
const [count, plusOne] = useCrazyCounter()
useEffect(() => {
const timer = setInterval(plusOne, 100)
return () => {
clearInterval(timer)
}}
, [])
return <div><div>{count}</div><div><button onClick={plusOne}>Plus One</button></div></div>
}
class ClassCounter extends React.Component {
constructor() {
super()
this.state = {
count: 0
}
for (let i = 1; i < callbackCount; i++) {
this['plus'+i] = () => {
this.setState(prev => ({
count: prev.count + i
}))
}
}
}
componentDidMount() {
this.timer = setInterval(() => {
this.plus1()
}, 100)
}
componentWillUnmount() {
clearInterval(this.timer)
}
render () {
return <div><div>{this.state.count}</div><div><button onClick={this.plus1}>Plus One</button></div></div>
}
}
const App = () => {
return (
<div className="App">
<Counter/>
{/* <ClassCounter/> */}
</div>
);
}
export default App;

But how big is the impact on performance if I do that 1000s of times? Is there a noticeable performance penalty?
It depends on the app. If you're just simply rendering 1000 rows of counters, it's probably ok, as seen by the code snippet below. Note that if you are just modifying the state of an individual <Counter />, only that counter is re-rendered, the other 999 counters are not affected.
But I think you're concerned over irrelevant things here. In real world apps, there is unlikely to have 1000 list elements being rendered. If your app has to render 1000 items, there's probably something wrong with the way you designed your app.
You should not be rendering 1000 items in the DOM. That's usually bad from a performance and UX perspective, with or without modern JavaScript frameworks. You could use windowing techniques and only render items that you see on the screen, the other off-screen items can be in memory.
Implement shouldComponentUpdate (or useMemo) so that the other items do not get re-rendered should a top level component have to re-render.
By using functions, you avoid the overhead of classes and some other class-related stuff that goes on under the hood which you don't know of because React does it for you automatically. You lose some performance because of calling some hooks in functions, but you gain some performance elsewhere also.
Lastly, note that you are calling the useXXX hooks and not executing the callback functions you passed into the hooks. I'm sure the React team has done a good job in making hooks invocation lightweight calling hooks shouldn't be too expensive.
And what would be a way to avoid it?
I doubt there would be a real world scenario where you will need to create stateful items a thousand times. But if you really have to, it would be better to lift the state up into a parent component and pass in the value and increment/decrement callback as a prop into each item. That way, your individual items don't have to create state modifier callbacks and can simply use the callback prop from its parent. Also, stateless child components make it easier to implement the various well-known perf optimizations.
Lastly, I would like to reiterate that you should not be worried about this problem because you should be trying to avoid landing yourself into such a situation instead of dealing with it, be leveraging on techniques like windowing and pagination - only loading the data that you need to show on the current page.
const Counter = ({ initial }) => {
const [count, setCount] = React.useState(initial);
const increase = React.useCallback(() => setCount(count => count + 1), [setCount]);
const decrease = React.useCallback(
() => setCount(count => (count > 0 ? count - 1 : 0)),
[setCount]
);
return (
<div className="counter">
<p>The count is {count}.</p>
<button onClick={decrease} disabled={count === 0}>
-
</button>
<button onClick={increase}>+</button>
</div>
);
};
function App() {
const [count, setCount] = React.useState(1000);
return (
<div>
<h1>Counters: {count}</h1>
<button onClick={() => {
setCount(count + 1);
}}>Add Counter</button>
<hr/>
{(() => {
const items = [];
for (let i = 0; i < count; i++) {
items.push(<Counter key={i} initial={i} />);
}
return items;
})()}
</div>
);
}
ReactDOM.render(
<div>
<App />
</div>,
document.querySelector("#app")
);
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>

One way could be memoizing the callbacks to prevent the unnecessary updates of child components.
you can read more about this here.
Also, I create an npm package useMemoizedCallback which I hope helps anyone looking for a solution to increase performance.

You are right, in large applications this can lead to performance issues. Binding the handler before you pass it to the component avoids that the child component might does an extra re-rendering.
<button onClick={(e) => this.handleClick(e)}>click me!</button>
<button onClick={this.handleClick.bind(this)}>click me!</button>
Both are equivalent. The e argument representing the React event, while with an arrow function, we have to pass it explicitly, with bind any arguments are automatically forwarded.

Related

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.

Should component itself prevent unwanted useEffect() calls?

Using useEffect() properly is sometimes not that easy. Imagine we have the following simple app using the Counter component:
import { useState, useEffect } from 'react';
const Counter = ({ onOdd, onEven }) => {
const [count, setCount] = useState(0);
useEffect(
() => {
console.log('Inside useEffect()');
if (count % 2 === 0) {
onEven(count);
} else {
onOdd(count);
}
},
[count, onOdd, onEven]
);
return (
<button
type="button"
onClick={() => setCount(count => count + 1)}
>
{count}
</button>
);
}
const App = () => {
const [isDarkMode, setIsDarkMode] = useState(false);
return (
<div style={{
backgroundColor: isDarkMode ? 'black' : 'white',
}}>
<Counter
onOdd={count => console.log(`Odd count: ${count}`)}
onEven={count => console.log(`Even count: ${count}`)}
/>
<button
type="button"
onClick={() => setIsDarkMode(isDarkMode => !isDarkMode)}
>
Toggle dark mode
</button>
</div>
);
}
export default App;
The app does two things:
It includes a Count button that increments its counter by 1. This components allows to inject two functions: onOdd and onEven. Whenever the counter changes, either onOdd or onEven is called, depending on the counter... being odd or even.
There is also a dark mode toggle. The only purpose I added it is to have something that causes the Counter to re-render for other reason than changing the count.
Now, the app works with one quirk - whenever we toggle the dark/light mode, the onOdd or onEven is being called. That's wrong, but understandable - we're creating new functions on each render, so useEffect() is being called.
I can think of 4 ways to fix this behavior:
Remove onOdd and onEven from useEffect() dependencies. It will fix the behavior, but it's considered a problem. The linter would complain about it, as we're losing data integrity. In theory, if we really change these callbacks, they should be re-run, right? That would be "the React way".
Move the callback functions outside of the App component:
const onOdd = count => console.log(`Odd count: ${count}`);
const onEven = count => console.log(`Even count: ${count}`);
const App = () => {
// ...
return (
// ...
<Counter
onOdd={onOdd}
onEven={onEven}
/>
// ...
);
}
This is a good and fast solution, but it's only possible because we don't use hooks or state inside these callbacks. What if we did?
Using useCallback() in App component:
const App = () => {
// ...
const onOdd = useCallback(
count => console.log(`Odd count: ${count}`),
[]
);
const onEven = useCallback(
count => console.log(`Even count: ${count}`),
[]
);
return (
// ...
<Counter
onOdd={onOdd}
onEven={onEven}
/>
// ...
);
}
Memoizing the callback functions in Counter component. If we had thousands of components using Counter component, it would still mean only one place to memoize these functions. I'm not sure if that makes sense though.
How do React gurus approach this problem? I wanted to keep the example as simple as possible, so option #2 will work perfectly and would probably be preferable. But what if we needed to keep these callbacks inside the App component?
Is it always the parent component responsible to memoize all callbacks it passes to the child? If so, is it a recognized pattern to always memoize all functions passed as props (and perhaps any other objects) with useCallback() or useMemo()?
I'm not properly a React Guru, but I consider all first three approaches to have their sweet spot, the 4th does not make sense. The only one to be careful with is the first one, since removing functions from deps, might lead to stale state issues, so if you know what you are doing, you may suppress lint warn ( I do that sometimes and know many others do that as it has been discussed extensiveley here https://github.com/facebook/react/issues/14920 ), otherwise it's better you avoid this approach.
The point number 2 is preferred everytime you have pure functions, always try to place your pure functions out of React components, inside some other folder like utils, misc, etc...
As per point number 3 that's the preferred way to handle functions declared inside React components, always memoize them with *useCallback* ( or useMemo if you need to perform calculations before to return a function ) , and there's nothing bad with doing that in the parent component. If you find yourself having dozens or hundreds of them and fear code pollution, consider that custom hooks let you to organize your code smartly, you could make a custom hook like useMemoizedHandlers inside your App component, where you create and memoize all your handlers and use it like:
const {
handler1,
handler2,
handler3
} = useMemoizedHandlers()
Options 2 and 3 are both absolutely valid and common, used interchangeably depending on whether the function has render cycle dependencies. Option 1 is a big no no. Option 4 is not really memoization at all - you can create stable references from functions passed as props but you cannot memoize the functions themselves as they've already been created anew.
Is it always the parent component responsible to memoize all callbacks it passes to the child?
In an application context I would say yes as this is the only way to enable React.memo on the consuming component's props. However, libraries will often convert functions to stable refs in the child, in case users forget to memoize themselves (or just as improved DX). Again, this is not the same as memoization, but it does mean that you can avoid the dependency issues highlighted in your question.
Is it a recognized pattern to always memoize all functions passed as props (and perhaps any other objects) with useCallback() or useMemo()?
You will find both memoization maxis and minimalists in the React community so it's hard to say that there's an accepted standard. Generally, you can get away with not doing it until you need it, like your example. However, purely from personal experience, once you do it a few times out of necessity it starts to become a habit as it reduces the possibility that bugs like this can occur.

Children useCallback dependency hell

From what I understand you use useCallback to prevent rerendering so I've been using it in every function and my spider senses are telling me it already sounds bad.
But the story doesn't ends there, since I've been using it everywhere I'm now passing dependencies to all my child components that they shouldn't need to worry about like in the following example :
EDIT // SANDBOX: https://codesandbox.io/s/bold-noether-0wdnp?file=/src/App.js
Parent component (needs colorButtons and currentColor)
const ColorPicker = ({onChange}) => {
const [currentColor, setCurrentColor] = useState({r: 255, g:0, b: 0})
const [colorButtons, setColorButtons] = useState({0: null})
const handleColorButtons = useCallback((isToggled, id) => {
/* code that uses colorButtons and currentColor */
}, [colorButtons, currentColor])
return <div className="color-picker">
<RgbColorPicker color={currentColor} onChange={setCurrentColor} />
<div className="color-buttons">
{
Object.entries(colorButtons).map(button => <ColorButton
//...
currentColor={currentColor}
onClick={handleColorButtons}
colorButtons={colorButtons}
/>)
}
</div>
</div>
}
1st child (needs style and currentColor but gets colorButtons for free from its parent)
const ColorButton = ({currentColor, onClick, id, colorButtons}) => {
const [style, setStyle] = useState({})
const handleClick = useCallback((isToggled) => {
/* code that uses setStyle and currentColor */
}, [style, currentColor, colorButtons])
return <ToggleButton
//...
onClick={handleClick}
style={style}
dependency1={style}
dependency2={currentColor}
dependency3={colorButtons}
>
</ToggleButton>
}
2nd child (only needs its own variables but gets the whole package)
const ToggleButton = ({children, className, onClick, style, data, id, onRef, ...dependencies}) => {
const [isToggled, setIsToggled] = useState(false)
const [buttonStyle, setButtonStyle] = useState(style)
const handleClick = useCallback(() => {
/* code that uses isToggled, data, id and setButtonStyle */
}, [isToggled, data, id, ...Object.values(dependencies)])
return <button
className={className || "toggle-button"}
onClick={handleClick}
style={buttonStyle || {}}
ref={onRef}
>
{children}
</button>
}
Am I doing an anti-pattern and if so, what is it and how to fix it ? Thanks for helping !
React hook useCallback
useCallback is a hook that can be used in functional React components. A functional component is a function that returns a React component and that runs on every render, which means that everything defined in its body get new referential identities every time. An exception to this can be accomplished with React hooks which may be used inside functional components to interconnect different renders and maintain state. This means that if you save a reference to a regular function defined in a functional component using a ref, and then compare it to the same function in a later render, they will not be the same (the function changes referential identity between renderings):
// Render 1
...
const fnInBody = () => {}
const fn = useRef(null)
console.log(fn.current === fnInBody) // false since fn.current is null
fn.current = fnInBody
...
// Render 2
...
const fnInBody = () => {}
const fn = useRef(null)
console.log(fn.current === fnInBody) // false due to different identity
fn.current = fnInBody
...
As per the docs, useCallback returns "a memoized version of the callback that only changes if one of the dependencies has changed" which is useful "when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders".
To sum up, useCallback will return a function that maintains its referential identity (e.g. is memoized) as long as the dependencies don't change. The returned function contains a closure with the used dependencies and must thus be updated once the dependencies change.
This results in this updated version of the previous example
// Render 1
...
const fnInBody = useCallback(() => {}, [])
const fn = useRef(null)
console.log(fn.current === fnInBody) // false since fn.current is null
fn.current = fnInBody
...
// Render 2
...
const fnInBody = useCallback(() => {}, [])
const fn = useRef(null)
console.log(fn.current === fnInBody) // true
fn.current = fnInBody
...
Your use case
Keeping the above description in mind, let's have a look at your use of useCallback.
Case 1: ColorPicker
const handleColorButtons = useCallback((isToggled, id) => {
/* code that uses colorButtons and currentColor */
}, [colorButtons, currentColor])
This function will get a new identity every time colorButtons or currentColor changes. ColorPicker itself rerenders either when one of these two are set or when its prop onChange changes. Both handleColorButtons and the children should be updated when currentColor or colorButtons change. The only time the children benefit from the use of useCallback is when only onChange changes. Given that ColorButton is a lightweight component, and that ColorPicker rerenders mostly due to changes to currentColor and colorButtons, the use of useCallback here seems redundant.
Case 2: ColorButton
const handleClick = useCallback((isToggled) => {
/* code that uses setStyle and currentColor */
}, [style, currentColor, colorButtons])
This is a situation similar to the first case. ColorButton rerenders when currentColor, onClick, id or colorButtons change and the children rerender when handleClick, style, colorButtons or currentColor change. With useCallback in place, the props id and onClick may change without rerendering the children (according to the above visible code at least), all other rerenders of ColorButton will lead to its children rerendering. Again, the child ToggleButton is lightweight and id or onClick are not likely to change more often than any other prop so the use of useCallback seems redundant here as well.
Case 3: ToggleButton
const handleClick = useCallback(() => {
/* code that uses isToggled, data, id and setButtonStyle */
}, [isToggled, data, id, ...Object.values(dependencies)])
This case is elaborate with a lot of dependencies but from what I see, one way or the other, most of the component props will lead to a "new version" of handleClick and with the children being lightweight components, the argument to use useCallback seems weak.
So when should I use useCallback?
As the docs say, use it in the very specific cases when you need a function to have referential equality between renders ...
You have a component with a subset of children that are expensive to rerender and that should rerender much less often than the parent component but rerender due to a function prop changing identity whenever the parent rerenders. To me, this use case also signals bad design and I would attempt to divide the parent component into smaller components but what do I know, maybe this is not always possible.
You have a function in the body of the functional component which is used in another hook (listed as a dependency) which is triggered every time due to the function changing identity whenever the component rerenders. Typically, you can omit such a function from the dependency array by ignoring the lint rule even if this is not by the book. Other suggestions are to place such a function outside the body of the component or inside the hook that uses it, but there might be scenarios where none of this works out as intended.
Good to know connected to this is ...
A function living outside a functional component will always have referential equality between renders.
The setters returned by useState will always have referential equality between renders.
I said in the comments that you can use useCallback when there is function doing expensive calculations in a component that rerenders often but I was a bit off there. Let's say you have a function that does heavy calculations based on some prop that changes less often than a component rerenders. Then you COULD use useCallback and run a function inside it that returns a function with a closure with some computed value
const fn = useCallback(
(
() => {
const a = ... // heavy calculation based on prop c
const b = ... // heavy calculation based on prop c
return () => { console.log(a + b) }
}
)()
, [c])
...
/* fn is used for something, either as a prop OR for something else */
This would effectively avoid calculating a and b every time the component rerenders without c changing, but the more straightforward way to do this would be to instead
const a = useMemo(() => /* calculate and return a */, [c])
const b = useMemo(() => /* calculate and return b */, [c])
const fn = () => console.log(a + b)
so here the use of useCallback just complicates things in a bad way.
Conclusion
It's good to understand more complicated concepts in programming and to be able to use them, but part of the virtue is also to know when to use them. Adding code, and especially code that involves complicated concepts, comes at the price of reduced readability and code that is harder to debug with a lot of different mechanisms that interplay. Therefore, make sure you understand the hooks, but always try to not use them if you can. Especially useCallback, useMemo and React.memo (not a hook but a similar optimization) should, in my opinion, only be introduced when they are absolutely needed. useRef has its very own use cases but should also not be introduced when you can solve your problem without it.
Good work on the sandbox! Always makes it easier to reason about code. I took the liberty of forking your sandbox and refactoring it a bit: sandbox link. You can study the changes yourself if you want. Here is a summary:
It's good that you know and use useRef and useCallback but I was able to remove all the uses, making the code much easier to understand (not only by removing these uses but by also removing the contexts where they are used).
Try to work with React to simplify things. I know this is not a hands-on suggestion but the more you get into React, the more you will realize that you can do things co-operating with React, or you can do things your own way. Both will work but the latter will result in more headache for you and everybody else.
Try to isolate the scope of a component; only delegate data that is necessary to child components and constantly question where you keep your state. Earlier you had click handlers in all three components and the flow was so complicated I didn't even bother to fully understand it. In my version, there is just one click handler in ColorPicker that is being delegated down. The buttons don't have to know what happens when you click them as long as the click handler takes care of that. Closures and the ability to pass functions as arguments are strong advantages of React and Javascript.
Keys are important in React and it's good to see that you use them. Typically, the key should correspond to something that uniquely identifies a specific item. Good to use here would be ${r}_${g}_${b} but then we would only be able to have one sample of each color in the button array. This is a natural limitation but if we don't want it, the only way to assign keys is to assign a unique identifier, which you did. I prefer using Date.now() but some would probably advise against it for some reason. You could use a global variable outside the functional component too if you don't want to use a ref.
Try to do things the functional (immutable) way, and not the "old" Javascript way. For example, when adding to an array, use [...oldArray, newValue] and when assigning to an object, use {...oldObject, newKey: newValue }.
There are more things to say but I think it's better for you to study the refactored version and you can let me know if you wonder about anything.

Function inside functional component in React hooks - Performance

Need suggestion on having function within a functional component in react Hooks.
As far as I researched, many are saying it is bad practice
because it creates nested/inner function every time we call re-render.
After doing some analysis,
I found we can use onClick={handleClick.bind(null, props)} on the element and place the function outside the functional component.
Example:
const HelloWorld = () => {
function handleClick = (event) => {
console.log(event.target.value);
}
return() {
<>
<input type="text" onChange={handleClick}/>
</>
}
}
Please advise if there is any alternative way.
Thanks in advance.
Don't worry about it
Don't worry about creating new functions on each render. Only in edge cases does that impede your performance.
Setting onClick handlers are not one of those, so just create a new function on each render.
However, when you need to make sure you use the same function every time, you can use useCallback
Why not use useCallback for onClick
Here is a reason why you shouldn't bother with useCallback for onClick handlers (and most other event handlers).
Consider the following code snippets, one without useCallback:
function Comp(props) {
return <button onClick={() => console.log("clicked", props.foo)}>Text</Button>
}
and one with useCallback:
function Comp(props) {
const onClick = useCallback(() => {
console.log("clicked", props.foo)
}, [props.foo])
return <button onClick={onClick}>Text</Button>
}
The only difference in the latter is that React doen's have
to change the onClick on your button if props.foo remains the same.
Changing the callback is a very cheap operation, and it's not at all
worth complicating your code for the theoretical performance improvement it gives.
Also, it's worth noting that a new function is still created on every render
even when you use useCallback, but useCallback will return the old one
as long as the dependencies passed as the second argument are unchanged.
Why ever use useCallback
The point of using useCallback is that if you compare two functions with reference
equality, fn === fn2 is true only if fn and fn2 point to the same function in memory.
It doesn't matter if the functions do the same.
Thus, if you have memoisation or otherwise only run code when the function changes,
it can be useful to use useCallback to use the same function again.
As an example, React hooks compare old and new dependencies, probably using Object.is.
Another example is React.PureComponent, which will only re-render when props or state have changed. This can be useful for components that use a lot of resources to render. Passing e.g. a new onClick to a PureComponent on each render will cause it to re-render every time.
many are saying it is bad practice because it creates nested/inner function every time we call re-render
No, inner functions / closures are so common, there is no problem with them. The engine can heavily optimize those.
The point here is that you pass the function as a prop to the child component. And as the function was "recreated", it does not equal the previous function passed, annd thus the child does rerender (and that's whats bad for performance).
You can resolve that with useCallback, which memoizes the function reference.
Interesting question, me and my coleagues had some worries about this, so I did a test.
I have created 1 Component with Hooks and 1 Component with Class, put some functions there and then render it 1000x times.
The Component with Class looks like this:
export class ComponentClass extends React.PureComponent {
click1 = () => {
return console.log("just a log");
};
render() {
return (
<>
<span onClick={this.click1}>1</span>
</>
);
}
}
The Component with Hooks looks like this:
export const ComponentHook = React.memo((props) => {
const click1 = () => {
return console.log("just a log");
};
return (
<>
<span onClick={click1}>1</span>
</>
);
});
I have added more click handlers to the components and then rendered them some 1000s times, the Class is faster as it does not define the functions each render, if you increase the number of functions defined, then the difference will be bigger:
Here it is a codesandbox so you can test the performance Class vs Hooks : https://codesandbox.io/s/hooks-vs-class-forked-erdpb
useCallback
You can use useCallback feature :
const HelloWorld = ({ dispatch }) => {
const handleClick = useCallback((event) => {
dispatch(() => {console.log(event.target.value)});
})
return() {
<>
<input type="name" onChange={handleClick}/>
</>
}
}
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).
For further details visit react documentation reference: React useCallback
Old Solution
First solution:
To pass the your handleClick function to your functional component.
const HelloWorld = (props) => {
return() {
<>
<input type="name" onChange={props.handleClick}/>
</>
}
}
Second solution:
To define your function outside of your functional component.
Inspired by #tibbus 's benchmark, I made this one that tests the perfomance using or not the useCallback hook. After several executions, it seams that the use of useCallback can be very important for high frequency rendering.
Execution 1
Execution 2
Execution 3
https://codesandbox.io/s/usecallback-vs-raw-definition-xke9v?file=/src/App.js
As per React Documentation (ending part),
The problem with latter syntax is that a different callback is created
each time the LoggingButton renders. In most cases, this is fine.
However, if this callback is passed as a prop to lower components,
those components might do an extra re-rendering. We generally
recommend binding in the constructor or using the class fields syntax,
to avoid this sort of performance problem.
Class field syntax:
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick.
// Warning: this is *experimental* syntax.
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
arrow function in the callback syntax:
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// This syntax ensures `this` is bound within handleClick
return (
<button onClick={() => this.handleClick()}>
Click me
</button>
);
}
}
I would honestly just use a class component in these cases. I'm aware of premature optimization, but creating a new function each time does just seem like extravagant wastefulness without much of a maintainability upside. tibbus has demonstrated the perf hit, and inline functions are arguably less readable than class methods. All you're losing out is the slick feeling of writing a functional component.
Just useCallback
Why would you need to define a function inside a component and not anywhere else? Because you either have to pass it to another child compononent, or you have to use it in an effect, memo, or another callback. For any of those cases if you dont wrap your function in useCallback you will be passing a new function and causing the component to rerender, the memo to re-run, the effect to re-run, or the callback to re-define.
You can never avoid the performance hit of redefining the function itself, but you can avoid the performance hit of performing any computation that has that function as a dependency to know if it has to run or not (be it a component or hook).
So... just wrap every function in your component in useCallback and forget about it, never seen a single in case in which this would cause any harm. If you can define the function outside the component, thats always better.

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