I've encountered an issue with MUI 5.14.1 where I'm getting an error every time I attempt to save themes across pages using localStorage. Any suggestions on how to resolve this problem or recommendations on a different approach would be greatly appreciated.
import React, { useState } from "react";
import {
CssBaseline,
Button,
Typography,
} from "@mui/material";
import { ThemeProvider } from "@mui/material/styles";
import { Toaster, toast } from "react-hot-toast";
import { Link } from "react-router-dom"; // Import the Link component from react-router-dom
import styles from "../styles/app.module.css";
import MuteSwitch from "../components/MuteSwitch.js";
import StyledAvatar from "../components/StyledAvatar.js";
import Sidebar from "../components/Sidebar";
import {
toggleDarkMode,
handleThemeChange,
lightTheme,
} from "../utils/themeUtils";
const persistedTheme = JSON.parse(localStorage.getItem("theme")) || lightTheme;
const HomePage = () => {
const [currentTheme, setCurrentTheme] = useState(persistedTheme); // Define the state variable for the current theme
const [darkMode, setDarkMode] = useState(false); // Track dark mode state, false = light mode, true = dark mode
const [userInputColor, setUserInputColor] = useState("#1976d2"); // Default initial color
const [colorPickerColor, setColorPickerColor] = useState("#1976d2"); // Default initial color
const saveTheme = (theme) => {
localStorage.setItem("theme", JSON.stringify(currentTheme));
};
const handleColorChange = (event) => {
setColorPickerColor(event.target.value);
setUserInputColor(event.target.value);
};
const handleDarkModeToggle = () => {
setDarkMode((prevMode) => !prevMode); // Toggle the dark mode state
toggleDarkMode(darkMode, setCurrentTheme);
};
const createToast = (message) => {
let toastBackground = currentTheme.palette.primary.main;
let toastColor = currentTheme.palette.primary.contrastText;
toast.success(message, {
style: {
background: toastBackground,
color: toastColor,
},
});
};
const handleNewMessages = () => {
createToast("You have 3 new messages");
};
const onThemeChange = () => {
//possibly darken color picker color
const updatedTheme = handleThemeChange(userInputColor);
setCurrentTheme(updatedTheme);
saveTheme(updatedTheme);
};
return (
<>
<Toaster />
<ThemeProvider theme={currentTheme}>
<CssBaseline />
<div className={styles.heading}>
<Typography variant="h1" component="h1" gutterBottom>
Home Page
</Typography>
</div>
{/* content */}
<div className={styles.centeredContent}>
<Button variant="contained">Pretty Colors</Button>
</div>
{/* mute switch */}
<div className={styles.muteSwitch}>
<MuteSwitch />
</div>
{/* avatar */}
<Link to="/profile" style={{ textDecoration: "none", color: "inherit" }}>
<div className={styles.avatar}>
<StyledAvatar>TS</StyledAvatar>
</div>
</Link>
{/* drawer */}
<div>
<Sidebar handleThemeChange={onThemeChange} darkMode={darkMode} handleDarkModeToggle={handleDarkModeToggle} handleNewMessages={handleNewMessages} colorPickerColor={colorPickerColor} handleColorChange={handleColorChange}/>
</div>
</ThemeProvider>
</>
);
};
export default HomePage;
Error:
Unexpected Application Error!
theme.transitions.create is not a function
TypeError: theme.transitions.create is not a function
at http://localhost:3000/static/js/bundle.js:11135:35
My attempt to use localStorage to store and retrieve the theme in a new file has resulted in an error when trying to change
const [currentTheme, setCurrentTheme] = useState(lightTheme);
. However, this issue has been resolved now. You can follow my solution, which involves using React Context, or Dewaun Ayers' solution (marked as solution in the replies), which utilizes localStorage.
Here's my solution:
Create a file named "ThemeContext.js" (or similar) with the following content:
// ThemeContext.js
import { createContext, useContext, useState } from "react";
import { createTheme } from "@mui/material/styles";
const ThemeContext = createContext();
export const useThemeContext = () => {
return useContext(ThemeContext);
};
export const ThemeContextProvider = ({ children }) => {
const [currentTheme, setCurrentTheme] = useState(lightTheme);
const handleThemeChange = (color) => {
const secondaryColor = color; //edit this to be your secondary color
const newTheme = createTheme({
palette: {
primary: {
main: color,
},
secondary: {
main: secondaryColor,
},
},
});
setCurrentTheme(newTheme);
};
return (
<ThemeContext.Provider
value={{ currentTheme, handleThemeChange }}
>
{children}
</ThemeContext.Provider>
);
};
Import this file into your top-level React file and wrap your router/components in the ThemeContextProvider component. Here's how I implemented it:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import HomePage from "./pages/HomePage";
import ProfilePage from "./pages/ProfilePage";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { ThemeContextProvider } from "./utils/ThemeContext";
const router = createBrowserRouter([
{
path: "/",
element: <HomePage />,
},
{
path: "/profile",
element: <ProfilePage />,
},
]);
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<ThemeContextProvider>
<RouterProvider router={router} />
</ThemeContextProvider>
</React.StrictMode>
);
Finally, update your page components to utilize the variables and functions stored in Context, like so:
import { useThemeContext } from "../utils/ThemeContext";
const HomePage = () => {
const { currentTheme, handleThemeChange, isDarkMode, toggleDarkMode, colorPickerColor, userInputColor, handleColorChange } = useThemeContext();
const createToast = (message) => {
let toastBackground = currentTheme.palette.primary.main;
let toastColor = currentTheme.palette.primary.contrastText;
toast.success(message, {
style: {
background: toastBackground,
color: toastColor,
},
});
};
const handleNewMessages = () => {
createToast("You have 3 new messages");
};
const onThemeChange = () => {
//possibly darken color picker color
handleThemeChange(userInputColor);
};
return (
<>
<Toaster />
<ThemeProvider theme={currentTheme}>
<CssBaseline />
<div className={styles.heading}>
<Typography variant="h1" component="h1" gutterBottom>
Home Page
</Typography>
</div>
{/* content */}
<div className={styles.centeredContent}>
<Button variant="contained">Pretty Colors</Button>
</div>
{/* mute switch */}
<div className={styles.muteSwitch}>
<MuteSwitch />
</div>
{/* avatar */}
<Link
to="/profile"
style={{ textDecoration: "none", color: "inherit" }}
>
<div className={styles.avatar}>
<StyledAvatar>TS</StyledAvatar>
</div>
</Link>
{/* drawer */}
<div>
<Sidebar
handleThemeChange={onThemeChange}
isDarkMode={isDarkMode}
handleDarkModeToggle={toggleDarkMode}
handleNewMessages={handleNewMessages}
colorPickerColor={colorPickerColor}
handleColorChange={handleColorChange}
currentTheme={currentTheme}
/>
</div>
</ThemeProvider>
</>
);
};
export default HomePage;
This commit to the project repository showcases all the changes made: https://github.com/AnthonySchneider2000/React-Material-UI-Dynamic-Theme-Changer/commit/5e89229b8ec04e2cc3aed3b7fc7205b1396ee401