I'm trying to learn about the hooks functionality, however I can't quite seem to figure out how I'm supposed to use the function useCallback
properly. As far as I understand from the rules about hooks I'm supposed to call them top-level and not within logic (such as loops). Which leads me to be quite confused how I'm supposed to implement useCallback
on components that are rendered from a loop.
Take a look at the following example snippet where I create 5 buttons with an onClick
handler that prints the number of the button to the console.
const Example = (props) => { const onClick = (key) => { console.log("You clicked: ", key); }; return( <div> { _.times(5, (key) => { return ( <button onClick={() => onClick(key)}> {key} </button> ); }) } </div> ); }; console.log("hello there"); ReactDOM.render(<Example/>, document.getElementById('root'));
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script> <div id='root'> </div>
Right now it creates a new function instance everytime the Example renders due to my use of the arrow function within the <button onClick={() => onClick(key)}>
, which I do because I want the onClick
function to know which button called it. I thought I could prevent this with the new react hooks functionality by using useCallback
, however if I apply it to the const onClick
then it would still create a new instance every render because of the inline arrow function needed to give the key
parameter, and I'm not allowed to apply it to the render within a loop as far as I know (especially if the loop order might change right?).
So how would I go about implementing useCallback
in this scenario while keeping the same functionality? Is it at all possible?
The simple answer here is, you probably shouldn't use useCallback
here. The point of useCallback
is to pass the same function instance to optimized components (eg PureComponent
or React.memo
ized components) to avoid unnecessary rerenders.
You're not dealing with optimized components in this case (or most cases, I'd suspect) so there's not really a reason to memoize callbacks like with useCallback
.
Supposing the memoization is important, though, the best solution here is probably to use a single function instead of five: instead of a unique function for each button carries the key
by closure, you can attach the key
to the element:
<button data-key={key}>{key}</button>
And then read the key from the event.target.dataset["key"]
inside a single click handler:
const Example = (props) => { // Single callback, shared by all buttons const onClick = React.useCallback((e) => { // Check which button was clicked const key = e.target.dataset["key"] console.log("You clicked: ", key); }, [/* dependencies */]); return( <div> { _.times(5, (key) => { return ( <button data-key={key} onClick={onClick}> {key} </button> ); }) } </div> ); }; console.log("hello there"); ReactDOM.render(<Example/>, document.getElementById('root'));
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script> <div id='root'> </div>
All that being said, it is possible to memoize multiple functions in a single hook. useCallback(fn, deps)
is equivalent to useMemo(() => fn, deps)
, and useMemo
can be used to memoize multiple functions at once:
const clickHandlers = useMemo(() => _.times(5, key =>
() => console.log("You clicked", key)
), [/* dependencies */]);
const Example = (props) => { const clickHandlers = React.useMemo(() => _.times(5, key => () => console.log("You clicked", key) ), [/* dependencies */]) return( <div> { _.times(5, (key) => { return ( <button onClick={clickHandlers[key]}> {key} </button> ); }) } </div> ); }; console.log("hello there"); ReactDOM.render(<Example/>, document.getElementById('root'));
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script> <div id='root'> </div>
Perhaps there's a case where that's useful, but in this case I would either leave it alone (and not worry about the optimization) or else use a single handler for each button.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.