mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
93.26k stars 32.12k forks source link

Material UI 6 CssVarsProvider causes Suspense to reload async components #43263

Open awinogrodzki opened 1 month ago

awinogrodzki commented 1 month ago

Steps to reproduce

Link to live example: https://next-15-mui-6-suspense-flicker.vercel.app/

Steps:

  1. Open the link
  2. Refresh the page
  3. See Suspense boundary to re-render async loaded component

https://github.com/user-attachments/assets/ef7ba60e-3888-4010-b0b0-527c2a3567d1

This behaviour is a regression – using Material 5.16.7 causes Suspense to work as expected:

https://github.com/user-attachments/assets/0aea80ab-b7eb-4893-90cc-6cd12fda7fc0

Link to repo with Material 6 reproduction: https://github.com/awinogrodzki/next-15-mui-6-suspense-flicker

Link to branch with working Material 5 example: https://github.com/awinogrodzki/next-15-mui-6-suspense-flicker/tree/material-5

Current behavior

On first render, async components inside Suspense boundary are reloaded and re-rendered

Expected behavior

Should work like in Material 5 –  async component tree should not be re-mounted on first render

Context

I am working on a https://ensite.in, which is a no-code website builder. Due to large amount of building-blocks user can choose from, the whole app renders as an async-component tree

Your environment

npx @mui/envinfo ``` System: OS: macOS 14.2.1 Binaries: Node: 20.12.0 - ~/.nvm/versions/node/v20.12.0/bin/node npm: 10.5.0 - ~/.nvm/versions/node/v20.12.0/bin/npm pnpm: Not Found Browsers: Chrome: 127.0.6533.100 Edge: Not Found Safari: 17.2.1 npmPackages: @emotion/react: ^11.13.0 => 11.13.0 @emotion/styled: ^11.13.0 => 11.13.0 @mui/core-downloads-tracker: 6.0.0-dev.240424162023-9968b4889d @mui/material: ^6.0.0-beta.5 => 6.0.0-beta.5 @mui/material-nextjs: ^6.0.0-beta.4 => 6.0.0-beta.4 @mui/private-theming: 6.0.0-beta.5 @mui/styled-engine: 6.0.0-beta.5 @mui/system: 6.0.0-beta.5 @mui/types: 7.2.15 @mui/utils: 6.0.0-beta.5 @types/react: ^18 => 18.3.3 react: 19.0.0-rc-f994737d14-20240522 => 19.0.0-rc-f994737d14-20240522 react-dom: 19.0.0-rc-f994737d14-20240522 => 19.0.0-rc-f994737d14-20240522 typescript: ^5 => 5.5.4 ```

Search keywords: material 6 suspense css vars cssvarsprovider

siriwatknp commented 4 weeks ago

@awinogrodzki in v6, the CssVarsProvider contains a rerender (not a remount) after hydration so that the mode is updated on the client. It's strange that a rerender will cause a Suspense to display a fallback. Is this the expected behavior?

awinogrodzki commented 4 weeks ago

Good point @siriwatknp. I think that the remount comes from a root async component after mode re-render in CssVarsProvider. Let me run some more experiments to be sure and I'll get back to you

awinogrodzki commented 4 weeks ago

@siriwatknp, after running different experiments I can confirm that no remount happens. I think I must've observed it when isolated the case in the real app. I don't think it's related to this issue however, so let's just ignore it.

I still think the issue lies somewhere inside CssVarsProvider. Here's some experiments that I've performed:

Recordings of the production build

Material UI 6

Code for layout: https://github.com/awinogrodzki/next-15-mui-6-suspense-flicker/blob/main/app/layout.tsx

You can notice that it's basic layout created using create-next-app updated with InitColorSchemeScript, CssVarsProvider and AppRouterCacheProvider, according to the docs

Code for theme: https://github.com/awinogrodzki/next-15-mui-6-suspense-flicker/blob/main/app/theme.ts

It's just a basic theme with custom selector

Light mode

https://github.com/user-attachments/assets/71410df2-f3e1-453d-820a-5142ab2850e2

Dark mode

https://github.com/user-attachments/assets/e0e5877e-923b-4d8f-933a-6ea352a680a8

Material UI 5

Code for layout: https://github.com/awinogrodzki/next-15-mui-6-suspense-flicker/blob/material-5/app/layout.tsx Code for theme: https://github.com/awinogrodzki/next-15-mui-6-suspense-flicker/blob/material-5/app/theme.ts

Light mode

https://github.com/user-attachments/assets/420a7832-ab08-4d9f-ac45-cd6b65946a1b

Dark mode

https://github.com/user-attachments/assets/c10a0561-f349-4057-88de-7f07fecd66b6

Control

I also tested the behavior with MUI6, but without CssVarProvider in layout.tsx:

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={`${geistSans.variable} ${geistMono.variable}`}>
        <AppRouterCacheProvider>
          <InitColorSchemeScript  attribute="[data-scheme-%s]" />
          {children}
        </AppRouterCacheProvider>
      </body>
    </html>
  );
}

It also does work as expected, which points that the problem is somewhere inside CssVarsProvider

Light mode

https://github.com/user-attachments/assets/c9174e33-a86e-4195-8b93-1530ab2d4744

Dark mode

https://github.com/user-attachments/assets/46910d50-441e-4a58-a0ef-e2c2a929445c

awinogrodzki commented 6 days ago

Hey @siriwatknp,

Are there any updates on the issue?

I have updated Material UI to the latest version – 6.0.2, and the issue still exists.

I have also replaced CssVarsProvider and extendTheme with recommended ThemeProvider + createTheme – the issue is still reproducible

Is there anything I could do to help?

awinogrodzki commented 6 days ago

I've cloned material-ui repository to debug the issue and it seems that everything starts working properly when I comment this line:

https://github.com/mui/material-ui/blob/master/packages/mui-system/src/cssVars/useCurrentColorScheme.ts#L163

Is this state update necessary?

With this line commented system, light and dark mode seems to be working correctly and Suspense also works as expected.

Maybe you could at least provide a opt-out from this behaviour?