Home  >  Q&A  >  body text

Can I use React.useCallback inside another React.useCallback?

<p>有一个渲染用户卡片的组件</p> <p> <pre class="brush:js;toolbar:false;">import React from "react"; const User = React.memo(function({id, name, isSelected, ...other}) { return ( <div {...other}> {name} - {isSelected && "Selected"} </div> ); }); export default User;</pre> </p> <p>以及渲染用户卡的父组件</p> <p> <pre class="brush:js;toolbar:false;">import React from "react"; function Application() { const [users, setUsers] = React.useState([ {id: 1, name: "John Doe #1"}, {id: 2, name: "John Doe #2"}, {id: 3, name: "John Doe #3"} ]); const [selectedUserId, setSelectedUserId] = React.useState(null); return users.map((user) => { const isSelected = selectedUserId === user.id; return ( <User {...user} key={user.id} isSelected={isSelected} onClick={() => setSelectedUserId(user.id)} /> ); }); } export default Application;</pre> </p> <p>任务是“选择用户后避免重新渲染其他用户卡”</p> <p>我尝试使用 <code>React.useCallback</code> 钩子,这是我的第一个实现</p> <p> <pre class="brush:js;toolbar:false;">import React from "react"; const User = React.memo(function({id, name, isSelected, ...other}) { return ( <div {...other}> {name} - {isSelected && "Selected"} </div> ); }); function Application() { const [users, setUsers] = React.useState([ {id: 1, name: "John Doe #1"}, {id: 2, name: "John Doe #2"}, {id: 3, name: "John Doe #3"} ]); const [selectedUserId, setSelectedUserId] = React.useState(null); const handleSelectUser = React.useCallback((userId) => () => { setSelectedUserId(userId); }, []); return users.map((user) => { const isSelected = selectedUserId === user.id; return ( <User {...user} key={user.id} isSelected={isSelected} onClick={handleSelectUser(user.id)} /> ); }); } export default Application;</pre> </p> <p>In this case, <code>React.useCallback</code> returns an anonymous function</p> with a new reference <p><strong>Result: All user cards still re-render after each click</strong></p> <p>I decided to wrap this anonymous function in <code>React.useCallback</code></p> <p> <pre class="brush:js;toolbar:false;">import React from "react"; const User = React.memo(function({id, name, isSelected, ...other}) { return ( <div {...other}> {name} - {isSelected && "Selected"} </div> ); }); function Application() { const [users, setUsers] = React.useState([ {id: 1, name: "John Doe #1"}, {id: 2, name: "John Doe #2"}, {id: 3, name: "John Doe #3"} ]); const [selectedUserId, setSelectedUserId] = React.useState(null); const handleSelectUser = React.useCallback((userId) => { return React.useCallback(() => { setSelectedUserId(userId); }, []); }, []); return users.map((user) => { const isSelected = selectedUserId === user.id; return ( <User {...user} key={user.id} isSelected={isSelected} onClick={handleSelectUser(user.id)} /> ); }); } export default Application;</pre> </p> <p>The problem is solved, but there is still one question, did I do it right? The React team says: <em>Don't call Hooks inside loops, conditionals, or nested functions</em> What side effects will I get? </p> <p>Note do not touch the <code>user</code>component</p>
P粉153503989P粉153503989387 days ago519

reply all(1)I'll reply

  • P粉810050669

    P粉8100506692023-09-02 12:43:37

    Explain why a hook cannot be called from within a hook (and expect it to work consistently and reliably)

    Why you can't call a hook inside a hook - go here for a super deep dive with more context than I need to provide in this answer https://overreacted.io/why-do-hooks -rely-on-call-order/

    Your solution works because despite "breaking the rules" the order of calls to the hooks is always the same... until the user is added or removed from the state.

    You can definitely use the solution you wrote. But what if you need to change the number of users?


    No, you cannot use hooks inside hooks. It might "work", but React is telling you that it doesn't work reliably and that you're doing it wrong. The hook must be called at the top level of the custom hook's top-level component.

    Your direction is correct, but the solution to the problem is

    1. Use the parameters provided to the callback function as the source of the clicked element AND
    2. If you cannot modify the User component, you must provide some data on the div element to let you know which user element was clicked.

    It looks like this:

    function Application() {
      const [users, setUsers] = React.useState([
        { id: 1, name: 'John Doe #1' },
        { id: 2, name: 'John Doe #2' },
        { id: 3, name: 'John Doe #3' },
      ]);
      const [selectedUserId, setSelectedUserId] = React.useState(null);
    
      // this callback is referentially stable - it doesn't change between renders because it has no dependencies
      const handleSelectUser = React.useCallback((e) => {
        setSelectedUserId(+e.target.getAttribute('data-userid'));
      }, []);
    
      return users.map((user) => {
        const isSelected = selectedUserId === user.id;
    
        return (
          <User
            {...user}
            data-userid={user.id} <- this line
            key={user.id}
            isSelected={isSelected}
            onClick={handleSelectUser}
          />
        );
      });
    }

    reply
    0
  • Cancelreply