question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[eslint-plugin-react-hooks] setState in effect guard prevents cases like DOM measurement

See original GitHub issue

Do you want to request a feature or report a bug?

Bug

What is the current behavior?

The new guard against a direct call to setState inside of an effect (https://github.com/facebook/react/pull/15184) seems to prevent a class of patterns where the value being set is dependent on something other than props. For example, the rule disallows storing a value read from the DOM via a ref (see below).

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn’t have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:

function MeasuredButton(props) {
  const buttonRef = useRef(null)
  const [buttonWidth, setButtonWidth] = useState(0)

  useLayoutEffect(() => {
    if (buttonRef.current) {
      // we rely on the same value bailout to avoid an infinite loop
      setButtonWidth(buttonRef.current.clientWidth)
      // we could bail out explicitly instead:
      // const {clientWidth} = buttonRef.current
      // if (clientWidth !== buttonWidth) setButtonWidth(clientWidth)
      // but the linter would still disallow it
    }
  })

  return (
    <>
      <button ref={buttonRef}>{props.children}</button>
      Button width: {buttonWidth}
    </>
  )
}

This code yields the error:

React Hook useLayoutEffect contains a call to ‘setButtonWidth’. Without a list of dependencies, this can lead to an infinite chain of updates. To fix this, pass [] as a second argument to the useLayoutEffect Hook.

The auto-fix breaks the component because the width no longer updates on subsequent renders.

What is the expected behavior?

Basically, the guard assumes that the infinite loop problem can always be solved by adding a dependency array. This is true when setting a value derived from props (such as data returned from a request based on a prop), but not when the source of the value can only be retrieved inside of the effect (such as a DOM measurement). In the latter case, an infinite loop has to be avoided by adding a condition or relying on the same value bailout.

Is this known and/or intentional? I notice that #15184 considered early returns, which would help.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

React: 16.8.6 eslint-plugin-react-hooks: 1.6.0

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:6 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
eps1loncommented, Apr 4, 2019

This can still cause infinite loops if the width of the <button /> depends on buttonwidth. It would create a size explosion if you set the width of the button to buttonWidth + 20 and then the layout reads a different value, updates and then the cycle starts again. There’s no way that a linter can verify this statically.

The other issue is if you add other hooks that change the size of the button during render.

If you think it’s safe you can eslint-ignore it. Otherwise you should probably use ResizeObserver or save the latest measurement in a ref and read from that when necessary.

0reactions
stale[bot]commented, Jan 17, 2020

Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Hooks API Reference
React guarantees that setState function identity is stable and won't change on re-renders. This is why it's safe to omit from the useEffect...
Read more >
React Hooks cheat sheet: Best practices with examples
Declaring a state variable is as simple as calling useState with some initial state value, like so: useState(initialStateValue) . const ...
Read more >
Early return issue with React Hooks
I decided to install eslint-plugin-react-hooks to prevent similar cases in the future. Now it warns with a React Hook "useState" is called ...
Read more >
How to useMemo and useCallback: you can remove most ...
There are two major sources of the poisonous spread of those hooks in the app: memoizing props to prevent re-renders; memoizing values to...
Read more >
React hooks gotchas: setState in async useEffect
Today, I'd like to share a new discovery because it is very far from obvious − to say the least! So you think...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found