Skip to content

How To: Create a web page using Material UI

mmbutton edited this page Aug 18, 2020 · 2 revisions

Creating a Web Page

Ensure that you have read React Tutorials and How To's before writing a web page. Each file created in this tutorial should be unit tested. The tutorial does not cover how to write unit testing but you can learn how at How To: Write a unit test in javascript. This page also assumes that you're writing using the Atomic Model

This will go over creating Pages, Templates, and Organisms. Atoms in our case are components imported using Material-UI (Buttons, tables and other basic building blocks). A Molecule is a combination of a few atoms into a reusable pattern (ie: A table with a header, a group of buttons at the end of a form or a confirmation dialog).

Page

(Optional: Only needed if your page has multiple sub-pages such as edit modes) Create a file that controls the routes to subpages. This makes heavy use of The React Router Library

import React, { Component } from 'react';
    import PropTypes from 'prop-types';
    import { Route, Switch } from 'react-router-dom';

    import MyPage1 from './MyPage1';
    import MyPage2 from './MyPage2'
    import MyPage3 from './MyPage3'

    export default class MyPage extends Component {
      static propTypes = {
        match: PropTypes.shape({
          path: PropTypes.string,
        }),
      };

      static defaultProps = {
        match: {
          path: undefined,
        },
      };

      // TODO: You only need to send paths that subpages will need to be linked to.
      renderMyPage1 = () => {
        const { match } = this.props;
        return (
          <MyPage1
            myPage2Path={`${match.path}/myPage2`}
          />
        );
      };

      renderMyPage2 = routeProps => {
        const { match } = this.props;
        return <MyPage2 myPage1Path={match.path} {...routeProps} />;
      };

      renderMyPage3 = routeProps => {
        return <MyPage3 {...routeProps} />;
      };

      render() {
        const { match } = this.props;

        return (
            <Switch>
              <Route exact path={match.path} component={this.renderMyPage} />
              <Route
                path={`${match.path}/myPage1/:someId`}
                component={this.renderMyPage2}
              />
              <Route
                path={`${match.path}/statistics/:someId`}
                component={this.renderMyPage3}
              />
            </Switch>
        );
      }
    }

Page Template

For each route declared in step 1 create a Page Template. Page Templates must be composed only of organisms and Elements used for Page layout only. See https://material-ui.com/customization/breakpoints/ for breakpoint explanations.

import React, { Component } from 'react';
    import PropTypes from 'prop-types';
    import Paper from '@material-ui/core/Paper';
    import { withStyles } from '@material-ui/core/styles';

    import MyAppComponent from 'AppComponents/my-app-component/MyAppComponent';

    const styles = theme => ({
      // TODO: This is the padding used to add whitespace around the page. 
      // These defaults are useful for a number of cases but should likely be changed.
      // In general you want no more than 1 unit of spacing for the smallest breakpoint "theme.breakpoints.down('sm')" 
      // and enough padding for larger breakpoints to only use as much of the page as needed to display your 
      // AppComponent effectively.
      pagePadding: {
        [theme.breakpoints.up('md')]: {
          paddingTop: theme.spacing(16),
          paddingRight: theme.spacing(16),
          paddingLeft: theme.spacing(16),
        },
        [theme.breakpoints.up('sm')]: {
          paddingTop: theme.spacing(8),
          paddingRight: theme.spacing(8),
          paddingLeft: theme.spacing(8),
        },
        [theme.breakpoints.down('sm')]: {
          paddingRight: theme.spacing(),
          paddingLeft: theme.spacing(),
        },
      },
    });

    class MyPage1 extends Component {
      static propTypes = {
        classes: PropTypes.shape({
          pagePadding: PropTypes.string,
        }),
      };

      static defaultProps = {
        classes: undefined,
      };

      render() {
        const { classes } = this.props;
        return (
          <Paper className={classes.pagePadding}>
            {/* While this page only has one organism most pages will have at least 3 */}
            <MyAppComponent />
          </Paper>
        );
      }
    }

    export default withStyles(styles)(MyPage1);

Create an organism that contains the application logic (Service calls and how to render said calls into the UI). These files should be small and usually only contain a small number of UI elements that go together (For instance a group of inputs, a table with a header or a list) and should not have any JSS that affects page layout.

TODO: Rewrite example to use a real service call, preferably to an erddap connected to CIOOS

import React, { Component } from 'react';

    // TODO: Replace WebRequest with axios. WebRequest is a wrapper ONC uses that points directly to our website only
    import WebRequest from 'util/WebRequest';

    // TODO: Replace BaseComponents with the material counterparts. Our ErrorSnackbar has some custom functionality
    import ErrorSnackbar from 'BaseComponents/Snackbar';
    
    // TODO: Replace SortableTable with Material table
    import SortableTable from 'CompositeComponents/table/SortableTable';

    const WebParameter = 8;

    class MyAppComponent extends Component {
      state = {
        dataList: [],
        error: null,
      };

      componentDidMount() {
        this.getAllData();
      }

      getAllData = async () =>
        WebRequest.get('DataService', {
          webParameter: WebParameter,
        })
          .then(response => WebRequest.parseResponse(response))
          .then(payload => {
            this.setState({
              data: payload.data.map(entry=> {
                const transfromedData = { ...data};
                transformedData.someBoolean = data.someBoolean.toString();
                return transformedData;
              }),
            });
          })
          .catch(error => {
            this.setState({ error });
          });

      onErrorClose = () => {
        this.setState({ error: undefined });
      };

      render() {
        const columns = [
          { name: 'id', title: 'ID' },
          { name: 'someBoolean', title: 'Some Boolean' },
          { name: 'otherData', title: 'Other Data' },
        ];

        const { error, dataList} = this.state;

        if (error) {
          return (
            <ErrorSnackbar message={error.message} onClose={this.onErrorClose} />
          );
        }

        return (
          <SortableTable columns={columns} rows={dataList} pageSize={15} />
        );
      }
    }

    export default MyAppComponent;