Currently this is what I am doing, passing a ThemeProvider above my component file:
import React from 'react';
import { ThemeProvider, createMuiTheme } from '#material-ui/core/styles';
import MUIButton from '#material-ui/core/Button';
const theme = createMuiTheme({
palette: {
primary: {
main: "#ff0000"
}
},
typography: {
fontFamily: 'Nunito Sans, sans-serif',
button: {
textTransform: 'none'
}
},
shape: {
borderRadius: 3
}
})
export default ({ variant, children }) => {
return (
<ThemeProvider theme={theme}>
<MUIButton
color="primary"
variant={variant}
>
{children}
</MUIButton>
</ThemeProvider>
)
}
I am trying to figure out how I can do this at a global level in Storybook. This is the first component I have built out called Button. So I want to be able to have the theme in an external file, and have the ThemeProvider coming in at a higher level so I don't have to wrap each component. Hope that makes sense, and if anyone has any ideas.
First, I suggest you to move your theme into a separate file (such as src/stylesheet so you can access it from different files (your global App component and your storybook preview file).
// src/stylesheet.ts
import { createMuiTheme } from '#material-ui/core/styles';
export const muiTheme = createMuiTheme({
palette: {
primary: {
main: "#ff0000"
}
},
typography: {
fontFamily: 'Nunito Sans, sans-serif',
button: {
textTransform: 'none'
}
},
shape: {
borderRadius: 3
}
})
Then, you need to setup your storybook the same way you set up your react app. To do so, try to create a file called:
.storybook/preview.js and put this inside of it:
// .storybook/preview.js
import React from 'react';
import { addDecorator } from '#storybook/react';
import { ThemeProvider } from '#material-ui/core/styles';
import { muiTheme } from '../src/stylesheet';
addDecorator((story) => (
<ThemeProvider theme={muiTheme}>{story()}</ThemeProvider>
));
It will wrap all your stories inside of the ThemeProvider.
In your app, you can can also have a global App component which will encapsulate the whole app within the ThemeProvider
More help:
https://storybook.js.org/docs/basics/writing-stories/
This is the only solution that worked for me with MUI v5:
// .storybook/main.js
const path = require('path');
const toPath = (filePath) => path.join(process.cwd(), filePath);
module.exports = {
webpackFinal: async (config) => {
return {
...config,
resolve: {
...config.resolve,
alias: {
...config.resolve.alias,
'#emotion/core': toPath('node_modules/#emotion/react'),
'emotion-theming': toPath('node_modules/#emotion/react'),
},
},
};
},
};
// .storybook/preview.js
import { ThemeProvider, createTheme } from '#mui/material/styles';
import { ThemeProvider as Emotion10ThemeProvider } from 'emotion-theming';
const defaultTheme = createTheme(); // or your custom theme
const withThemeProvider = (Story, context) => {
return (
<Emotion10ThemeProvider theme={defaultTheme}>
<ThemeProvider theme={defaultTheme}>
<Story {...context} />
</ThemeProvider>
</Emotion10ThemeProvider>
);
};
export const decorators = [withThemeProvider];
// ...other storybook exports
It's from the MUI migration guide but it took me unnecessarily long to find so I want to share it here:
https://mui.com/guides/migration-v4/
For Storybook 6.3+ it should look like the following
Update preview.js with:
import React from 'react';
import { theme } from './src/theme'; // whereever you have defined your material ui theme
export const decorators = [
Story => (
<ThemeProvider theme={theme}>
<Story />
</ThemeProvider>
),
];
More details on how to decorate your stories here:
https://storybook.js.org/docs/react/writing-stories/decorators
And creating your theme for Material UI here:
https://material-ui.com/customization/theming/
This works for me with react-router and mui V5
my preview.js =>
import React from 'react';
import { addDecorator } from '#storybook/react';
import { MemoryRouter } from 'react-router';
import { ThemeProvider } from '#mui/material/styles';
import { ThemeProvider as Emotion10ThemeProvider } from 'emotion-theming';
import customTheme from '../src/theme/index';
addDecorator((story) => (
<Emotion10ThemeProvider theme={customTheme}>
<ThemeProvider theme={customTheme}>
<MemoryRouter initialEntries={['/']}>{story()}</MemoryRouter>
</ThemeProvider>
</Emotion10ThemeProvider>
));
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};
I believe this is the most up to date answer... and the cleanest, updating path/to/your/Theme with your path
// .storybook/preview.js
import React from 'react';
import { ThemeProvider } from '#mui/system';
import { Theme } from 'path/to/your/Theme';
export const decorators = [
(Story) => (
<ThemeProvider theme={Theme}>
<Story />
</ThemeProvider>
),
];
Related
I have followed the documentation of chakra UI and am trying to style my website. For some reason the config part for the dark/light mode works but the theme part does not work.
I have used create-react-app
This is my Index.js code-
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { AuthProvider } from './Contexts/AuthProvider';
import { ChakraProvider, theme } from '#chakra-ui/react'
import defaultTheme from "./Themes/stylesEntryPoint"
import { ColorModeScript } from '#chakra-ui/react'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ChakraProvider theme={defaultTheme}>
<AuthProvider>
<ColorModeScript initialColorMode={theme.config.initialColorMode} />
<App />
</AuthProvider>
</ChakraProvider>
);
This is the code for stylesEntryPoint file
import { extendTheme } from '#chakra-ui/react'
import {theme,config} from "./FoundationTheme/globalStyles"
const defaultTheme = {
theme , config
}
export default extendTheme(defaultTheme)
This is the code for globalStyles... I have copied it from chakra UI documentation here https://chakra-ui.com/docs/styled-system/global-styles
import { mode } from '#chakra-ui/theme-tools'
import { extendTheme } from '#chakra-ui/react'
export const config = {
initialColorMode: 'dark',
useSystemColorMode: true
}
export const theme = {
styles: {
global: (props) => ({
'html, body': {
fontSize: 'lg',
color: props.colorMode === 'dark' ? 'pink.100' : 'gray.600',
lineHeight: 'tall',
},
a: {
color: props.colorMode === 'dark' ? 'teal.300' : 'teal.500',
},
}),
},
}
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);
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);
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;
I'm learning React with TypeScript and using the Material UI framework for the frontend. I try to get the media queries working, but I got an error:
Uncaught TypeError: Cannot read property 'up' of undefined
at styles (webpack-internal:///./app/components/navigation/Toolbar/index.tsx:59)
This is the corresponding code:
const styles = ({breakpoints}: Theme) => createStyles( {
grow: {
flexGrow: 1
},
menuButton: {
marginLeft: -12,
marginRight: 20
},
sectionDesktop: {
display: 'none',
[breakpoints.up('md')]: {
display: 'flex'
}
},
sectionMobile: {
display: 'flex'
},
})
The styles are passed to the component with:
export default withStyles(styles)(Toolbar)
I read that it is not required to create a custom theme as the default one will be passed automatically to the functions. However, the breakpoints property of theme is undefined which cause a blank page.
Thanks for your help
Edit
Here is the code of the component which will still produce the problem without any other components.
import * as React from 'react'
import {
Theme
} from '#material-ui/core'
import {
createStyles,
WithStyles,
withStyles
} from '#material-ui/styles'
// import Drawer from '../Drawer'
const styles = ({breakpoints}: Theme) => createStyles( {
grow: {
flexGrow: 1
},
menuButton: {
marginLeft: -12,
marginRight: 20
},
sectionDesktop: {
display: 'none',
[breakpoints.up('md')]: {
display: 'flex'
}
},
sectionMobile: {
display: 'flex'
},
})
namespace Toolbar {
interface Props {
}
export interface State {
isOpen : boolean
mobileMoreAnchorEl? : EventTarget & HTMLElement
}
export type AllProps = Props & WithStyles<typeof styles>
}
class Toolbar extends React.Component<Toolbar.AllProps, Toolbar.State> {
constructor(props: Toolbar.AllProps, context?: any) {
super(props, context);
this.state = { isOpen: false, mobileMoreAnchorEl: undefined}
}
render() {
const { classes } = this.props
// const { isOpen } = this.state
return(
<React.Fragment>
<div className={classes.sectionDesktop}>
Hello
</div>
<div className={classes.sectionMobile}>
World
</div>
</React.Fragment>
)
}
}
export default withStyles(styles)(Toolbar)
The main.tsx (a.k.a index.js) looks like this:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createBrowserHistory } from 'history';
import { configureStore } from 'app/store';
import { Router } from 'react-router';
import { MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles';
import { App } from './app';
// prepare store
const history = createBrowserHistory()
const store = configureStore()
const theme = createMuiTheme()
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<MuiThemeProvider theme={theme} >
<App />
</MuiThemeProvider>
</Router>
</Provider>,
document.getElementById('root')
);
So, adding MuiThemeProvider does not help.
UPDATE
At the time when this answer was first written, #material-ui/styles was unstable. It is not anymore (as of v4), but it is still generally best to import from #material-ui/core/styles since the default theme will not be available when importing from #material-ui/styles.
You can read here that #material-ui/styles is unstable (alpha version).
You'll notice in my CodeSandbox that I am using:
import { withStyles, createStyles } from "#material-ui/core/styles";
instead of importing these from #material-ui/styles. When I use the same import as you, I am able to reproduce your problem.
UPDATE for v5
In v5, usage of makeStyles and withStyles is deprecated and they were removed from #material-ui/core and are only accessible via #material-ui/styles. In v5, makeStyles and withStyles do not have access to the theme unless you provide access via the ThemeProvider (and for that to work you should be using the latest v5 versions of both #material-ui/core and #material-ui/styles). Below is a working v5 example.
import React from "react";
import ReactDOM from "react-dom";
import { createTheme, ThemeProvider } from "#material-ui/core/styles";
import { withStyles } from "#material-ui/styles";
const styles = ({ breakpoints }) => ({
grow: {
flexGrow: 1
},
menuButton: {
marginLeft: -12,
marginRight: 20
},
sectionDesktop: {
display: "none",
[breakpoints.up("md")]: {
display: "flex"
}
},
sectionMobile: {
display: "flex"
}
});
const MyToolbar = (props) => {
return (
<>
<div className={props.classes.sectionDesktop}>Section Desktop</div>
{props.children}
<div className={props.classes.sectionMobile}>Section Mobile</div>
</>
);
};
const theme = createTheme();
const StyledToolbar = withStyles(styles)(MyToolbar);
function App() {
return (
<ThemeProvider theme={theme}>
<StyledToolbar>
<div>Hello</div>
<div>CodeSandbox</div>
</StyledToolbar>
</ThemeProvider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);