I am new Docusaurus user, trying to synchronise Docusaurus dark/light mode with MaterialUI's dark/light mode. For example, when the toggle switch is changed from light to dark mode in Docusaurus then dark mode should be activated in MaterialUI.
My approach so far has been to swizzle Docusaurus ColorModeToggle
via
wrapping. From the wrapped ColorModeToggle
I retrieve a function stored in a React context to toggle the light/dark theme in MaterialUI. Within the swizzled Root
I use the react context provider which, in turn, wraps a MaterialUI ThemeProvider
. For further details I have included the code below.
However, when I browse my site, I get the following error:
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Has anyone else managed to synchronise Docusaurus light/dark theme with MaterialUI?
The swizzled ColorModeToggle
import React from "react";
import ColorModeToggle from "@theme-original/ColorModeToggle";
import { useToggleTheme } from "@site/src/components/MuiTheme";
export default function ColorModeToggleWrapper(props) {
console.log("<ColorModeToggleWrapper> properties = " + JSON.stringify(props));
// "value" holds docusaurus color theme. Either "light" or "dark"
const { value } = props;
const muiToggle = useToggleTheme();
console.log("Docusaurus theme = " + value);
console.log("MUI theme dark = " + muiToggle());
return (
<>
<ColorModeToggle {...props} />
</>
);
}
The React Context
import React, { useContext } from "react";
import { createTheme, ThemeProvider } from "@mui/material/styles";
const CustomThemeContext = React.createContext({ toggleTheme: () => {} });
const darkTheme = createTheme({
components: {
MuiListItemText: {
styleOverrides: {
primary: {
color: "orange",
},
secondary: {
color: "purple",
},
},
},
},
palette: {
mode: "dark",
primary: {
main: "hsl(8,71%,28%)" /* burgundy mapped to link */,
},
secondary: {
main: "hsl(61,78%,26%)" /* brown */,
},
},
});
const lightTheme = createTheme({
components: {
MuiListItemText: {
styleOverrides: {
primary: {
color: "aqua",
},
secondary: {
color: "grey",
},
},
},
},
palette: {
mode: "light",
primary: {
main: "hsl(8,10%,18%)" /* burgundy mapped to link */,
},
secondary: {
main: "hsl(61,18%,26%)" /* brown */,
},
},
});
export function CustomThemeProvider({ children }) {
const [dark, setDark] = React.useState(false);
function toggleTheme() {
console.log("toggleTheme :: from dark = " + dark);
if (dark === true) {
setDark(false);
} else {
setDark(true);
}
}
const theme = React.useMemo(() => {
if (dark === true) {
return createTheme(darkTheme);
}
return createTheme(lightTheme);
}, [dark]);
return (
<CustomThemeContext.Provider value={toggleTheme}>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</CustomThemeContext.Provider>
);
}
export function useToggleTheme() {
const context = useContext(CustomThemeContext);
if (context === undefined) {
throw new Error(
"useCustomThemeContext must be used within an CustomThemeProvider"
);
}
return context;
}
The Root component
import React from "react";
import { CustomThemeProvider } from "@site/src/components/MuiTheme";
import App from "@site/src/components/App";
export default function Root({ children }) {
return (
<>
<CustomThemeProvider>
<App children={children}></App>
</CustomThemeProvider>
</>
);
}
The App component
import React from "react";
export default function App(props) {
return <React.Fragment>{props.children}</React.Fragment>;
}
Solved why the error message was happening. I was calling the toggleTheme
function directly during rendering. Further details are explained here
I updated the ColorModeToggleWrapper
to be as listed below. This uses useEffect
to monitor change of the Docusaurus value property. When the value changes then useEffect
toggles the Material-UI theme using a reference to the function stored in the react context.
import React, { useEffect } from "react";
import ColorModeToggle from "@theme-original/ColorModeToggle";
import { useToggleTheme } from "@site/src/components/MuiTheme";
export default function ColorModeToggleWrapper(props) {
// extract the docusaurus theme from the component properties
const { value } = props;
// get the toggleTheme function from the context
const toggleTheme = useToggleTheme();
// whenever the theme changes in docusaurus trigger the change
// in MaterialUI by calling the callback function stored in the react
// context
useEffect(() => {
console.log("Docusaurus theme changed to = " + value);
console.log("Synching theme change with MaterialUI");
toggleTheme();
}, [value]);
return (
<>
<ColorModeToggle {...props} />
</>
);
}
This solved the error detailed in the question. Now I just need to ensure that the initial starting state of the Material-UI theme matches the initial state of the Docusaurus theme......