React Material Theme Chooser - reactjs

So I've been working with react material on my application. Everything until now it's fine but I want to make application in which themes can be stored on the back-end and I can load them based on user choice.
So until now, I know I can create multiple themes and store them as stated here
but I want to store them to the back-end and I don't have Idea how that would work
So I need help for an idea or some kind of tutorial which can point me in the right direction?

You just need to store the attributes that you are allowing the user to change/specify. For instance, you might only allow them to choose a primary and secondary color. You would then save those two pieces of information in your DB and then recreate the theme using createMuiTheme.
Here's some sample code demonstrating this:
import React from "react";
import ReactDOM from "react-dom";
import CssBaseline from "#material-ui/core/CssBaseline";
import AppBar from "#material-ui/core/AppBar";
import Button from "#material-ui/core/Button";
import { createMuiTheme, MuiThemeProvider } from "#material-ui/core/styles";
const themeDB = {
a: {
primaryColor: "#0f0",
secondaryColor: "#f0f"
},
b: {
primaryColor: "#ff0",
secondaryColor: "#0ff"
}
};
const createThemeFromThemeDBEntry = themeDBEntry => {
return createMuiTheme({
palette: {
primary: {
main: themeDBEntry.primaryColor
},
secondary: {
main: themeDBEntry.secondaryColor
}
}
});
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = { currentTheme: createMuiTheme() };
}
switchToThemeA = () => {
const themeA = createThemeFromThemeDBEntry(themeDB.a);
this.setState({ currentTheme: themeA });
};
switchToThemeB = () => {
const themeB = createThemeFromThemeDBEntry(themeDB.b);
this.setState({ currentTheme: themeB });
};
useDefaultTheme = () => {
this.setState({ currentTheme: createMuiTheme() });
};
render() {
return (
<>
<CssBaseline />
<MuiThemeProvider theme={this.state.currentTheme}>
<AppBar position="static">AppBar using Primary Color</AppBar>
<AppBar position="static" color="secondary">
AppBar using Secondary Color
</AppBar>
<br />
<Button
onClick={this.switchToThemeA}
variant="contained"
color="primary"
>
Use Theme A
</Button>
<Button
onClick={this.switchToThemeB}
variant="contained"
color="secondary"
>
Use Theme B
</Button>
<Button onClick={this.useDefaultTheme} color="secondary">
Use Default Theme
</Button>
</MuiThemeProvider>
</>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Related

cannot use custom Material UI theme [duplicate]

I'm trying something very simple: building two themes for a website using Material-UI themes:
A light theme and dark one, but it does not work well: the theme is on every Material-UI react element, but the root element on the html document keeps having the same default white background.
Of course it can be changed by attacking the body with pure .css:
body {
background-color: #222;
}
But I was looking to change it dynamically with React, I though this would work, but it does not:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { ThemeProvider } from '#material-ui/styles';
import { MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles';
const themeLight = createMuiTheme({
palette: {
background: {
default: "#e4f0e2"
}
},
});
const themeDark = createMuiTheme({
palette: {
background: {
default: "#222222",
}
},
});
ReactDOM.render(
<MuiThemeProvider theme = { themeDark }>
<App />
</MuiThemeProvider>, document.getElementById('root'));
and I'm lost here, there is no way to make this with Material-UI theme?
CssBaseline is the component that controls this aspect. If you aren't using CssBaseline, then you are just seeing the default provided by the browser.
Here is a working v4 example (v5 example further down):
import React from "react";
import ReactDOM from "react-dom";
import CssBaseline from "#material-ui/core/CssBaseline";
import { MuiThemeProvider, createMuiTheme } from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
const themeLight = createMuiTheme({
palette: {
background: {
default: "#e4f0e2"
}
}
});
const themeDark = createMuiTheme({
palette: {
background: {
default: "#222222"
},
text: {
primary: "#ffffff"
}
}
});
const App = () => {
const [light, setLight] = React.useState(true);
return (
<MuiThemeProvider theme={light ? themeLight : themeDark}>
<CssBaseline />
<Button onClick={() => setLight(prev => !prev)}>Toggle Theme</Button>
</MuiThemeProvider>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Below is a Material-UI v5 example. The only difference from v4 is the name change for ThemeProvider (though this name is also available in v4 in addition to MuiThemeProvider) and createTheme (instead of createMuiTheme) and using the new #mui/material package name instead of #material-ui/core.
import React from "react";
import ReactDOM from "react-dom";
import CssBaseline from "#mui/material/CssBaseline";
import { ThemeProvider, createTheme } from "#mui/material/styles";
import Button from "#mui/material/Button";
const themeLight = createTheme({
palette: {
background: {
default: "#e4f0e2"
}
}
});
const themeDark = createTheme({
palette: {
background: {
default: "#222222"
},
text: {
primary: "#ffffff"
}
}
});
const App = () => {
const [light, setLight] = React.useState(true);
return (
<ThemeProvider theme={light ? themeLight : themeDark}>
<CssBaseline />
<Button onClick={() => setLight((prev) => !prev)}>Toggle Theme</Button>
</ThemeProvider>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
In MUI v5, you can also use GlobalStyles component to add the styles to the body element:
<GlobalStyles
styles={{
body: { backgroundColor: "lightyellow" }
}}
/>
On top of #NearHuscarl 's answer, importing the GlobalStyles after the CSSBaseLine, will retain the page defaults (like margin: 0, etc.,) still able to customize root-level / global styles. For eg,
import { Component } from "react";
import { Button, CssBaseline, GlobalStyles } from "#mui/material";
import { ThemeProvider, createTheme } from "#mui/material/styles";
export class App extends Component {
render() {
const theme = createTheme({
palette: {
mode: "dark",
primary: {
main: "#ff0000",
contrastText: "#fff",
},
secondary: {
main: green[500],
},
},
});
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<GlobalStyles
styles={{
body: { backgroundColor: "cyan" },
}}
/>
<Button color="primary" variant="contained">
Button
</Button>
</ThemeProvider>
);
}
}
export default App;
(I'm just using class component out of habit 😅)
Full example with nested themes MUI Theme toggle
My use case, I only wanted to change background-color of body from within a React component, not the entire theme. Used a global override.
TL;DR code:
// other imports ...
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
'#global':{
body:{
backgroundColor:"#382E7E"
}
},
otherstyles:{
// other styles ....
},
}));
// React component, etc ...
MUI Version 5
import { createTheme } from "#mui/material/styles";
const themeX = createMuiTheme({
palette: {
mode: "dark",
}
});
MUI Version 4
import { createMuiTheme } from '#material-ui/core/styles';
const themeX = createMuiTheme({
palette: {
type: "dark",
}
});
just as simple that, changing the pallets type to dark by default its set to light. This will also help in custom color for other components like typography, icon etc
ReactDOM doesn't replace the targeted element. I haven't worked with material ui personally. However, if you put the background color you want into your App state as something like 'currentRootColor', then in your App component's render function you could put:
render() {
document.body.style.backgroundColor = this.state.currentRootColor;
...the rest of your App render code
}
This would set the body's background color and if you change 'this.state.currentRootColor', then your App component would re-render with the new background color.
However if you dont already have a < body > tag in your document you would need to add one.
In MUI v5, this seem to work for me. I used it to apply specific styles only to HomePage (overwrite default styles).
pages/HomePage.js
...
import GlobalStyles from '#mui/material/GlobalStyles';
// or
import { GlobalStyles } from '#mui/material';
Note: It is a good practice to hoist the <GlobalStyles /> to a static
constant, to avoid rerendering. This will ensure that the tag
generated would not recalculate on each render.
const homePageStyles = (
<GlobalStyles
styles={{
body: { backgroundColor: 'cyan' },
'.MuiTypography-root': {
color: 'red',
},
}}
/>
);
...
return (
<>
{homePageStyles}
<MyComponents />
</>
);
....
More:
https://mui.com/material-ui/customization/how-to-customize/
https://mui.com/material-ui/api/global-styles/
All the above answers did not work for me, why?? I don't know.
I covered all of my components with ScopedCssBaseline and mui will apply the palette style only to the children.
Below is my answer,
import React from "react";
import ReactDOM from "react-dom";
import { ScopedCssBaseline, Button } from "#mui/material";
import { ThemeProvider, createTheme } from "#mui/material/styles";
const themeLight = createTheme({
palette: {
background: {
default: "#fff"
},
text: {
default: "#000"
}
}
});
const themeDark = createTheme({
palette: {
background: {
default: "#000"
},
text: {
primary: "#fff"
}
}
});
const App = () => {
const [light, setLight] = React.useState(true);
return (
<ThemeProvider theme={light ? themeLight : themeDark}>
<ScopedCssBaseline enableColorScheme>
<Button onClick={() => setLight((prev) => !prev)}>Toggle Theme</Button>
</ScopedCssBaseline>
</ThemeProvider>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Change root background color with Material-UI theme

I'm trying something very simple: building two themes for a website using Material-UI themes:
A light theme and dark one, but it does not work well: the theme is on every Material-UI react element, but the root element on the html document keeps having the same default white background.
Of course it can be changed by attacking the body with pure .css:
body {
background-color: #222;
}
But I was looking to change it dynamically with React, I though this would work, but it does not:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { ThemeProvider } from '#material-ui/styles';
import { MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles';
const themeLight = createMuiTheme({
palette: {
background: {
default: "#e4f0e2"
}
},
});
const themeDark = createMuiTheme({
palette: {
background: {
default: "#222222",
}
},
});
ReactDOM.render(
<MuiThemeProvider theme = { themeDark }>
<App />
</MuiThemeProvider>, document.getElementById('root'));
and I'm lost here, there is no way to make this with Material-UI theme?
CssBaseline is the component that controls this aspect. If you aren't using CssBaseline, then you are just seeing the default provided by the browser.
Here is a working v4 example (v5 example further down):
import React from "react";
import ReactDOM from "react-dom";
import CssBaseline from "#material-ui/core/CssBaseline";
import { MuiThemeProvider, createMuiTheme } from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
const themeLight = createMuiTheme({
palette: {
background: {
default: "#e4f0e2"
}
}
});
const themeDark = createMuiTheme({
palette: {
background: {
default: "#222222"
},
text: {
primary: "#ffffff"
}
}
});
const App = () => {
const [light, setLight] = React.useState(true);
return (
<MuiThemeProvider theme={light ? themeLight : themeDark}>
<CssBaseline />
<Button onClick={() => setLight(prev => !prev)}>Toggle Theme</Button>
</MuiThemeProvider>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Below is a Material-UI v5 example. The only difference from v4 is the name change for ThemeProvider (though this name is also available in v4 in addition to MuiThemeProvider) and createTheme (instead of createMuiTheme) and using the new #mui/material package name instead of #material-ui/core.
import React from "react";
import ReactDOM from "react-dom";
import CssBaseline from "#mui/material/CssBaseline";
import { ThemeProvider, createTheme } from "#mui/material/styles";
import Button from "#mui/material/Button";
const themeLight = createTheme({
palette: {
background: {
default: "#e4f0e2"
}
}
});
const themeDark = createTheme({
palette: {
background: {
default: "#222222"
},
text: {
primary: "#ffffff"
}
}
});
const App = () => {
const [light, setLight] = React.useState(true);
return (
<ThemeProvider theme={light ? themeLight : themeDark}>
<CssBaseline />
<Button onClick={() => setLight((prev) => !prev)}>Toggle Theme</Button>
</ThemeProvider>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
In MUI v5, you can also use GlobalStyles component to add the styles to the body element:
<GlobalStyles
styles={{
body: { backgroundColor: "lightyellow" }
}}
/>
On top of #NearHuscarl 's answer, importing the GlobalStyles after the CSSBaseLine, will retain the page defaults (like margin: 0, etc.,) still able to customize root-level / global styles. For eg,
import { Component } from "react";
import { Button, CssBaseline, GlobalStyles } from "#mui/material";
import { ThemeProvider, createTheme } from "#mui/material/styles";
export class App extends Component {
render() {
const theme = createTheme({
palette: {
mode: "dark",
primary: {
main: "#ff0000",
contrastText: "#fff",
},
secondary: {
main: green[500],
},
},
});
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<GlobalStyles
styles={{
body: { backgroundColor: "cyan" },
}}
/>
<Button color="primary" variant="contained">
Button
</Button>
</ThemeProvider>
);
}
}
export default App;
(I'm just using class component out of habit 😅)
Full example with nested themes MUI Theme toggle
My use case, I only wanted to change background-color of body from within a React component, not the entire theme. Used a global override.
TL;DR code:
// other imports ...
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
'#global':{
body:{
backgroundColor:"#382E7E"
}
},
otherstyles:{
// other styles ....
},
}));
// React component, etc ...
MUI Version 5
import { createTheme } from "#mui/material/styles";
const themeX = createMuiTheme({
palette: {
mode: "dark",
}
});
MUI Version 4
import { createMuiTheme } from '#material-ui/core/styles';
const themeX = createMuiTheme({
palette: {
type: "dark",
}
});
just as simple that, changing the pallets type to dark by default its set to light. This will also help in custom color for other components like typography, icon etc
ReactDOM doesn't replace the targeted element. I haven't worked with material ui personally. However, if you put the background color you want into your App state as something like 'currentRootColor', then in your App component's render function you could put:
render() {
document.body.style.backgroundColor = this.state.currentRootColor;
...the rest of your App render code
}
This would set the body's background color and if you change 'this.state.currentRootColor', then your App component would re-render with the new background color.
However if you dont already have a < body > tag in your document you would need to add one.
In MUI v5, this seem to work for me. I used it to apply specific styles only to HomePage (overwrite default styles).
pages/HomePage.js
...
import GlobalStyles from '#mui/material/GlobalStyles';
// or
import { GlobalStyles } from '#mui/material';
Note: It is a good practice to hoist the <GlobalStyles /> to a static
constant, to avoid rerendering. This will ensure that the tag
generated would not recalculate on each render.
const homePageStyles = (
<GlobalStyles
styles={{
body: { backgroundColor: 'cyan' },
'.MuiTypography-root': {
color: 'red',
},
}}
/>
);
...
return (
<>
{homePageStyles}
<MyComponents />
</>
);
....
More:
https://mui.com/material-ui/customization/how-to-customize/
https://mui.com/material-ui/api/global-styles/
All the above answers did not work for me, why?? I don't know.
I covered all of my components with ScopedCssBaseline and mui will apply the palette style only to the children.
Below is my answer,
import React from "react";
import ReactDOM from "react-dom";
import { ScopedCssBaseline, Button } from "#mui/material";
import { ThemeProvider, createTheme } from "#mui/material/styles";
const themeLight = createTheme({
palette: {
background: {
default: "#fff"
},
text: {
default: "#000"
}
}
});
const themeDark = createTheme({
palette: {
background: {
default: "#000"
},
text: {
primary: "#fff"
}
}
});
const App = () => {
const [light, setLight] = React.useState(true);
return (
<ThemeProvider theme={light ? themeLight : themeDark}>
<ScopedCssBaseline enableColorScheme>
<Button onClick={() => setLight((prev) => !prev)}>Toggle Theme</Button>
</ScopedCssBaseline>
</ThemeProvider>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

How to create deterministic styles when using createStyles()

I have create a component that needs to have custom styles, so I used createStyles({}). This seems to have worked (almost) as I want it to. I have also used createGenerateClassName({}) to indicate I need deterministic style names. However, the two do not seem to be working together. While the standard MUI components no longer have the hash number as part of the class name, the custom styles do. What need to change to support deterministic styles for every class name?
Here is the code I have:
import {Component, ComponentMeta, ComponentProps, SizeObject} from '#xyz/abc' // real name removed here due to restrictions
import {Button, Paper} from '#material-ui/core'
import {createGenerateClassName, createStyles, MuiThemeProvider, Theme, withStyles} from '#material-ui/core/styles'
import JssProvider from 'react-jss/lib/JssProvider'
const theme = createMuiTheme({
palette: {
primary: {
main: 'blue',
},
secondary: {
main: 'green',
},
error: {
main: 'red',
},
},
typography: {
useNextVariants: true,
},
})
const styles = ({palette, spacing}: Theme) =>
createStyles({
button: {
backgroundColor: '#2196f3',
},
buttonDark: {
backgroundColor: '#880e4f',
},
buttonLight: {
backgroundColor: '#e1bee7',
},
})
const generateClassName = createGenerateClassName({
dangerouslyUseGlobalCSS: true,
})
class AnalysisSelector extends Component<ComponentProps, any> {
render() {
const {classes} = this.props
return (
<MuiThemeProvider theme={theme}>
<JssProvider generateClassName={generateClassName}>
<Paper {...this.props.emit()} className={'paperContainer'}>
<Button className={classes.button}>Primary Light</Button>
<Button className={classes.buttonLight}>Primary</Button>
<Button className={classes.buttonDark}>Primary Dark</Button>
</Paper>
</JssProvider>
</MuiThemeProvider>
)
}
}
export const MNOAnalysisSelector = withStyles(styles, {name: 'mnoButton'})(AnalysisSelector)
Finally here is the rendered HTML:
<button class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-flat mnoButton-button-1" tabindex="0" type="button">
<span class="MuiButton-label">Primary Light</span>
<span class="MuiTouchRipple-root"></span>
</button>
<button class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-flat mnoButton-buttonLight-3" tabindex="0" type="button">
<span class="MuiButton-label">Primary</span>
<span class="MuiTouchRipple-root"></span>
</button>
<button class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-flat mnoButton-buttonDark-2" tabindex="0" type="button">
<span class="MuiButton-label">Primary Dark</span>
<span class="MuiTouchRipple-root"></span>
</button>
</div>
I am fine with the class names being mnoButton-button, mnoButton-buttonLight, and mnoButton-buttonDark, I just need the ending hash removed.
Thanks for any suggestions / assistance.
You can use global class names as documented in v4 here: https://next.material-ui.com/styles/advanced/#jss-plugin-global
jss-plugin-global is included in v3 as well, so the same approach will work with it.
The only way for the other syntax to create global names is if the name passed to withStyles starts with "Mui" (which I wouldn't recommend doing).
I've shown both approaches in the code below.
import React from "react";
import { withStyles } from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
const styles = theme => ({
"#global": {
".mnoButton-button": {
backgroundColor: "#2196f3"
},
".mnoButton-buttonDark": {
backgroundColor: "#880e4f"
},
".mnoButton-buttonLight": {
backgroundColor: "#e1bee7"
}
},
button: {
backgroundColor: "purple",
color: "white"
}
});
const MyButtons = ({ classes }) => {
return (
<>
<Button className="mnoButton-button">Hello World</Button>
<Button className="mnoButton-buttonDark">Hello World</Button>
<Button className="mnoButton-buttonLight">Hello World</Button>
<Button className={classes.button}>Hello World</Button>
</>
);
};
export default withStyles(styles, { name: "Mui-mnoButton" })(MyButtons);
Source: github
import { StylesProvider, createGenerateClassName } from '#material-ui/core/styles';
// other imports
const generateClassName = (rule, styleSheet) =>
`${styleSheet.options.classNamePrefix}-${rule.key}`;
test(deterministic classnames, () => {
render(
<StylesProvider generateClassName={generateClassName}>
<App />
</StylesProvider>
);
});

React Material UI Theme Change

can you please help me to change the React Material UI theme Dynamically .
https://imgur.com/S8zsRPQ
https://imgur.com/Ul8J40N
I have tried by changing the theme Properties on button click . The theme properties are getting changed as seen in the console . But the change is not reflecting on the theme .
Sandbox Code : https://codesandbox.io/s/30qwyk92kq
const themeChange = () => {
alert(theme);
theme.palette.type = "light";
console.log(theme);
};
ReactDOM.render(
<MuiThemeProvider theme={theme}>
<React.Fragment>
<CssBaseline />
<App changeTheme={themeChange} />
</React.Fragment>
</MuiThemeProvider>,
document.getElementById("app")
);
When I click the button the theme has to change to Dark color
I am using styledComponents, typescript and material-ui.
First I defined my themes:
// This is my dark theme: dark.ts
// I defined a light.ts too
import createMuiTheme from '#material-ui/core/styles/createMuiTheme';
export const darkTheme = createMuiTheme({
palette: {
type: 'dark', // Name of the theme
primary: {
main: '#152B38',
},
secondary: {
main: '#65C5C7',
},
contrastThreshold: 3,
tonalOffset: 0.2,
},
});
I defiend a themeProvider function and in this function I wrapped the material-ui's ThemeProvider in a React context to be able to change the theme easily:
import React, { useState } from 'react';
import {ThemeProvider} from "#material-ui/core/styles/";
import { lightTheme } from "./light";
import { darkTheme } from "./dark";
const getThemeByName = (theme: string) => {
return themeMap[theme];
}
const themeMap: { [key: string]: any } = {
lightTheme,
darkTheme
};
export const ThemeContext = React.createContext(getThemeByName('darkTheme'));
const ThemeProvider1: React.FC = (props) => {
// State to hold the selected theme name
const [themeName, _setThemeName] = useState('darkTheme');
// Retrieve the theme object by theme name
const theme = getThemeByName(themeName);
return (
<ThemeContext.Provider value={_setThemeName}>
<ThemeProvider theme={theme}>{props.children}</ThemeProvider>
</ThemeContext.Provider>
);
}
export default ThemeProvider1;
Now I can use it in my components like this:
import React from 'react';
import styled from 'styled-components';
import useTheme from "#material-ui/core/styles/useTheme";
const MyCardHeader = styled.div`
width: 100%;
height: 40px;
background-color: ${props => props.theme.bgColor};
color: ${props => props.theme.txtColor};
display: flex;
align-items:center;
justify-content: center;
`;
export default function CardHeader(props: { title: React.ReactNode; }) {
const theme = {
bgColor: useTheme().palette.primary.main,
txtColor: useTheme().palette.primary.contrastText
};
return (
<MyCardHeader theme={theme}>
{props.title}
</MyCardHeader>
);
}
For Changing between themes:
import React, {useContext} from 'react';
import { ThemeContext} from './themes/themeProvider';
export default function Header() {
// Get the setter function from context
const setThemeName = useContext(ThemeContext);
return (
<header>
<button onClick={() => setThemeName('lightTheme')}>
light
</button>
<button onClick={() => setThemeName('darkTheme')}>
dark
</button>
</header>
);
}
I'm using Material UI v4.
I tried something like Ashkan's answer, but it didn't work for me.
However, I found this in the documentation, and abstracting it to apply to a different piece of state, instead of user preference, worked for me. For your example, I'd probably make a context:
// context.js
import React, { useContext } from "react";
import { ThemeProvider, createTheme } from "#material-ui/core/styles";
import CssBaseline from "#material-ui/core/CssBaseline";
const CustomThemeContext = React.createContext();
// You can add more to these and move them to a separate file if you want.
const darkTheme = {
palette: {
type: "dark",
}
}
const lightTheme = {
palette: {
type: "light",
}
}
export function CustomThemeProvider({ children }) {
const [dark, setDark] = React.useState(false);
function toggleTheme() {
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}>
<CssBaseline />
{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;
}
Then wrap your app in that:
ReactDOM.render(
<CustomThemeProvider>
<App />
</CustomThemeProvider>,
document.getElementById("app")
);
And then access it in your app:
export default function App(){
const toggleTheme = useToggleTheme();
return (
<div>
<button onClick={toggleTheme}>Toggle the theme!!</button>
</div>
);
}
On a side note, in my app, I actually have a different theme in two sections of the app, based on whether the user is logged in or not. So I'm just doing this:
function App() {
const { authState } = useAuthContext();
const theme = React.useMemo(
() => {
if (authState.user) {
return createTheme(dashboardTheme);
}
return createTheme(loginTheme);
},
[authState.user],
);
return (
<ThemeProvider theme={theme}>
<TheRestOfTheApp />
</ThemeProvider>
}
It seems you can base the theme off any piece of state, or multiple pieces, by referencing them in useMemo and including them in the dependency array.
EDIT:
I just noticed that MUI v5 actually has something very similar in their docs.
In your code, theme type is changed. But the Page is not re-rendered with new theme.
I have changed code in index.js and App.js like following.
Try this approach. It works.
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(
<App/>,
document.getElementById("app")
);
App.js
import React from "react";
import CssBaseline from "#material-ui/core/CssBaseline";
import Typography from "#material-ui/core/Typography";
import { Button } from "#material-ui/core";
import { MuiThemeProvider, createMuiTheme } from "#material-ui/core/styles";
import blueGrey from "#material-ui/core/colors/blueGrey";
import lightGreen from "#material-ui/core/colors/lightGreen";
class App extends React.Component {
constructor(props){
super(props);
this.state = {
themeType : 'dark',
}
}
changeTheme(){
if (this.state.themeType == 'dark'){
this.setState({themeType:'light'});
} else {
this.setState({themeType:'dark'});
}
}
render() {
let theme = createMuiTheme({
palette: {
primary: {
light: lightGreen[300],
main: lightGreen[500],
dark: lightGreen[700]
},
secondary: {
light: blueGrey[300],
main: blueGrey[500],
dark: blueGrey[700]
},
type: this.state.themeType
}
});
return (
<MuiThemeProvider theme={theme}>
<CssBaseline />
<Typography>Hi there!</Typography>
<Button
variant="contained"
color="secondary"
onClick={()=>{this.changeTheme()}}
>
Change
</Button>
</MuiThemeProvider>
);
}
}
export default App;

implement BackButton in react-admin

I need to implement a <BackButton /> in react-admin for example when I open show page for a resource I need able to back to the list page.
Can you guide me to implement this?
I'm not familiar with react-admin routing mechanism.
Now I'm using this component in my edit form actions props:
const MyActions = ({ basePath, data, resource }) => (
<CardActions>
<ShowButton basePath={basePath} record={data} />
<CloneButton basePath={basePath} record={data} />
{/* Need <BackButton /> here */}
</CardActions>
);
export const BookEdit = (props) => (
<Edit actions={<MyActions />} {...props}>
<SimpleForm>
...
</SimpleForm>
</Edit>
);
You can use react-router-redux's goBack() function to achieve this.
For example, your button component will look something like this:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Button from '#material-ui/core/Button';
import { goBack } from 'react-router-redux';
class BackButton extends Component {
handleClick = () => {
this.props.goBack();
};
render() {
return <Button variant="contained" color="primary" onClick={this.handleClick}>Go Back</Button>;
}
}
export default connect(null, {
goBack,
})(BackButton);
Now use that button component in your CardActions.
You can get help from an example which uses react-router-redux's push() function in a similar way from the official docs.
Create a back button. This one will pass props and children (text) and uses react-router directly, which I think makes more sense and keeps your code simple.
// BackButton.js
import React from 'react'
import Button from '#material-ui/core/Button'
import { withRouter } from 'react-router'
const BackButton = ({ history: { goBack }, children, ...props }) => (
<Button {...props} onClick={goBack}>
{children}
</Button>
)
export default withRouter(BackButton)
Example usage:
import { Toolbar, SaveButton } from 'react-admin'
import BackButton from './BackButton'
const SomeToolbar = props => (
<Toolbar {...props}>
<SaveButton />
<BackButton
variant='outlined'
color='secondary'
style={{ marginLeft: '1rem' }}
>
Cancel
</BackButton>
</Toolbar>
)
The complete code is below.
//BackButton.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import compose from 'recompose/compose';
import { withStyles, createStyles } from '#material-ui/core/styles';
import { translate } from 'ra-core';
import Button from '#material-ui/core/Button';
import ArrowBack from '#material-ui/icons/ArrowBack';
import classnames from 'classnames';
import { fade } from '#material-ui/core/styles/colorManipulator';
const styles = theme =>
createStyles({
deleteButton: {
color: theme.palette.error.main,
'&:hover': {
backgroundColor: fade(theme.palette.error.main, 0.12),
// Reset on mouse devices
'#media (hover: none)': {
backgroundColor: 'transparent',
},
},
},
});
const sanitizeRestProps = ({
basePath,
className,
classes,
label,
invalid,
variant,
translate,
handleSubmit,
handleSubmitWithRedirect,
submitOnEnter,
record,
redirect,
resource,
locale,
...rest
}) => rest;
class BackButton extends Component {
static contextTypes = {
router: () => true, // replace with PropTypes.object if you use them
}
static propTypes = {
label: PropTypes.string,
refreshView: PropTypes.func.isRequired,
icon: PropTypes.element,
};
static defaultProps = {
label: 'ra.action.back',
icon: <ArrowBack />,
};
render() {
const {
className,
classes = {},
invalid,
label = 'ra.action.back',
pristine,
redirect,
saving,
submitOnEnter,
translate,
icon,
onClick,
...rest
} = this.props;
return (
<Button
onClick={this.context.router.history.goBack}
label={label}
className={classnames(
'ra-back-button',
classes.backButton,
className
)}
key="button"
{...sanitizeRestProps(rest)}>
{icon} {label && translate(label, { _: label })}
</Button>
)
}
}
const enhance = compose(
withStyles(styles),
translate
);
export default enhance(BackButton);
//Toolbar.js
import React from 'react';
import {
Toolbar,
SaveButton,
DeleteButton,
} from 'react-admin';
import { withStyles } from '#material-ui/core';
import BackButton from './BackButton'
const toolbarStyles = {
toolbar: {
display: 'flex',
justifyContent: 'space-between',
},
};
export const CustomEditToolbar = withStyles(toolbarStyles)(props => (
<Toolbar {...props}>
<SaveButton/>
<DeleteButton/>
<BackButton/>
</Toolbar>
));
export const CustomCreateToolbar = withStyles(toolbarStyles)(props => (
<Toolbar {...props}>
<SaveButton/>
<BackButton/>
</Toolbar>
));

Resources