0

I have a React version 17 app with a series of nested <Suspense> components. Each one uses the same <Loader> component, which contains a CSS-animated spinner:

<App>
  <Suspense fallback={<Loader/>}>
    <OuterWrapper>
      <Suspense fallback={<Loader/>}>
        <InnerWrapper>
          {content}
        </InnerWrapper>
      </Suspense>
    </OuterWrapper>
  </Suspense>
</App>

When the outer <Suspense> component finishes loading and swaps <Loader> for <OuterWrapper>, and then begins loading the inner <Suspense> component, a new instance of the <Loader> component is loaded in the DOM. Because of this, the animation restarts at the beginning of its animation cycle, which makes the spinner appear to "stutter".

I have tried wrapping <Loader> in React.memo(), but this does not help because the same instance of the <Loader> component is not actually being re-rendered, but rather is being replaced in the DOM with a brand new instance by the inner <Suspense> component. I think I need the same instance to remain rendered in the DOM so that the animation looks smooth.

What is a good way to solve for this problem? I know the experimental concurrent mode features of React version 18 can help with this, but I am unable to use alpha/beta versions of React in this app.

MattSidor
  • 2,599
  • 4
  • 21
  • 32

1 Answers1

0

I don't think there's a good way to solve this within <Suspense> on React 17, so I moved the <Loader> component into its own provider that wraps everything else. It uses React Context to swap out the loading animation for the app content once the last nested component has finished loading.

The fallback props for <Suspense> are changed to null in this scheme.

<LoaderProvider loader={<Loader />}>
  <App>
    <Suspense fallback={null}>
      <OuterWrapper>
        <Suspense fallback={null}>
          <InnerWrapper>
            {content}
          </InnerWrapper>
        </Suspense>
      </OuterWrapper>
    </Suspense>
  </App>
</LoaderProvider>
MattSidor
  • 2,599
  • 4
  • 21
  • 32
  • How is `LoaderProvider` informed about when the app has rendered? Do you provide a custom context that is used by `OuterWrapper` and `InnerWrapper`, or have you found a way to access the suspense state directly inside `LoaderProvider`? – cdauth Sep 29 '22 at 12:22
  • Thanks for your question. I am setting a boolean variable called 'isLoaded' via ``'s context. When everything has finished loading, I set it to `true`, which swaps out the loading animation for the child components in the provider. – MattSidor Oct 04 '22 at 20:10