I am trying to implement a theme toggle feature in my React application using the ThemeContext and the useState hook. I have created two themes, "light" and "dark", and a ThemeContext object to make the current theme available to all child components. I have also created an App component that wraps the provided component with the ThemeContext.Provider and a toggle button to switch between the two themes.
The problem is that, when I click the toggle button, the theme is not changing correctly. The button click seems to be registered, but the theme is not updating. I have tried different approaches, but I can't seem to find the issue.
Here is my _app.js file:
`
import { ThemeContext } from '../behaviour/context';
import { useState } from 'react';
function App({ Component, pageProps }) {
const [currentTheme, setCurrentTheme] = useState(ThemeContext.themes.light);
function toggleTheme() {
setCurrentTheme(currentTheme === ThemeContext.themes.light ? ThemeContext.themes.dark :ThemeContext.themes.light);
}
return (
<ThemeContext.Provider value={currentTheme}>
<button onClick={toggleTheme}>Toggle theme</button>
<Component {...pageProps} />
</ThemeContext.Provider>
);
}
export default App;
`
And here is my context.js file defined inside of a folder named behaviour:
`
import { createContext } from 'react';
export const themes = {
light: {
background: 'white',
text: 'black'
},
dark: {
background: 'black',
text: 'white'
}
};
export const ThemeContext = createContext(themes);
`
Please let me know if you see anything wrong with my code or if you have any suggestions for how to fix this issue.
I tried to use createGlobalStyle from styled-components but I confused myself so I removed it.
Related
Antd 5.0 has been introduced their new theme system.
But I wonder how to access to those design tokens of theme when I declare a component by style-component.
normally I declare my component like this.
const MyComponent = styled.button`
color:${props=> props.theme.color.primary};
`;
theme here is getting from ThemeProvider of styled component that is defined in App.jsx
<ThemeProvider theme={baseTheme}>
<App/>
</ThemeProvider>
So, theme only can access to design tokens that were defined in theme file.
How can I access other tokens of Antd theme?
One way I'm thinking is creating a theme that overrides every single design token of Antd. But I think that's a bad idea
From the documentation consume-design-token, you can get and consume the token by using the theme.useToken() hook.
Create a ThemeProvider get the antd theme token and combine it with base theme, then pass the combined theme to the ThemeProvider of styled-components.
Then you can get the combined theme via passed props of styled-components.
theme-provider.tsx:
import { ThemeProvider } from "styled-components";
import { theme } from "antd";
import React from "react";
export default ({ children }: React.PropsWithChildren) => {
const { token } = theme.useToken();
return (
<ThemeProvider theme={{ antd: token, base: { color: "mediumseagreen" } }}>
{children}
</ThemeProvider>
);
};
App.tsx:
import styled from "styled-components";
import { ConfigProvider } from "antd";
import ThemeProvider from "./theme-provider";
const Button = styled.button`
color: ${(props) => {
console.log("props.theme: ", props.theme);
return props.theme.antd.colorPrimary;
}};
`;
export default function App() {
return (
<ConfigProvider
theme={{
token: {
colorPrimary: "red"
}
}}
>
<ThemeProvider>
<Button>Hello World</Button>
</ThemeProvider>
</ConfigProvider>
);
}
The log:
props.theme: {antd: Object, base: Object}
codesandbox
I'm using styled-components in my React app and wanting to use a dynamic theme. Some areas it will use my dark theme, some will use the light. Because the styled components have to be declared outside of the component they are used in, how do we pass through a theme dynamically?
That's exactly what the ThemeProvider component is for!
Your styled components have access to a special theme prop when they interpolate a function:
const Button = styled.button`
background: ${props => props.theme.primary};
`
This <Button /> component will now respond dynamically to a theme defined by a ThemeProvider. How do you define a theme? Pass any object to the theme prop of the ThemeProvider:
const theme = {
primary: 'palevioletred',
};
<ThemeProvider theme={theme}>
<Button>I'm now palevioletred!</Button>
</ThemeProvider>
We provide the theme to your styled components via context, meaning no matter how many components or DOM nodes are in between the component and the ThemeProvider it'll still work exactly the same:
const theme = {
primary: 'palevioletred',
};
<ThemeProvider theme={theme}>
<div>
<SidebarContainer>
<Sidebar>
<Button>I'm still palevioletred!</Button>
</Sidebar>
</SidebarContainer>
</div>
</ThemeProvider>
This means you can wrap your entire app in a single ThemeProvider, and all of your styled components will get that theme. You can swap that one property out dynamically to change between a light and a dark theme!
You can have as few or as many ThemeProviders in your app as you want. Most apps will only need one to wrap the entire app, but to have a part of your app be light themed and some other part dark themed you would just wrap them in two ThemeProviders that have different themes:
const darkTheme = {
primary: 'black',
};
const lightTheme = {
primary: 'white',
};
<div>
<ThemeProvider theme={lightTheme}>
<Main />
</ThemeProvider>
<ThemeProvider theme={darkTheme}>
<Sidebar />
</ThemeProvider>
</div>
Any styled component anywhere inside Main will now be light themed, and any styled component anywhere inside Sidebar will be dark themed. They adapt depending on which area of the application they are rendered in, and you don't have to do anything to make it happen! 🎉
I encourage you to check out our docs about theming, as styled-components was very much built with that in mind.
One of the big pain points of styles in JS before styled-components existed was that the previous libraries did encapsulation and colocation of styles very well, but none of them had proper theming support. If you want to learn more about other pain points we had with existing libraries I'd encourage you to watch my talk at ReactNL where I released styled-components. (note: styled-components' first appearance is at ~25 minutes in, don't be surprised!)
While this question was originally for having multiple themes running at the same time, I personally wanted to dynamically switch in runtime one single theme for the whole app.
Here's how I achieved it: (I'll be using TypeScript and hooks in here. For plain JavaScript just remove the types, as, and interface):
I have also included all the imports at the top of each block code just in case.
We define our theme.ts file
//theme.ts
import baseStyled, { ThemedStyledInterface } from 'styled-components';
export const lightTheme = {
all: {
borderRadius: '0.5rem',
},
main: {
color: '#FAFAFA',
textColor: '#212121',
bodyColor: '#FFF',
},
secondary: {
color: '#757575',
},
};
// Force both themes to be consistent!
export const darkTheme: Theme = {
// Make properties the same on both!
all: { ...lightTheme.all },
main: {
color: '#212121',
textColor: '#FAFAFA',
bodyColor: '#424242',
},
secondary: {
color: '#616161',
},
};
export type Theme = typeof lightTheme;
export const styled = baseStyled as ThemedStyledInterface<Theme>;
Then in our main entry, in this case App.tsx we define the <ThemeProvider> before every component that's going to use the theme.
// app.tsx
import React, { memo, Suspense, lazy, useState } from 'react';
import { Router } from '#reach/router';
// The header component that switches the styles.
import Header from './components/header';
// Personal component
import { Loading } from './components';
import { ThemeProvider } from 'styled-components';
// Bring either the lightTheme, or darkTheme, whichever you want to make the default
import { lightTheme } from './components/styles/theme';
// Own code.
const Home = lazy(() => import('./views/home'));
const BestSeller = lazy(() => import('./views/best-seller'));
/**
* Where the React APP main layout resides:
*/
function App() {
// Here we set the default theme of the app. In this case,
// we are setting the lightTheme. If you want the dark, import the `darkTheme` object.
const [theme, setTheme] = useState(lightTheme);
return (
<Suspense fallback={<Loading />}>
<ThemeProvider theme={theme}>
<React.Fragment>
{/* We pass the setTheme function (lift state up) to the Header */}
<Header setTheme={setTheme} />
<Router>
<Home path="/" />
<BestSeller path="/:listNameEncoded" />
</Router>
</React.Fragment>
</ThemeProvider>
</Suspense>
);
}
export default memo(App);
And in header.tsx we pass the setTheme to the component (Lifting the state up):
// header.tsx
import React, { memo, useState } from 'react';
import styled, { ThemedStyledInterface } from 'styled-components';
import { Theme, lightTheme, darkTheme } from '../styles/theme';
// We have nice autocomplete functionality
const Nav = styled.nav`
background-color: ${props => props.theme.colors.primary};
`;
// We define the props that will receive the setTheme
type HeaderProps = {
setTheme: React.Dispatch<React.SetStateAction<Theme>>;
};
function Header(props:
function setLightTheme() {
props.setTheme(lightTheme);
}
function setDarkTheme() {
props.setTheme(darkTheme);
}
// We then set the light or dark theme according to what we want.
return (
<Nav>
<h1>Book App</h1>
<button onClick={setLightTheme}>Light </button>
<button onClick={setDarkTheme}> Dark </button>
</Nav>
);
}
export default memo(Header);
Here's something that did the job for me:
import * as React from 'react';
import { connect } from 'react-redux';
import { getStateField } from 'app/redux/reducers/recordings';
import { lightTheme, darkTheme, ThemeProvider as SCThemeProvider } from 'app/utils/theme';
import { GlobalStyle } from 'app/utils/globalStyles';
interface ThemeProviderProps {
children: JSX.Element;
isLightMode?: boolean;
}
const ThemeProvider = ({ children, isLightMode }: ThemeProviderProps) => {
return (
<SCThemeProvider theme={isLightMode ? lightTheme : darkTheme}>
<React.Fragment>
{children}
<GlobalStyle />
</React.Fragment>
</SCThemeProvider>
);
};
export const ConnectedThemeProvider = connect((state) => ({
isLightMode: getStateField('isLightMode', state)
}))(ThemeProvider);
I tried using different properties in the sidebar style to override the link color but nothing is working.
Default style/color of Sidebar drawer (before overriding the style)
MySidebar.js
// MySidebar.js
import { Sidebar } from 'react-admin';
import { makeStyles } from '#material-ui/core/styles';
import React from 'react';
const useSidebarStyles = makeStyles({
drawerPaper: {
backgroundColor: '#0c2d48',
color: '#fff',
},
});
const MySidebar = props => {
const classes = useSidebarStyles();
return (
<Sidebar classes={classes} {...props} />
);
};
export default MySidebar;
MyLayout.js
// MyLayout.js
import React from 'react';
import { Layout } from 'react-admin';
import MySidebar from './MySidebar';
const MyLayout = props => (
<Layout
{...props}
sidebar={MySidebar}
/>
);
export default MyLayout;
Result (after overriding the default style in MySidebar.js)
As you can see, I'm able to change bg color of the sidebar but not the link colors.
Please help. It's driving me crazy!
You can create your own theme as described here:
https://marmelab.com/react-admin/Theming.html#writing-a-custom-theme
and in your theme redefine the colors of all MenuItemLink components:
export const lightTheme = {
...
overrides: {
RaMenuItemLink: {
root: {
color: "#c51162",
},
active: {
color: "#ff4081",
},
},
},
}
I can see that you are using React-Admin for the sidebar and not the Material UI sidebar directly. So possibly the React-Admin template modified the props of original material Ui and you should take a look inside the react admin sidebar and modify it directly there to get the results. If you apply material UI customization on this one, it might be the reason its not working.
Alternative is to try Material UI Themeing with ThemeProvider and apply the theme, which can overwrite the current styling.
Try changing link colors.
drawerPaper: {
backgroundColor: '#0c2d48',
color: '#fff',
},
link: {
color: '#fff',
},
Update: Instead of link you can give it the css class name, by looking at inspect element on the ouput sidebar.
I am looking for something like ThemeConsumer (which probably doesn't exist). I've React component and I am using withStyles() higher-order component to inject custom styles. It's pretty well described in documentation but I didn't find any example which uses theme.
I have some base component which contains ThemeProvider. It means any of MUI components are being affected by it.
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const theme = getTheme(prefersDarkMode);
return (
<ThemeProvider theme={theme}>
...
</ThemeProvider>
)
I also use some functional components with makeStyles() to create styles with provided theme.
const useStyles = makeStyles(theme => ({
// here I can use theme provided by ThemeProvider
});
But it can't be used in class components. So I am using withStyles() HOC.
const styles = {
// I would like to use here provided theme too
}
export default withStyles(styles)(SomeComponent);
Summary of my question:
How do I use provided theme in class component?
withStyles supports similar syntax as makeStyles:
const styles = theme => ({
// here I can use theme provided by ThemeProvider
});
export default withStyles(styles)(SomeComponent);
Here's a simple working example:
import React from "react";
import { withStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
const StyledPaper = withStyles(theme => ({
root: {
backgroundColor: theme.palette.secondary.main
}
}))(Paper);
export default function App() {
return (
<StyledPaper className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</StyledPaper>
);
}
A component decorated with withStyles(styles) gets a special classes prop injected, which may be used for your custom styles. For example:
import { Box } from "#material-ui/core"
import { withStyles } from "#material-ui/core/styles"
const styles = theme => ({
myCustomClass: {
color: theme.palette.tertiary.dark
}
})
class myComponent extends Component {
render() {
const { classes, theme } = this.props
// In last line, you see we have passed `{ withTheme: true }` option
// to have access to the theme variable inside the class body. See it
// in action in next line.
return <Box className={classes.myCustomClass} padding={theme.spacing(4)} />
}
}
export default withStyles(styles, { withTheme: true })(myComponent)
If you pass { withTheme: true } option to withStyles HOC, you'll get the theme variable injected as a prop also.
If you have other HOCs (e.g. Redux's connect, Router, etc) applied to your component, you may use it like this:
export default withStyles(styles, { withTheme: true })(
withRouter(connect(mapStateToProps)(myComponent))
)
A more comprehensive explanation for this topic could be found in this article: Using Material UI theme variable in React Function and Class Components.
Use withTheme HOC
modified example from docs
import { withTheme } from '#material-ui/core/styles';
class DeepChildRaw
{
/*...*/
render()
{
return <span>{`spacing ${this.props.theme.spacing}`}</span>;
}
}
const DeepChild = withTheme(DeepChildRaw);
if are working with Class Components, you can use like here ;)
import React from 'react';
import Routes from './Routes';
import '../custom.css';
import { BrowserRouter } from 'react-router-dom';
import { MuiThemeProvider, createTheme } from '#material-ui/core/styles';
const theme = createTheme({
palette: {
primary: {
main: '#fff'
},
secondary: {
main: '#351436'
}
}
});
class App extends React.Component {
render() {
return (
<div className="App">
<MuiThemeProvider theme={theme}>
<BrowserRouter>
<Routes />
</BrowserRouter>
</MuiThemeProvider>
</div>
)
}
}
export default App;
I want to reuse components of a react project that are transpiled with roll-up. Each of the components have the "makeStyles" function of material-ui to be customized from where they are called.
I currently have a project and I want to use these components, but despite the fact that I include ThemeProvider, I am not reading properties such as color palettes, and use the one with the default material-ui.
I have the reusable component project at the root of the new project. I added it as a local dependency, and then I do a test including some of the reusable components like the button.
It renders me well, even with the properties that happened to it, the reusable component (button) reads them to me and it works. But it doesn't use any of the custom properties of the theme.
Código del botón (Commons Project)
const useStyles = makeStyles( theme => {
console.log(theme); // <-- no theme custom variables
return {
root: {
width: '100%',
},
};
});
const Button = (props) => {
const { title, outlined, theme, color } = props;
const classes = useStyles(props);
return (
<Button variant="contained"
{...props}>
{ title }
</Button>
);
};
Código que llama al botón (New Project)
import React, { useState } from 'react';
import { ThemeProvider} from '#material-ui/styles';
import { Button} from 'commons-lib';
import theme from './theme';
function App() {
return (
<ThemeProvider theme={theme}>
<div className="App">
<Button id="btn12" color="primary" title="Aceptar" size="medium" />
</div>
</ThemeProvider>
);
}
export default App;
code theme
const theme = createMuiTheme({
palette: {
primary: purple,
secondary: {
light:'#b1b',
main:'#066',
dark:'#838',
contrastText: '#fff'
}
}
},
});
export default theme;
It is not using the palette that I defined, but it is using the default palette. From the console I can see that the themeprovider properties are correct, but in the button console.log the default palette appears.
I want you to use the palette that is being injected from the new project.
The problem arises because the root project was using "#material-ui/styles" of the "commons-lib" project, so the latter installed the default theme.
The solution was to make an
npm link commons-lib/node_modules/#material-ui/styles
from the root project.