Codementor Events

Theming in React

Published Apr 11, 2020Last updated Sep 25, 2020
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.

Discover and read more posts from DhananjayKumar
get started
post comments2Replies
Mohd Belal
5 years ago

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

DhananjayKumar
5 years ago

Thanks Mohd for you feedback and advice.