简体   繁体   中英

React hooks useCallback with parameters inside loop

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM