Skip to content

[Compiler Bug]: False positives “calling setState synchronously within an effect body” with stable primitive values #34045

@controversial

Description

@controversial

What kind of issue is this?

  • React Compiler core (the JS output is incorrect, or your app works incorrectly after optimization)
  • babel-plugin-react-compiler (build issue installing or using the Babel plugin)
  • eslint-plugin-react-compiler (build issue installing or using the eslint plugin)
  • react-compiler-healthcheck (build issue installing or using the healthcheck script)

Link to repro

https://playground.react.dev/#N4Igzg9grgTgxgUxALhAgHgBwjALgAgBMEAzAQygBsCSoA7OXASwjvwFkBPAQU0wAoAlPmAAdNvjiswBANowEZQpwA0+MAlwAlRcoC6+ALz4oGgMq4yuBP3KUNggNzj8JjQFESJBI35CjAHwiLq74APRhRBD4ABYICuoQALaaMUx0AOb4uDFWsWRg2dG5fAhsrNlxkpRMZQT8dBAE6fhmZlqCIa4R+AB0-V3qmjpKnPy4MFAITiEAvmqyejN0s+Igs0A

Repro steps

Observe react compiler lint error

I think it should be possible to set state in an effect under the condition that:

  • The state is being set to a primitive value, so it will not cause cascading renders if it happens multiple times because the state value will be equal.
  • The state variable being set isn’t in the dependency array of the effect, so it won’t cause cascading renders.

This is a common pattern, even in “synchronization”-style uses of effects (i.e. uses that comply with the standards in “you might not need an effect”). To give a real-world example, consider an effect that sets up an IntersectionObserver, but also wants to set a reasonable “initial” value before the first time the IntersectionObserver fires:

  useEffect(() => {
    if (!ref.current) return undefined;
    const rect = ref.current.getBoundingClientRect();
    const isIntersecting = rect.top < window.innerHeight && rect.bottom > 0;
    setVisible(isIntersecting);
    const io = new IntersectionObserver(([entry]) => {
      if (!entry) return;
      setVisible(entry.isIntersecting);
    });
    io.observe(ref.current);
    return () => { io.disconnect(); };
  }, []);

Here, setVisible uses a primitive boolean value, so multiple calls to setVisible(true) won’t cause cascading re-renders. Additionally, the effect doesn’t fire in response to changes to setVisible. Finally, this effect usage seems squarely within the intended “synchronization with an external system” (IntersectionObserver) usage of an effect.

How often does this bug happen?

Every time

What version of React are you using?

[email protected]

What version of React Compiler are you using?

[email protected]

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions