how does this fix the state closure problem with react hooks?

后端 未结 1 699
挽巷
挽巷 2020-12-02 02:43

I am looking at the code in formik that apparently is a way around the stale closure problem with react hooks.

function useEventCallback

        
相关标签:
1条回答
  • 2020-12-02 03:38

    The documentation actually states:

    In either case, we don’t recommend this pattern and only show it here for completeness. Instead, it is preferable to avoid passing callbacks deep down.

    Let's say we can't avoid passing callbacks then the simplest way would be to use a callback for the state setter: setSomeState(currentState=>....return something based on current state)

    I'm not sure how this would behave when concurrent mode is released but here is an example of how you can use a callback to the state setter:

    const ParentContainer = () => {
      //list is created and maintained in parent
      const [list, setList] = React.useState([
        { id: 1, val: true },
        { id: 2, val: true },
      ]);
      //simplest way to get current list is to pass a callback
      //  to the state setter, now we can use useCallback without
      //  dependencies and never re create toggle during this life cycle
      const toggle = React.useCallback(
        id =>
          setList(list =>
            list.map(item =>
              item.id === id
                ? { ...item, val: !item.val }
                : item
            )
          ),
        []
      );
      return Parent({ list, toggle });
    };
    const Parent = ({ list, toggle }) => (
      <div>
        {list.map(item => (
          <ItemContainer
            key={item.id}
            item={item}
            //every item gets the same toggle function
            //  reference to toggle never changes during Parent life cycle
            toggle={toggle}
          />
        ))}
      </div>
    );
    //Added memo to make ItemContainer a pure component
    //  as long as item or toggle never changes the (render) function
    //  will not be executed
    //  normally a pure component should not have side effects so don't
    //  do side effects in pure compnents (like mutating rendered var)
    //  it is only to visibly display how many times this function was
    //  called
    const ItemContainer = React.memo(function ItemContainer({
      item,
      toggle: parentToggle,
    }) {
      const rendered = React.useRef(0);
      //toggling item with id 1 will not increase render for
      //  other items (in this case item with id 2)
      //  this is because this is a pure component and this code
      //  will not be executed due to the fact that toggle or item
      //  never changed for item 2 when item 1 changed
      rendered.current++;
      const toggle = React.useCallback(
        () => parentToggle(item.id),
        [item.id, parentToggle]
      );
      return Item({ toggle, item, rendered });
    });
    const Item = ({ toggle, item, rendered }) => (
      <div
        onClick={() => toggle(item.id)}
        style={{ cursor: 'pointer' }}
      >
        <div>{item.val ? '[X]' : '[-]'}</div>
        <div>times rendered:{rendered.current}</div>
      </div>
    );
    
    //render app
    ReactDOM.render(
      <ParentContainer />,
      document.getElementById('root')
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
    <div id="root"></div>

    0 讨论(0)
提交回复
热议问题