The optimum way of changing Material UI react theme at runtime - reactjs

I am new to React and Material UI and still am trying to grasp the composition over inheritance.
I am trying to achieve switching to dark/light theme at runtime in a react app. I have achieved it somehow but with lots of code duplication. I am sure there is a better way.
Here is what I have so far:
Theme.js
import { createMuiTheme } from "#material-ui/core/styles";
export const darkTheme = createMuiTheme({
palette: {
type: "dark",
},
//.....a lot of items
});
export const lightTheme = createMuiTheme({
palette: {
type: "light",
},
//.....duplicating same items as above
});
App.js
import { lightTheme, darkTheme } from "../shared/Theme";
const App = ({
theme
}) => {
return (
<ThemeProvider theme={theme === "dark" ? darkTheme : lightTheme}>
{/*Components...*/}
</ThemeProvider>
);
};
The theme props is getting injected using redux and its doing its job fine. This is workable solution but not the best one.
I found in Material UI documentation that we can have [outer and inner theme provider][1] and I tried doing the following (but it didn't work):
<ThemeProvider theme={…} >
<ThemeProvider theme={outerTheme => ({ darkMode: true, ...outerTheme })}>
{...Component}
</ThemeProvider>
</ThemeProvider>
I am aware of useStyle hook but it just lets you create class names that you can use in your component. What I want is: take a certain section of the existing theme object and replace a property in it.
Any help will is appreciated. Thanks for reading this far!
[1]: https://material-ui.com/styles/advanced/#main-content

"Duplication" is a problem when there is property-value repetition, otherwise is an alternate configuration, that is, repeating objects or their properties are not an issue. If there is no property-value repetition, using separate objects is the way, MUI does it, so you are right on track.
Now, Assuming you meant property-value repetition (shared styling) at:
//.....duplicating same items as above
where there is property-value repetition mixed with alternate configuration within your objects, and based on your goal:
take a certain section of the existing theme object and replace a property in it
Try a factory method:
const createThemeOptions = (isDarkMode)=>({
palette: {
mode: isDarkMode? "dark" : "light", // alternate configuration
},
primary: {
main: indigo['A700'], // property-value repetition(shared styling)
},
secondary: {
main: isDarkMode? deepOrange['A200'] : deepOrange['900'], // alternate configuration
},
// make your other object properties shared or alternate...
});
export const darkTheme = createMuiTheme(createThemeOptions(true));
export const lightTheme = createMuiTheme(createThemeOptions());

Related

Dynamic Material-UI Theme Provider Color Palette hex values from either API response or values from form

i working on front end project for my company.project is based on next/reactJs with material-ui v5 as styling support. i am fairly familiar with MUIv5 ThemeProvider and its usage. As per company's latest requirements themeproviders color palette values should be coming from backend or should be coming from form where user types the color codes or names in respective fields and it should be reflected throughout the app. i provided them with dropdown with 2-3 color options but they want to give an option to their clients to customize the app (hope it technically possible! ;)).
i am attaching the code snippets of themeprovider which utilize the useContext concept.
i request you all guys to provide me some kind of solution/guidance to my problem
have a nice day ahead
you guys are awesome.................
import { createTheme, responsiveFontSizes } from "#mui/material";
const primaryColor = "#02475B";
const secondaryColor = "#07AE8B";
const warningColor = "#FFA343";
const errorColor = "#CD4A4A";
const textColorLight = "#f5f5f5";
const textColorDark = "#001219";
const baseTheme = createTheme({
palette: {
mode: "light",
primary: {
main: primaryColor,
},
secondary: {
main: secondaryColor,
},
warning: {
main: warningColor,
},
error: {
main: errorColor,
},
neutral: {
main: "#f2f4f3",
},
darkNeutral: {
main: "#353c55",
},
typography: {
fontFamily: ["Nunito Sans", "sans-serif"].join(","),
},
},
});
const theme = responsiveFontSizes(baseTheme);
export default theme;
i did try calling api in theme.js file but throw an error about useContext also tried making theme component to make work ... its child to data manipulation i was successful for manipulating one color from child
I would for example create a function that will create my theme and store that theme in the local state of my root component.
import { createTheme } from '#mui/material/styles';
const createCustomTheme = (severty?: 'primary' | 'secondary' | undefined , color?: undefined | string)=>{
return createTheme({
palette: {
mode: 'dark',
primary: {
main: (severty == 'primary' ) ? color :'#2a7696',
},
secondary: {
main: (severty == 'secondary' ) ? color :'#f50057',
},
background: {
default: '#0a1929',
paper: '#0f2237',
},
divider: 'rgba(255,255,255,0.54)',
},
shape: {
borderRadius: 4
}
});};
export default createCustomTheme;
This way you can customize the creation of your theme. Here my function is not really developed. Once the theme is created and stored in the local state of your root component. You will only have to pass that local state to the ThemeProvider.
You can then create a form that will ask the user to choose the colors and everything... once created, simply update your state by passing the user's information to your function for creating the theme.

Set MUI Theme for Specific React Page

We are using MUI with our React site and currently have light & dark themes. I would like to force the light theme on a specific page (log in). For some reason, I cannot seem to find a solution anywhere online. How can I go about doing this?
import { createTheme, ThemeProvider } from '#material-ui/core';
const loginTheme = createTheme({
palette: {
background: {
default: '#303030',
paper: '#424242'
},
},
});
<ThemeProvider theme={loginTheme}>
wrap the content you want to use the above theme
</ThemeProvider>

Typescript error says property does not exist on type

Note! I have managed to mitigate the problem by using "theme: any" in the below declaration but I would prefer a better approach.
I am using React (v17.0.2) for front-end with material-ui (v5.0.0) and I get the following error:
Property 'palette' does not exist on type 'Theme'.
whenever I try to access my theme like so:
import { useTheme } from '#emotion/react';
export default function MyComponent() {
const theme = useTheme()
return (
<Box
sx={{
backgroundColor: theme.palette.mode === 'dark' ? 'primary.dark' : 'primary',
}}
></Box>
);
}
I logged the object with console.log(theme) underneath the declaration, and it logged my theme succesfully. So it is there but I cannot access it like shown above.
Here is some of what was logged:
{breakpoints: {…}, direction: 'ltr', components: {…}, palette: {…}, spacing: ƒ, …}
> breakpoints: {keys: Array(5), ...}
> components: {MuiTypography: {…}, ...}
direction: "ltr"
> mixins: {toolbar: {...}}
> palette: {mode: 'dark', ...}
...
Also, I found the file where the type "Theme" is located and the property "palette" definitely exists. Here is a snippet of the file:
export interface Theme extends SystemTheme {
mixins: Mixins;
components?: Components;
palette: Palette;
shadows: Shadows;
transitions: Transitions;
typography: Typography;
zIndex: ZIndex;
unstable_strictMode?: boolean;
}
I also tried importing and using Theme like so:
import { Theme } from '#mui/material/styles';
...
const theme: Theme = useTheme()
...
And this gave me a new error ( underlining the "theme" variable ):
Type 'Theme' is missing the following properties from type 'Theme': mixins, palette, shadows, transitions, and 6 more.
I tried like this too:
import { Theme } from '#mui/material/styles';
...
const theme = useTheme<Theme>()
...
And this gave me a new error ( underlining "Theme" in useTheme<Theme>() )
Expected 0 type arguments, but got 1.
and also
Property 'palette' does not exist on type 'Theme'.
I am new to typescript, so any help is appreciated.
EDIT
Got the answer thanks to Alex Wayne (and maybe also windowsill if I initially misunderstood the answer). Here's the code that worked:
import { useTheme, Theme } from '#mui/material';
const theme: Theme = useTheme()
<Box sx={{backgroundColor: theme.palette.mode}}></Box>
In order to get proper type checks you can extend emotion Theme interface with MUI's.
import { Theme as MuiTheme } from "#mui/material/styles";
declare module '#emotion/react' {
export interface Theme extends MuiTheme {}
}
As specified in
https://emotion.sh/docs/typescript#define-a-theme
#emotion/react exports a Theme type, returned by useTheme() from the same package.
#mui/material/styles exports a Theme type, returned by createTheme from the same package.
These are not the same type.
They have the same name in each package, but are completely unrelated.
This is why this fails:
import { useTheme } from '#emotion/react';
import { Theme } from '#mui/material/styles';
const theme: Theme = useTheme()
// Type 'Theme' is missing the following properties from type 'Theme': mixins, palette, shadows, transitions, and 2 more.(2740)
Playground
But this succeeds.
import { useTheme, Theme } from '#emotion/react';
const theme: Theme = useTheme()
Playground
I don't know exactly which one you intend to use, but here are the docs on emotion themes and here are the docs on Material UI themes. They are separate things, and you need to use each according to their intended use.

How to use react-admin with material ui version 5

How to use react-admin with Material UI version 5. Is it possible to make it independent from material ui 4?
You'll need to use the latest theme and the legacy theme. The legacy theme should be set on the Admin component and the latest theme should be set via the ThemeProvider.
MUI v5 and MUI v4.x aren't that different in terms of the basic default theme. Given some things have been moved around and one or two keys removed. You can create an object to be the global theme (containing the typography, palette, breakpoints, etc if you customize these values).
A key difference in v5.x and v4.x is how style overrides and default props for components are defined. You will need to create a function to loop over all themeV5.components and grab the values in defaultProps and styleOverrides and assign them under the themeV4.props and themeV4.overrides.
import { ThemeProvider } from '#mui/material/styles';
import { createTheme } from '#mui/material/styles';
import { createTheme as createThemeV4 } from '#material-ui/core/styles';
const theme = {
sidebar: {...},
palette: {...},
typography: {...},
}
let latestTheme = createTheme({
...theme,
components: {},
});
let legacyTheme = createThemeV4({
...theme,
overrides: {},
props: {},
});
<ThemeProvider theme={latestTheme}>
<Admin
title={APP_NAME}
authProvider={authProvider}
dataProvider={dataProvider}
i18nProvider={i18nProvider}
history={history}
theme={legacyTheme}
>
{resources}
</Admin>
</ThemeProvider>
Looks like the next major version (4) of React-Admin adds support for MUI v5

Extending Material UI's existing dark mode colors

Material UI's default theme ships a palette of colors, including a special set of dark colors (docs, code).
What makes these dark mode colors special is components who consume them don't need to depend on knowing the theme's palette.mode (aka light/dark mode) - they update automatically.
codesandbox demo
My goal is to extend this set of colors, such that components I write can use new colors beyond this built-in set, e.g. theme.palette.myColor and benefit from the same automatic behavior.
In other words, I don't want to have dark mode logic be duplicated in each theme-consuming component:
const WhatIDontWantComponent = () => (
<Box
sx={{
color: (theme) =>
theme.palette.mode === "light"
? theme.palette.myColor.light
: theme.palette.myColor.dark,
}}
/>
);
I instead want to use
const WhatIWantComponent = () => (
<Box
sx={{
color: (theme) => theme.palette.myColor
}}
/>
);
So myColor would be included in the light/dark set that already exists, and benefit from this automatic behavior.
Possible? Is there some way to accomplish this within my app without patching MUI in some way to accept custom colors?
deps
#material-ui/core version 5.0.0-beta.4
react, react-dom 17.0.2
next.js 11.0
Ended up going with this:
const baseTheme = createTheme({...common options...})
export const lightTheme = createTheme({ ...light specific...}, baseTheme)
export const darkTheme = createTheme({ mode: "dark", ...dark specific...}, baseTheme)
based on this discussion.
We basically perform theme composition in multiple steps + utilizing how the second arg to createTheme() gets deepmerge'd.
Once you have the two themes, you could consider setting up a toggle by bringing your own mode:
<ThemeProvider theme={mode ? lightTheme : darkTheme}>
{...app...}
</ThemeProvider>
This works, but IMO a downside of this approach though is that I have two themes and have to manage dark/light state externally. Not a huge problem but feels a bit redundant -- in my mind dark/light mode seems like a concern that could (should) be handled internally to a single theme.
Perhaps there are good reasons against, but IMO it feels more ergonomic if MUI would allow users to specify our own dark palette, like I asked in my original question, vs the current hard coded one.
So if you're looking for custom MUI dark colors, this approach seems to be the best one given the situation right now.
You can customize the material-ui theme using theme provider component in order to add your custom colors for e.g,
import { createTheme, colors } from '#material-ui/core/styles';
const theme = createTheme({
palette: {
background: {
default: "#F6F7FF",
paper: colors.common.white
},
primary: {
main: "#43A047"
},
secondary: {
main: "#43A047"
},
text: {
primary: "#000000",
secondary: "#6b778c"
},
// Add your custom colors if any
},
});
You can write this configuration code inside a separate file and then import it into your root component file.
import theme from "src/theme";
import { ThemeProvider } from "#material-ui/core";
<ThemeProvider theme={theme}>
<YourRootComponent />
</ThemeProvider>
Then consume it the same way you do it for the default material UI colors
Also, you can customize typography, shadows, override default class, and much more check out this

Resources