'Why can't `useCallback` always return the same ref
I don't understand why useCallback
always returns a new ref each time one of the deps is updated. It results in many re-render that React.memo()
could have avoided.
What is, if any, the problem with this implementation of useCallback
?
export function useCallback(callback) {
const callbackRef = useRef();
callbackRef.current = callback;
return useState(() =>
(...args) => callbackRef.current(...args)
)[0];
}
Using this instead of the built-in implementation sure has a significant positive impact on performance.
Own conclusion:
There is no reason not to use an implementation using ref over the built's in as long as you are aware of the implications, namely, as pointed out by @Bergy, you can't store a callback for use later (after a setTimeout
for example) and expect the callback to have the same effect as if you'd have called it synchronously.
In my opinion however this is the preferred behaviour so no downside 🥂.
Update:
There is a React RFC for introducing a builtin hook that does just that. It would be called useEvent
Solution 1:[1]
What is, if any, the problem with this implementation of
useCallback
?
I suspect it has unintended consequences when someone stores a reference to your callback for later, as it will change what it is doing:
const { Fragment, useCallback, useState } = React;
function App() {
const [value, setValue] = useState("");
const printer = useCallback(() => value, [value]);
return <div>
<input type="text" value={value} onChange={e => setValue(e.currentTarget.value)} />
<Example printer={printer} />
</div>
}
function Example({printer}) {
const [printerHistory, setHistory] = useState([]);
return <Fragment>
<ul>{
printerHistory.map(printer => <li>{printer()}</li>)
}</ul>
<button onClick={e => setHistory([...printerHistory, printer])}>Store</button>
</Fragment>
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<div id="root"></div>
(Sure, in this simplified demo the printer
callback is nothing but a useless closure over the value
itself, but you can imagine a more complex case where one could select an individual history entry and would want to use a complicated on-demand computation in the callback)
With the native useCallback
, the functions stored in the printerHistory
would be distinct closures over distinct values, while with your implementation they would all be the same function that refers to the latest useCallback
argument and only prints the current value on every call.
Solution 2:[2]
The use cases of useCallback and useMemo are different. You said that useCallback returns a memoized version of the callback that only changes if one of the dependencies has changed. As a result, it rerenders the components according to the change of dependencies. But useMemo only hold mutable value. Generally, we use useCallback for processing event handler. For example, let's assume that when you click the button, the count of button click is displayed. In this case, click event is implemented using useCallback.
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
console.log(count + 1);
}, [count]);
To get new increased count value and display its value whenever the button is clicked, it is needed to rerender. Say again, the use cases of useCallback and useMemo are depended on the purpose.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Patrick Roberts |
Solution 2 | Jhonatan |