Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Theme Mode] CssBaseline not working with Next.js App Router (with ThemeRegistry) #37935

Closed
2 tasks done
mtr1990 opened this issue Jul 12, 2023 · 4 comments · Fixed by #37962
Closed
2 tasks done

[Theme Mode] CssBaseline not working with Next.js App Router (with ThemeRegistry) #37935

mtr1990 opened this issue Jul 12, 2023 · 4 comments · Fixed by #37962
Assignees
Labels
bug 🐛 Something doesn't work component: CssBaseline The React component examples Relating to /examples package: system Specific to @mui/system

Comments

@mtr1990
Copy link

mtr1990 commented Jul 12, 2023

Duplicates

  • I have searched the existing issues

Latest version

  • I have tested the latest version

Steps to reproduce 🕹

Referenced from the example
https://mui.com/material-ui/guides/next-js-app-router/


Link to live example: https://codesandbox.io/p/sandbox/inspiring-bush-l4jqyv?file=%2Fapp%2FThemeRegistry.tsx%3A21%2C25-21%2C38

Steps:

  1. Dark mode settings
  2. Switch modes

mode

Current behavior 😯

body tag not apply change color and background-color

Expected behavior 🤔

body tag apply change color and background-color

Here is an example without ThemeRegistry https://codesandbox.io/p/sandbox/beautiful-carlos-6pn3k4?file=%2Fapp%2FThemeRegistry.tsx%3A76%2C17.

And it works.

expect CssBaseLine to work when used with ThemeRegistry

Context 🔦

No response

Your environment 🌎

No response

@mtr1990 mtr1990 added the status: waiting for maintainer These issues haven't been looked at yet by a maintainer label Jul 12, 2023
@zannager zannager added package: system Specific to @mui/system component: CssBaseline The React component labels Jul 12, 2023
@mj12albert mj12albert assigned mj12albert and unassigned siriwatknp Jul 12, 2023
@siriwatknp
Copy link
Member

siriwatknp commented Jul 14, 2023

I can make it work but without prepend: true

// ThemeRegistry.js
"use client";
import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";
import { useServerInsertedHTML } from "next/navigation";
import { useState } from "react";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import { useModeContext } from "@/app/context";

export default function RootStyleRegistry({ children }) {
  const [{ cache, flush }] = useState(() => {
    const cache = createCache({ key: "css" });
    cache.compat = true;
    const prevInsert = cache.insert;
    let inserted = [];
    cache.insert = (...args) => {
      const serialized = args[1];
      if (cache.inserted[serialized.name] === undefined) {
        inserted.push({ name: serialized.name, global: !args[0] });
      }
      return prevInsert(...args);
    };
    const flush = () => {
      const prevInserted = inserted;
      inserted = [];
      return prevInserted;
    };
    return { cache, flush };
  });

  useServerInsertedHTML(() => {
    const names = flush();
    if (names.length === 0) return null;

    const nonGlobalNames = [];
    const globalStyles = [];
    let styles = "";
    for (const { name, global } of names) {
      if (global) {
        globalStyles.push({ name, css: cache.inserted[name] });
      } else {
        nonGlobalNames.push(name);
        styles += cache.inserted[name];
      }
    }
    return [
      ...globalStyles.map((style, i) => (
        <style
          key={style.name}
          data-emotion={`${cache.key}-global`}
          dangerouslySetInnerHTML={{
            __html: style.css,
          }}
        />
      )),
      <style
        key="css"
        data-emotion={`${cache.key} ${nonGlobalNames.join(" ")}`}
        dangerouslySetInnerHTML={{
          __html: styles,
        }}
      />,
    ];
  });

  const { mode } = useModeContext();

  const theme = createTheme({
    palette: {
      mode: mode,
    },
  });

  return (
    <CacheProvider value={cache}>
      <ThemeProvider theme={theme}>
        <CssBaseline />
        {children}
      </ThemeProvider>
    </CacheProvider>
  );
}

@mj12albert mj12albert removed the status: waiting for maintainer These issues haven't been looked at yet by a maintainer label Jul 15, 2023
@siriwatknp
Copy link
Member

@mtr1990 This will be fixed by #37962. In the meanwhile, you can copy and paste this to EmotionCacheProvider directly (it is the same code):

'use client';

import * as React from 'react';
import createCache from '@emotion/cache';
import { useServerInsertedHTML } from 'next/navigation';
import { CacheProvider as DefaultCacheProvider } from '@emotion/react';
import type { EmotionCache, Options as OptionsOfCreateCache } from '@emotion/cache';

export type NextAppDirEmotionCacheProviderProps = {
  /** This is the options passed to createCache() from 'import createCache from "@emotion/cache"' */
  options: Omit<OptionsOfCreateCache, 'insertionPoint'>;
  /** By default <CacheProvider /> from 'import { CacheProvider } from "@emotion/react"' */
  CacheProvider?: (props: {
    value: EmotionCache;
    children: React.ReactNode;
  }) => React.JSX.Element | null;
  children: React.ReactNode;
};

