Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
621 views
in Technique[技术] by (71.8m points)

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

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

function useEventCallback<T extends (...args: any[]) => any>(fn: T): T {
  const ref: any = React.useRef();

  // we copy a ref to the callback scoped to the current state/props on each render
  useIsomorphicLayoutEffect(() => {
    ref.current = fn;
  });

  return React.useCallback(
    (...args: any[]) => ref.current.apply(void 0, args),
    []
  ) as T;
}

I've seen this pattern a lot in other libs but I don't understand why this cures it.

I don't understand why creating a ref in a useEffect() cures anything.

Does it silence the linter?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...