Theming in React
React components that you can reuse is great, but it is challenging to change their styling to adapt to a new theme. I was using both Bootstrap and Material UI component and was bit difficult to apply theme to both of them. Slowly I refactored to use more of Material UI Component as it has good support and documentation for React Component Theming. You can find Material UI Theming documentation here. Thumb rule about theming is - don’t allow every property of our component’s style to be changed by the theme otherwise we would lose any consistency across projects. The things that we should change are border-color, background-color etc.
There is a nice article about Theming React Components which I bookmarked and might be helpful for you to understand concept behind the scene.
Code Snippet
Code snippet which is needed to apply theme on runtime. Add app-theme.js to the root of the src folder and below code -
import { createMuiTheme } from '@material-ui/core/styles';
const defaultTheme = createMuiTheme({
overrides: {
MuiOutlinedInput: {
root: {
backgroundColor: 'white',
'&$focused $notchedOutline': {
borderColor: '#8ca103',
borderWidth: 1,
},
}
},
MuiFormLabel: {
root: {
color: '#8ca103',
"&$focused": {
color: '#8ca103',
}
},
}
},
typography: { useNextVariants: true},
textColor: '#8ca103',
buttonBackground: '#8ca103',
appBarBackground: 'linear-gradient(#d0d0b7, #d0d0b7)',
appNameTextShadow: '1px 1px 2px greenyellow, 0 0 10px black, 0 0 2px greenyellow',
drawerBackground: 'linear-gradient( #ffffff, #f5f5f2)',
contentHeader: {
fontWeight: "bold",
color: "black",
fontSize: "16px",
textTransform: "capitalize",
padding: "8px",
border: "1px solid lightgrey",
backgroundColor: "#ebeadf"
},
contentArea : {
padding: "16px",
borderStyle: "solid",
borderWidth: "1px",
borderColor: "#d0d0b7",
},
blockBackground: "#f5f5f2"
});
const blueTheme = createMuiTheme({
overrides: {
MuiOutlinedInput: {
root: {
backgroundColor: 'white',
'&$focused $notchedOutline': {
borderColor: '#0f04d9',
borderWidth: 1,
},
}
},
MuiFormLabel: {
root: {
color: '#0f04d9',
"&$focused": {
color: '#0f04d9',
}
},
}
},
typography: { useNextVariants: true},
textColor: '#0f04d9',
buttonBackground: '#0f04d9',
appBarBackground: 'linear-gradient(#9e9eff, #9e9eff)',
appNameTextShadow: '1px 1px 1px orange, 0 0 10px white, 0 0 1px orange',
drawerBackground: 'linear-gradient( #ffffff, #dddcf5)',
contentHeader: {
fontWeight: "bold",
color: "black",
fontSize: "16px",
textTransform: "capitalize",
padding: "8px",
marginBottom: "8px",
backgroundColor: "#f5f5ff"
},
contentArea : {
padding: "25px",
borderStyle: "solid",
borderWidth: "1px",
borderColor: "#b5bbf5",
},
blockBackground: "#f5f5ff"
});
export const themeOptions = [
{ value: defaultTheme, label: 'default-theme' },
{ value: blueTheme, label: 'blue-theme' }
];
You have created the themes which you need to pass to MuiThemeProvider based on the selection. Add below code in App.js -
import React, { Component } from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom'
import PropTypes from 'prop-types';
import CssBaseline from '@material-ui/core/CssBaseline';
import withStyles from '@material-ui/core/styles/withStyles';
import { MuiThemeProvider } from '@material-ui/core/styles';
import SignIn from "./Components/signIn";
import {themeOptions} from './app-theme';
class App extends Component {
constructor(props) {
super(props);
this.state = {
themeOptions: themeOptions,
selectedTheme: ctx !== null && ctx.themeName !== undefined
? themeOptions.filter(o=> o.label === ctx.themeName)[0]
: themeOptions[0],
};
}
handleThemeChange = (e) => {
this.setState({ selectedTheme: e});
}
render() {
const { classes } = this.props;
return this.renderUnAuthenticatedView(classes);
}
renderUnAuthenticatedView(classes) {
let theme = this.state.selectedTheme.value;
return (
<div>
<CssBaseline />
<MuiThemeProvider theme={theme}>
<div className={classes.appHeader} style={{backgroundImage: theme.appBarBackground}}>
<div className={classes.appName}>
Bookindesk
</div>
<div className={classes.themeName}>
<Select value={this.state.selectedTheme} options={this.state.themeOptions} onChange={this.handleThemeChange}/>
</div>
</div>
<div className={classes.content}>
<SignIn onLogin={this.handleLogin} {...this.state} />
</div>
<Footer></Footer>
</MuiThemeProvider>
</div>
);
}
}
App.propTypes = {
classes: PropTypes.object.isRequired,
};
const styles = theme => ({
appHeader: {
display: "grid",
gridGap: "10px",
gridTemplateColumns: 'repeat(2, 1fr)',
height: "90px"
},
appName: {
textAlign: "right",
gridColumn: '1/2',
paddingTop:"15px",
},
themeName: {
gridColumn: '2/3',
paddingTop:"25px",
fontSize: "16px",
width:"170px",
},
});
export default withStyles(styles)(App);
Now, the selected theme is available to all the components wrapped inside MuiThemeProvider. In this example we want to make this theme object available to SignIn component. Here goes signIn.js code -
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {Avatar, Button, TextField, Checkbox} from '@material-ui/core';
import {LockOutlined} from '@material-ui/icons';
import {withStyles} from '@material-ui/core/styles';
class SignIn extends Component {
constructor(props) {
super(props);
this.state = {
signEmail: "",
signPassword: "",
signHotelName: "",
rememberMe: true,
};
}
render() {
const { theme, classes } = this.props;
return (
<div className={classes.main}>
<div className={classes.paper}>
<div style={theme.contentHeader}>
Already Registered?
</div>
<Avatar className={classes.avatar}>
<LockOutlined />
</Avatar>
<div className={classes.section}>SIGN IN</div>
<form className={classes.root} noValidate autoComplete="off">
<div>
<TextField
noValidate
autoComplete="off"
id="signEmail"
label="Email Id:"
variant="outlined"
autoFocus value={this.state.signEmail}
onChange={this.handleChange}
margin="dense"
className={classes.textField} />
</div>
<div>
<TextField
id="signPassword"
label="Password:"
variant="outlined"
autoComplete="off"
type="password"
value={this.state.signPassword}
onChange={this.handleChange}
margin="dense"
className={classes.textField}/>
</div>
<div>
<label className={classes.rememberMe}>
<Checkbox checked={this.state.rememberMe}/>
Remember me
</label>
<Button className={classes.forgot}>
Don't remember your password?
</Button>
<Button className={classes.forgot}>
Change password?
</Button>
</div>
<Button className={classes.signIn}>
Sign In
</Button>
</form>
</div>
</div>
);
}
}
SignIn.propTypes = {
classes: PropTypes.object.isRequired,
};
const styles = theme => ({
main: {
padding: theme.spacing.unit * 3,
},
root: {
'& .MuiTextField-root': {
width: '100%', // Fix IE 11 issue.
},
},
paper: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: theme.spacing.unit * 2,
borderStyle: "solid",
borderWidth: "1px",
borderColor: theme.contentArea.borderColor,
},
avatar: {
backgroundColor: theme.textColor,
margin: "16px"
},
rememberMe: {
marginTop: "16px",
},
forgot: {
color: theme.textColor,
marginTop: "4px",
},
section: {
fontSize: '12px',
fontWeight: 'bold',
marginTop: '12px',
color: theme.textColor,
},
textField: {
width: '100%',
},
signIn: {
marginTop: "46px",
marginBottom: "56px",
padding: "10px",
fontWeight:"bold",
[theme.breakpoints.down('sm')]: {
marginTop: "16px",
marginBottom: "16px",
},
}
});
export default withStyles(styles, { withTheme: true })(SignIn);
Above you can see I am using theme.contentArea.borderColor, theme.textColor to css classes and directly reading theme object theme.contentHeader in the render method of SignIn Component.
Hi Bookindesk,
Be generic and not confined
The main idea behind adding post on Codementor is to leverage up the technical knowledge behind any application to the readers and restless enthusiastic developers
Although you have provided in depth of your application area on how you have developed it, but this does not circulates things globally to the people. Instead it should be what challenges you faced and how did you encountered that issues with the available technology we have in the market.
Stay safe is the first need of the hour. So keep yourself and your loved ones safe
Thanks Mohd for you feedback and advice.