// Adatped from https://github.com/garronej/tss-react/blob/main/src/next/appDir.tsx
export default function NextAppDirEmotionCacheProvider(props: NextAppDirEmotionCacheProviderProps) {
  const { options, CacheProvider = DefaultCacheProvider, children } = props;

  const [registry] = React.useState(() => {
    const cache = createCache(options);
    cache.compat = true;
    const prevInsert = cache.insert;
    let inserted: { name: string; isGlobal: boolean }[] = [];
    cache.insert = (...args) => {
      const [selector, serialized] = args;
      if (cache.inserted[serialized.name] === undefined) {
        inserted.push({
          name: serialized.name,
          isGlobal: selector === '',
        });
      }
      return prevInsert(...args);
    };
    const flush = () => {
      const prevInserted = inserted;
      inserted = [];
      return prevInserted;
    };
    return { cache, flush };
  });

  useServerInsertedHTML(() => {
    const inserted = registry.flush();
    if (inserted.length === 0) {
      return null;
    }
    let styles = '';
    let dataEmotionAttribute = registry.cache.key;

    const globals: {
      name: string;
      style: string;
    }[] = [];

    inserted.forEach(({ name, isGlobal }) => {
      const style = registry.cache.inserted[name];

      if (typeof style !== 'boolean') {
        if (isGlobal) {
          globals.push({ name, style });
        } else {
          styles += style;
          dataEmotionAttribute += ` ${name}`;
        }
      }
    });

    return (
      <React.Fragment>
        {globals.map(({ name, style }) => (
          <style
            key={name}
            data-emotion={`${registry.cache.key}-global ${name}`}
            // eslint-disable-next-line react/no-danger
            dangerouslySetInnerHTML={{ __html: style }}
          />
        ))}
        {styles !== '' && (
          <style
            data-emotion={dataEmotionAttribute}
            // eslint-disable-next-line react/no-danger
            dangerouslySetInnerHTML={{ __html: styles }}
          />
        )}
      </React.Fragment>
    );
  });

  return <CacheProvider value={registry.cache}>{children}</CacheProvider>;
}

@mtr1990
Copy link
Author

mtr1990 commented Jul 16, 2023

Thank you so much! It's worked

@mtr1990 mtr1990 closed this as completed Jul 16, 2023
@oliviertassinari oliviertassinari added bug 🐛 Something doesn't work examples Relating to /examples labels Jul 22, 2023
@oliviertassinari oliviertassinari changed the title [Theme Mode] CssBaseline not working with next-app-dir (with ThemeRegistry) [Theme Mode] CssBaseline not working with Next.js App Router (with ThemeRegistry) Jul 24, 2023
@omerman
Copy link

omerman commented Oct 14, 2023

@mtr1990 This will be fixed by #37962. In the meanwhile, you can copy and paste this to EmotionCacheProvider directly (it is the same code):

'use client';

import * as React from 'react';
import createCache from '@emotion/cache';
import { useServerInsertedHTML } from 'next/navigation';
import { CacheProvider as DefaultCacheProvider } from '@emotion/react';
import type { EmotionCache, Options as OptionsOfCreateCache } from '@emotion/cache';

export type NextAppDirEmotionCacheProviderProps = {
  /** This is the options passed to createCache() from 'import createCache from "@emotion/cache"' */
  options: Omit<OptionsOfCreateCache, 'insertionPoint'>;
  /** By default <CacheProvider /> from 'import { CacheProvider } from "@emotion/react"' */
  CacheProvider?: (props: {
    value: EmotionCache;
    children: React.ReactNode;
  }) => React.JSX.Element | null;
  children: React.ReactNode;
};

// Adatped from https://github.com/garronej/tss-react/blob/main/src/next/appDir.tsx
export default function NextAppDirEmotionCacheProvider(props: NextAppDirEmotionCacheProviderProps) {
  const { options, CacheProvider = DefaultCacheProvider, children } = props;

  const [registry] = React.useState(() => {
    const cache = createCache(options);
    cache.compat = true;
    const prevInsert = cache.insert;
    let inserted: { name: string; isGlobal: boolean }[] = [];
    cache.insert = (...args) => {
      const [selector, serialized] = args;
      if (cache.inserted[serialized.name] === undefined) {
        inserted.push({
          name: serialized.name,
          isGlobal: selector === '',
        });
      }
      return prevInsert(...args);
    };
    const flush = () => {
      const prevInserted = inserted;
      inserted = [];
      return prevInserted;
    };
    return { cache, flush };
  });

  useServerInsertedHTML(() => {
    const inserted = registry.flush();
    if (inserted.length === 0) {
      return null;
    }
    let styles = '';
    let dataEmotionAttribute = registry.cache.key;

    const globals: {
      name: string;
      style: string;
    }[] = [];

    inserted.forEach(({ name, isGlobal }) => {
      const style = registry.cache.inserted[name];

      if (typeof style !== 'boolean') {
        if (isGlobal) {
          globals.push({ name, style });
        } else {
          styles += style;
          dataEmotionAttribute += ` ${name}`;
        }
      }
    });

    return (
      <React.Fragment>
        {globals.map(({ name, style }) => (
          <style
            key={name}
            data-emotion={`${registry.cache.key}-global ${name}`}
            // eslint-disable-next-line react/no-danger
            dangerouslySetInnerHTML={{ __html: style }}
          />
        ))}
        {styles !== '' && (
          <style
            data-emotion={dataEmotionAttribute}
            // eslint-disable-next-line react/no-danger
            dangerouslySetInnerHTML={{ __html: styles }}
          />
        )}
      </React.Fragment>
    );
  });

  return <CacheProvider value={registry.cache}>{children}</CacheProvider>;
}

@siriwatknp
Thank you so much it works ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 Something doesn't work component: CssBaseline The React component examples Relating to /examples package: system Specific to @mui/system
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants