Style React with Material-UI

Full Stack Web and Mobile Applications with React, React-Native, iOS and Android

Style React with Material-UI

Material-UI

Material-UI is a component library that implements Google’s material design specifications. I provide an overview of it in my article User Experience Design with React. If you don’t mind adding a layer of complexity to your application, then perhaps Material-UI is the way to go because it offers a pretty nice feature set along with the components.

I should note that I used the beta version of Material-UI (currently in version 1.0.0 beta 33). Trust me when I say that version 1.0 of Material-UI is so drastically different from the 0.xx releases that  any potential bugs or instabilities outweighs the amount of effort that will be required to upgrade later. The reason is due to performance optimizations for larger applications and issues encountered with server-side rendering with pre-1.0 versions of Material-UI. Additionally, I’ve already seen it being used in a number of production applications and I haven’t personally had any problems with it, so I recommend going with the latest beta 1 release. To install that version, run:

npm install --save material-ui@next

Build a Material-UI Theme

It took me a bit of trial and error to figure out how to make the styling work because the quickest and easiest way to get started using Material-UI is by building a global theme first. Trying to use the Material-UI components without these initial steps are going to make your app look all sorts of goofy as the built-in stylings of the default theme interact with your app’s previous stylings. That is what happened for me, anyway.

For this article I will show how to build an app bar that controls a left navigation drawer that slides in and out. The MuiMenuBar that I’ll use for this article is based on the Material-UI’s AppBar component. But first we’re going to start with App.js and make the necessary imports to make sure things look the way we want once we start bringing in our Material-UI components.

import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import { createMuiTheme } from 'material-ui/styles';
import MuiMenuBar from '../components/common/MuiMenuBar';

The first import, MuiThemeProvider, is a wrapper that requires a valid material-ui theme object to be passed to it. In short, MuiThemeProvider injects the theme into your application context. Above the class declaration in App.js, you can set up your theme object, or you can set it up in a separate file and import it into App.js.

Next, we’re going to call createMuiTheme and set up our color pallette above the class declaration in App.js:

const theme = createMuiTheme({
  palette: {
    primary: {
      light: '#7986CB',
      main: '#3949AB',
      dark: '#1A237E',
      contrastText: '#eecf8f',
    },
    secondary: {
      light: '#999999',
      main: '#10234f',
      dark: '#1f1311',
      contrastText: '#eecf8f',
    },
    appBar: {
      color: 'primary'
  },
});

The theme is where you can set up all your global styles like the base color palette. For that you define a palette object and set up color groupings as I have done in the previous code snippet. You have the option of using colors defined with hex, or you can use predefined javascript variables used in Google’s UI Color Palette . So instead of hex for your main primary color you could define for example, main: blue[900] if you prefer. You can also check out this really nifty tool created by the Material-UI team for building your color palette. You can also declare global component styles using predefined selectors as I did with appBar. Each time you bring in a new component to your application, its a good practice to go back to your global theme and define any global styles for that particular component.

Now, here is what the rest of our App.js will look like:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.onSetSidebarOpen = this.onSetSidebarOpen.bind(this);
  }

  onSetSidebarOpen() {
    let side = !this.props.sidebarOpen;
    this.props.dispatch(navigationActions.toggleSidebar(side));
  }
  render() {
    var sidebarContent = <NewsMenu />;
    
    return (
     <div>
      <MuiThemeProvider theme={theme}>
      <MuiMenuBar title={this.props.title} 
                  onSetSidebar={this.onSetSidebarOpen} />
      <Sidebar sidebarClassName="sidebar" 
               sidebar={sidebarContent}
               open={this.props.sidebarOpen}
               onSetOpen={this.onSetSidebarOpen}>
        <Switch>
          <Route exact path="/" component={Login} />
          <Route path='/login' component={Login}/>
          <Route path='/Home' component={Home}/>
          <Route path='/About' component={About}/>
        </Switch>
        </Sidebar>
        </MuiThemeProvider> 
      </div>
    );
  }
}

function mapStateToProps(state, ownProps) {
  return {
     title: state.navigationReducer.title,
     sidebarOpen: state.navigationReducer.sidebarOpen,
  };
}

export default connect(mapStateToProps)(App);

First and foremost, you may notice that I have used a different component outside the Material-UI library for the sidebar. There are a few different reasons why I currently have it set up this way. First, I wanted the sidebar to be decoupled from the menu bar and have sidebarOpen passed in as a prop from the redux store instead of having it as part of state due to external requirements of the application. This is why onSetSidebarOpen()dispatches an action that toggles the sidebar.

More importantly, you’ll notice that MuiThemeProvider wraps all children of the App component, the highest node in the DOM tree. This way the theme is injected into the entire application context. Now lets look at our MuiMenuBar component.

Styling Components with Material-UI

In order to understand how styling is done with Material-UI, you should have a basic understanding of higher-order components (HOC). The withStyles() higher-order component is used to inject your own styles and pass the class name that you define in the styles const via the classes prop.

const styles = theme => ({
  root: {
    width: '100%',
    display: "inline",
    position: "fixed",
    top: "0",
    height: "75px",
    fontWeight: "400",
    textAlign: 'left',
    padding: '0px 0 0 20px',
    zIndex: '1',
  },
  flex: {
    flex: 1,
    fontSize: '22px',
  },
  menuIcon: {
    fontSize: '32px'
  },
  menuButton: {
    marginLeft: -12,
    marginRight: 20,
  },
});

Remember that theme was already injected into the component, so now we can pass it to the styles function, which will include the theme values when the withStyles() HOC injects these styles into the dom. Our component-specific styles, as well as those from the global theme, are set  using the className property of the component, like so:

<MenuIcon className={classes.menuIcon}/>

To use this higher-order component, you place it in the export definition, same as you would if you were using Redux’s connect function with mapStateToProps.

export default withStyles(styles)(MuiMenuBar);

Using withStyles() as the HOC,  we pass in the styles function when the component is exported, which injects our style declarations that are specific to this component, and allow us to use these classes in the className field of our jsx (classes are passed in via props).

Here is what the MuiMenuBar component looks like in its entirety:

import React, { Component } from 'react';
import {PropTypes} from 'prop-types';
import { withStyles } from 'material-ui/styles';
import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar';
import Typography from 'material-ui/Typography';
import IconButton from 'material-ui/IconButton';
import MenuIcon from 'material-ui-icons/Menu';
import Menu, { MenuItem } from 'material-ui/Menu';

const styles = theme => ({
  root: {
    width: '100%',
    display: "inline",
    position: "fixed",
    top: "0",
    height: "75px",
    fontWeight: "400",
    textAlign: 'left',
    padding: '0px 0 0 20px',
    zIndex: '1',
  },
  flex: {
    flex: 1,
    fontSize: '22px',
  },
  menuIcon: {
    fontSize: '32px'
  },
  menuButton: {
    marginLeft: -12,
    marginRight: 20,
  },
});

const MuiMenuBar  = (props) => {
  const {classes, title, onSetSidebar} = props;
  return (   
    <AppBar className={classes.root}>
      <Toolbar>
        <IconButton className={classes.menuButton} onClick={() => onSetSidebar()} color="inherit" aria-label="Menu">
          <MenuIcon className={classes.menuIcon}/>
        </IconButton>
        <Typography variant="title" color="inherit" className={classes.flex} >
          {title}
        </Typography>  
      </Toolbar>
    </AppBar>
  );
};
  
MuiMenuBar.propTypes = {
  classes: PropTypes.object.isRequired,
  title: PropTypes.string,
  onSetSidebar: PropTypes.func,
};           

export default withStyles(styles)(MuiMenuBar);

If you remember, we set the color of the AppBar component using the predefined selector in the global theme, which was defined in App.js. That will be applied in addition to the styles defined in the root class definition, which is specific to this component only. The same goes for the MenuIcon and Typography elements. Those styles are defined here because, for example, we don’t want every Typography element in the app to have a fontSize of 22px, only the one that is a child to the MuiMenuBar component.

Conclusion

There’s no denying that the material design specification outlined by Google has become the most popular set of guidelines to follow when it comes to user experience design and the React Material-UI framework implements it very well. I highly recommend becoming more familiar with it and get to know the Material-UI component API because it is very powerful and will likely save you a lot of time in the design and development of your web application.

 

Leave a Reply

Your email address will not be published.