Material UI: Use theme in React Class Component - reactjs

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;

Related

How to use Ant design v5 theme with style component

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

Can we keep different themes for different pages in react with styled components website? [duplicate]

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);

Material ui class based components

Where can I find material ui class based components instead of function based components and if I can,t.
Where to write
const classes = useClasses();
const theme = useTheme();
in my class component?
useTheme - Its a hook, which can be used with functional components
You can however use withTheme for class component.
Refer this link : https://material-ui.com/styles/advanced/
There are two HoCs for your use-case, i.e. withStyles and withTheme.
Usage:
import React from "react"
import { withStyles } from "#material-ui/core/styles"
const styles = theme => ({
wrapper: {
display: "flex",
// ...
}
// ...
})
class ClassComponent extends React.Component {
// ...
render() {
const { classes } = this.props
// ...
return (
<div className={classes.wrapper}>
{/* ... */}
</div>
)
}
}
export const EnhancedComponent = withStyles(styles)(ClassComponent)
Now you can render EnhancedComponent.

Material UI nested theme providers breaks withStyles HOC

I have a React application created with Create React App and I use the #material-ui/core npm package for theming.
To customize components I use the withStyles higher-order component provided by MaterialUI.
According to documentation it supports nested ThemeProviders https://material-ui.com/customization/theming/#nesting-the-theme.
But inside the child ThemeProvider withStyles won't apply classes.
Here is a basic application demonstrating the issue -> https://codesandbox.io/s/vibrant-tree-eh83d
ExampleComponent.tsx
import React, { FunctionComponent } from "react";
import {
WithStyles,
withStyles,
createStyles,
StepButton,
Step,
Stepper,
Box
} from "#material-ui/core";
const styles = createStyles({
button: {
"& .MuiStepIcon-root.MuiStepIcon-active": {
fill: "red"
}
}
});
interface Props extends WithStyles<typeof styles> {
title: string;
}
const ExampleComponent: FunctionComponent<Props> = ({ title, classes }) => {
console.log(title, classes);
return (
<Box display="flex" alignItems="center">
<span>{title}</span>
<Stepper activeStep={0}>
<Step>
<StepButton className={classes.button}>Test</StepButton>;
</Step>
</Stepper>
</Box>
);
};
export default withStyles(styles)(ExampleComponent);
App.tsx
import * as React from "react";
import { ThemeProvider, createMuiTheme } from "#material-ui/core";
import ExampleComponent from "./ExampleComponent";
const theme = createMuiTheme();
function App() {
return (
<ThemeProvider theme={theme}>
<ExampleComponent title="Root" />
<ThemeProvider theme={theme}>
<ExampleComponent title="Nested" />
</ThemeProvider>
</ThemeProvider>
);
}
export default App;
Inside the ExampleComponent I console.log the generated classes object.
I want to use nested ThemeProviders and override classes inside components regardless of the ThemeProvider.
Am I missing something or is this not possible?
When you are using nested themes, you cannot reliably use Material-UI's global class names (e.g. .MuiStepIcon-root.MuiStepIcon-active). Within a nested theme, the "Mui..." class names have to be different to avoid conflicting with the CSS classes for the top-level theme since the nested theme will cause some of the CSS for the "Mui..." classes to be different.
You can use the following syntax in order to successfully match the suffixed versions of the Mui class names that occur within nested themes:
const styles = createStyles({
button: {
'& [class*="MuiStepIcon-root"][class*="MuiStepIcon-active"]': {
fill: "red"
}
}
});
Related answer:
How reliable are MUI Global Class names in JSS?

How to pass Styled-Component theme variables to Components?

Within my React+StyledComponent app, I have a theme file like so:
theme.js:
const colors = {
blacks: [
'#14161B',
'#2E2E34',
'#3E3E43',
],
};
const theme = {
colors,
};
export default theme;
Currently, I can easily use these colors to style my components like so:
const MyStyledContainer = styled.div`
background-color: ${(props) => props.theme.colors.blacks[1]};
`;
The problem is, how do I pass blacks[1] to a Component as the prop of the color to use like so:
<Text color="black[1]">Hello</Text>
Where Text.js is:
const StyledSpan = styled.span`
color: ${(props) => props.theme.colors[props.color]};
`;
const Text = ({
color,
}) => {
return (
<StyledSpan
color={color}
>
{text}
</StyledSpan>
);
};
Text.propTypes = {
color: PropTypes.string,
};
export default Text;
Currently the above is silently failing and rending the following in the DOM:
<span class="sc-brqgn" color="blacks[1]">Hello</span>
Any ideas on how I can get this to work? Thank you
EDIT: Updated to use styled-components withTheme HOC
New answer
You could wrap the component rendering <Text> in the higher order component (HOC) withTheme provided by styled-components. This enables you to use the theme given to the <ThemeProvider> directly in the React component.
Example (based on the styled-components docs):
import React from 'react'
import { withTheme } from 'styled-components'
import Text from './Text.js'
class MyComponent extends React.Component {
render() {
<Text color={this.props.theme.colors.blacks[1]} />;
}
}
export default withTheme(MyComponent)
Then you could do
const MyStyledContainer = styled.div`
background-color: ${(props) => props.color};
`;
Old answer
You could import the theme where you render and pass <Text color={theme.blacks[1]} />.
import theme from './theme.js'
...
<Text color={theme.colors.blacks[1]} />
Then you could do
const MyStyledContainer = styled.div`
background-color: ${(props) => props.color};
`;
You can use defaultProps
import PropTypes from 'prop-types'
MyStyledContainer.defaultProps = { theme }
App.js
App gets theme and passes color to Text
import React, { Component } from 'react'
import styled from 'styled-components'
const Text = styled.div`
color: ${props => props.color || 'inherit'}
`
class App extends Component {
render() {
const { theme } = this.props
return (
<Text color={theme.colors.black[1]} />
)
}
}
export default App
Root.js
Root component passes theme to entire application.
import React, { Component } from 'react'
import { ThemeProvider } from 'styled-components'
import theme from './theme'
import App from './App'
class Root extends Component {
render() {
return (
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
)
}
}
export default Root
If you're using functional components in React and v4.x and higher styled-components, you need to leverage useContext and styled-components' ThemeContext. Together, these allow you to use your theme settings inside of components that aren't styled-components.
import { useContext } from 'react'
import { ThemeContext } from 'styled-components'
export default function MyComponent() {
// place ThemeContext into a context that is scoped to just this component
const themeProps = useContext(ThemeContext)
return(
<>
{/* Example here is a wrapper component that needs sizing params */}
{/* We access the context and all of our theme props are attached to it */}
<Wrapper maxWidth={ themeProps.maxWidth }>
</Wrapper>
</>
)
}
Further reading in the styled-components docs: https://styled-components.com/docs/advanced#via-usecontext-react-hook

Resources