I'm new and trying a project to make a switch that toggles between two themes;
Here is what I have so far and I'm struggling to understand why it's not toggling between my themes.
theme.js
import { createContext } from "react";
export const themes = {
dark: "dark-mode",
light: "white-mode",
};
export const ThemeContext = createContext({
theme: themes.dark,
changeTheme: () => {},
});
themeWrapper.js
import React, { useState, useEffect } from 'react';
import { ThemeContext, themes } from './theme.js';
import "./App.css"
export default function ThemeContextWrapper(props) {
const [theme, setTheme] = useState(themes.dark);
function changeTheme(theme) {
setTheme(theme);
}
useEffect(() => {
switch (theme) {
case themes.light:
document.body.classList.add('white-mode');
break;
case themes.dark:
default:
document.body.classList.remove('white-mode');
break;
}
}, [theme]);
return (
<ThemeContext.Provider value={{ theme: theme, changeTheme: changeTheme }}>
{props.children}
</ThemeContext.Provider>
);
}
So I've got a wrapper to re-render the app when dark mode is toggled, and two themes that are being exported.
App.css
.white-mode {
font: #333;
background: #eee;
link: cornflowerblue;
}
.dark-mode {
font: #eee;
background: rgb(41, 41, 41);
link: lightblue;
}
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import ThemeContextWrapper from '.themeWrapper';
ReactDOM.render(
<ThemeContextWrapper>
<React.StrictMode>
<App />
</React.StrictMode>{' '}
</ThemeContextWrapper>,
document.getElementById('root'),
);
So now in App.js I have the button
import './App.css';
import { Button, Container, InputGroup } from 'reactstrap';
import { ThemeContext, themes } from '.theme';
import React from 'react';
function App() {
const [darkMode, setDarkMode] = React.useState(true);
return (
<div className="App">
<header className="App-header">
<h1 className="text-warning">Dark/Light mode</h1>
<InputGroup>
<ThemeContext.Consumer>
{({ changeTheme }) => (
<Button
color="link"
onClick={() => {
setDarkMode(!darkMode);
changeTheme(darkMode ? themes.light : themes.dark);
}}
>
<span className="d-lg-none d-md-block">Toggle</span>
</Button>
)}
</ThemeContext.Consumer>
</InputGroup> */}
</header>
</div>
);
}
export default App;
But this button doesn't appear to do anything, where have I gone wrong?
Related
Here is my small sandbox only with Material-UI:
In this case my styling don't work as i expect
import React from 'react'
import { Typography } from "#mui/material";
import { makeStyles } from "#mui/styles";
const useStyles = makeStyles({
text: {
fontSize: 30,
},
});
function App() {
const classes = useStyles();
return (
<div className="App">
<Typography className={classes.text}>Hello world</Typography>
</div>
);
}
export default App
Result (https://ibb.co/S74pq0m)
I think you are not using material UI theme structure or are not aware of it so try to read about createTheme so you can use it globally https://mui.com/customization/typography/
import React from "react";
import { ThemeProvider, createMuiTheme } from "#mui/material";
import { Typography } from "#mui/material";
const theme = createMuiTheme({
typography: {
body2: {
fontSize: 30
}
},
});
export default function App() {
return (
<ThemeProvider theme={theme}>
<div className="App">
<Typography variant="body2">Hello World</Typography>
</div>
</ThemeProvider>
);
}
I keep getting an error when I try to use Context API for dark/light mode in App.js
Theme.js
import React, { useState } from "react";
export const ThemeContext = React.createContext();
//theme = light, dark
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState("dark");
const toggleTheme = () => {
if (theme === "light") setTheme("dark");
else setTheme("light");
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
App.js
import { ThemeContext, ThemeProvider } from "./app/utility/ThemeManager";
export default function App() {
const { theme } = useContext(ThemeContext); // This is throwing the error
return (
<ThemeProvider>
...//Rest of my app
//How I'd like to use my theme
<StatusBar style={theme === "dark" ? "light" : "dark"} />
</ThemProvider>
);
};
I'd like to understand why this is throwing the error and how I could fix it?
Thanks in advance!
Import ThemeProvider in index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { ThemeProvider } from "./Theme.js";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<ThemeProvider>
<App />
</ThemeProvider>,
rootElement
);
and in App.js
import {ThemeContext} from "./Theme.js"
import StatusBar from './StatusBar';
import {useContext} from 'react'
export default function App() {
const { theme } = useContext(ThemeContext);
return (
<>
..... Rest Of your Code
</>
);
};
I'm trying to do this:
import { extendTheme } from "#chakra-ui/react";
import Fonts from "./Fonts";
const theme = extendTheme({
fonts: {
logo: "Satisfy",
},
colors: {
main: {
500: "red.500",
},
},
});
but when using it like this it doesn't work(main.500 is not recognized):
function Logo() {
return (
<Center as={(props) => <Link {...props} to="/" />} h="full" p="10">
<Heading
fontFamily="logo"
as="h1"
fontSize="4xl"
fontWeight="bold"
color="main.500"
>
My Recipes
</Heading>
</Center>
);
}
What is the correct approach? thanks.
Here's an example of how to use the default colors:
App.js
import React from 'react';
import ReactDOM from 'react-dom';
import { ChakraProvider } from "#chakra-ui/react";
import theme, { Theme } from "#chakra-ui/theme";
import { extendTheme } from "#chakra-ui/react";
import Chakra from "./Test-Chakra.js"
export default function App() {
const theme = extendTheme({
fonts: {
logo: "Satisfy",
},
colors: {
main: {
500: "#00dd00",
900: "#dd3333",
},
},
})
return (
<>
<ChakraProvider theme={theme}>
<Chakra />
</ChakraProvider>
</>
);
}
ReactDOM.render(<App />,
document.getElementById('root')
);
Test-Chakra.js - using this code you can now change the color to main.500 (custom color) or red.500 (default color) as you wish.
import React from 'react';
import { Center, Heading } from "#chakra-ui/react"
import theme, { Theme } from "#chakra-ui/theme"
export default function Chakra(props) {
return (
<>
<Center >
<Heading
fontFamily="logo"
as="h1"
fontSize="4xl"
fontWeight="bold"
color="red.500"
>
My Recipes
</Heading>
</Center>
</>
);
}
I'm using the Context API to store the theme value. The theme itself is created with <createMuiTheme> and passed down from <Layout> to children via <MuiThemeProvider> and <CssBaseline>. I can see the state change via React DevTools but the theme itself is not being applied - and I'm at loss as to why...
Here is codesandbox with a full example - warning: contains Samuel L. Ipsum. What follows is an abridged version.
Default and dark theme definitions:
// theme/dark.js
import { createMuiTheme } from "#material-ui/core/styles";
const theme = createMuiTheme({
typography: {
useNextVariants: true
},
palette: {
type: "dark"
}
});
export default theme;
// theme/default.js
import { createMuiTheme } from "#material-ui/core/styles";
const theme = createMuiTheme({
typography: {
useNextVariants: true
},
palette: {
type: "light"
}
});
export default theme;
Context:
// context/settings/SettingsContext.js
import React from "react";
export default React.createContext({
darkMode: false
});
// context/settings/SettingsProvider.js
import React, { useState } from "react";
import SettingsContext from "./SettingsContext";
const storage = {
getItem(key) {
if (localStorage) {
return localStorage.getItem(key);
}
},
setItem(key, value) {
if (localStorage) {
return localStorage.setItem(key, value);
}
}
};
const SettingsProvider = props => {
const [darkMode, setDarkMode] = useState(
storage.getItem("darkMode") === "true"
);
const onSetDarkMode = darkMode => {
setDarkMode(darkMode);
storage.setItem("darkMode", darkMode);
};
return (
<SettingsContext.Provider
value={{
darkMode,
onSetDarkMode
}}
>
{props.children}
</SettingsContext.Provider>
);
};
export default SettingsProvider;
index.js:
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./containers/app";
import SettingsProvider from "./context/settings/SettingsProvider";
ReactDOM.render(
<BrowserRouter>
<SettingsProvider>
<App />
</SettingsProvider>
</BrowserRouter>,
document.getElementById("root")
app/index.js:
import React, { useState } from "react";
import { Switch, Route } from "react-router-dom";
import { default as home } from "../home/routes";
import Layout from "../layout";
const App = () => {
const [anchorEl, setAnchorEl] = useState(null);
return (
<div>
<Layout anchorEl={anchorEl} setAnchorEl={setAnchorEl}>
<Switch>
{home.map((route, index) => (
<Route
key={index}
path={route.path}
exact={route.exact}
render={route.render}
/>
))}
</Switch>
</Layout>
</div>
);
};
export default App;
And layout/index.js:
import React, { useContext } from "react";
import { MuiThemeProvider } from "#material-ui/core/styles";
import { makeStyles } from "#material-ui/core/styles";
import CssBaseline from "#material-ui/core/CssBaseline";
import defaultTheme from "../../themes/default";
import darkTheme from "../../themes/default";
import SettingsContext from "../../context/settings/SettingsContext";
import Header from "../../components/header/index";
const useStyles = makeStyles(theme => ({
toolbarMargin: {
...theme.mixins.toolbar
}
}));
const Layout = props => {
const classes = useStyles();
const context = useContext(SettingsContext);
const theme = context.darkMode ? darkTheme : defaultTheme;
const { children, anchorEl, setAnchorEl } = props;
return (
<MuiThemeProvider theme={theme}>
<CssBaseline />
<Header anchorEl={anchorEl} setAnchorEl={setAnchorEl} />
<main>
<div className={classes.toolbarMargin} />
{children}
</main>
</MuiThemeProvider>
);
};
export default Layout;
What did I miss?
You're importing the same theme twice. I'd suggest using named exports instead of defaults, it helps a lot with auto importing, as well as spotting mistakes like this.
// layout/index.js
import defaultTheme from "../../themes/default";
import darkTheme from "../../themes/default"; // should be "../../theme/dark"
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;