React: Suspense calls useEffect before render

Created on 27 Nov 2019  路  6Comments  路  Source: facebook/react

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

Bug

What is the current behavior?

The React docs say

useEffect lets us express different kinds of side effects after a component renders.

But under suspense that doesn't seem to be true any longer. useEffect is called pre-emptively. Or at least "rendering" seems to refer to the double-buffer (?)

There are situations where side-effects rely on the element having been rendered out effectively and visually.

Demo

In my case i need to render content on the screen that's supposed to follow along the parents whereabouts.

https://codesandbox.io/s/mystifying-wilson-30u2m

useEffect (belonging to the <Dom/> component) dumps out content before the parent has rendered, which is very odd since the parent is suspended until all the other components are done loading. This is mixed mode react, two reconcilers. But that shouldn't have anything to do with it. The culprit is useEffect.

What is the expected behavior?

useEffect should fire after the component has effectively rendered. I think that is what most people thought it would already do.

Which versions of React are affected by this issue?

16.12.0

Needs Investigation

Most helpful comment

This is a known issue. The problem is that legacy life-cycles and the correct behavior are in direct conflict. So we can't fix it and also preserve legacy life-cycles at the same time.

The fix is to switch to Blocking Mode or Concurrent Mode.

All 6 comments

Debugging deeper into how this works, it is clear to me now what happens. So essentially the component does render, but the reconciler calls hideInstance on it. When the suspense is lifted it calls unhideInstance.

The problem is that useEffect is let loose on an object that has no visual representation, which affects interop (enter state, animation, measuring, position, etc).

Can you make an example that doesn't mix two renderers? That would be a bit easier to follow.

It would also make it easier to check whether this happens in Concurrent Mode too. (It's expected that Suspense in legacy mode has some unfortunate unfixable quirks.)

Here's a plain react demo: https://codesandbox.io/s/distracted-goldstine-07bpl

You have to open codesandboxes console to see the ready logs, indicating that useEffect fires even though these components aren't ready yet.

I added react-spring for a simple fade-in on mount animation, something like this is pretty common. It falls flat inside suspense, the animation fires long before the component shows up and by the time it does show it's already at end-state.

function Suspend({ time }) {
  const [props, set] = useSpring(() => ({ opacity: 0 }))
  // This hook suspends the component for a while
  usePromise(ms => new Promise(res => setTimeout(res, ms)), [time])
  // useEffect should indicate that we are ready to roll ...
  useEffect(() => {
    // Component has mounted, start the animation ...
    // useEffect will be called regardless if this component is ready or not, hence
    //   animations or other interop actions relying on refs are without foundation.
    set({ opacity: 1 })
  }, [])
  // The view will be rendered out and silenced by the reconciler via display: none
  return <animated.div style={props}>Suspense: {time}ms</animated.div>
}

<Suspense fallback={<div>Fallback</div>}>
  <Suspend time={1000} />
  <Suspend time={2000} />
  <Suspend time={3000} />
  <div>No suspense</div>
</Suspense>

Tried the dom-only demo in concurrent mode, and it seems to behave correctly there. That already is a huge relief.

edit:

works in react-reconciler as well given that it receives "2" as the 2nd arg to createContainer. https://codesandbox.io/s/romantic-leaf-zu2wo

useLayoutEffect also appears to have the same issue

This is a known issue. The problem is that legacy life-cycles and the correct behavior are in direct conflict. So we can't fix it and also preserve legacy life-cycles at the same time.

The fix is to switch to Blocking Mode or Concurrent Mode.

Was this page helpful?
0 / 5 - 0 ratings