Pup

v1.x

The BasicsComponents & Containers

The core building block of Pup's user interface is React components. In Pup, there are several components pre-defined for you that can either be used as-is, or modified to fit the particular needs of your product.

Layout, page, component diagram.
Layout, page, component diagram.

Components in Pup are split into three categories:

  • /imports/ui/layouts - Components that are responsible for building layouts that combine a set of static elements (e.g., the application's navigation bar) with dynamic elements. In Pup, the <App /> component serves as the main layout component and is used to render every page in the application.
  • /imports/ui/pages - Components that are compositions of other components, rendered within the dynamic portion of a layout component and corresponding to a particular route in the application (e.g., the /login route renders the <Login /> page component). Some page components are wrapped with containers to provide them with data.
  • /imports/ui/components - Standalone components that can be used independently or composed together with other components in a page.
/imports/ui/layouts/App/App.js
[...]

import './App.scss';

const App = props => (
  <Router>
    {!props.loading ? <div className="App">
      <Navigation {...props} />
      <Grid>
        <Switch>
          {/* Routes rendered here. Each route renders a page component. */}
        </Switch>
      </Grid>
      <Footer />
    </div> : ''}
  </Router>
);

App.propTypes = {
  loading: PropTypes.bool.isRequired,
};

export default createContainer(() => {
  /* Data container for App component. */
}, App);
/imports/ui/pages/Index/Index.js
import React from 'react';
import { Button } from 'react-bootstrap';

import './Index.scss';

const Index = () => (
  <div className="Index">
    <img
      src="https://s3-us-west-2.amazonaws.com/cleverbeagle-assets/graphics/email-icon.png"
      alt="Clever Beagle"
    />
    <h1>Pup</h1>
    <p>A boilerplate for products.</p>
    <div>
      <Button href="http://cleverbeagle.com/pup">Read the Docs</Button>
      <Button href="https://github.com/cleverbeagle/pup"><i className="fa fa-star" /> Star on GitHub</Button>
    </div>
    <footer>
      <p>Need help and want to stay accountable building your product? <a href="https://cleverbeagle.com?utm_source=pupappindex&utm_campaign=oss">Check out Clever Beagle</a>.</p>
    </footer>
  </div>
);

export default Index;
/imports/ui/components/InputHint/InputHint.js
import React from 'react';
import PropTypes from 'prop-types';

import './InputHint.scss';

const InputHint = ({ children }) => (
  <div className="InputHint">
    {children}
  </div>
);

InputHint.propTypes = {
  children: PropTypes.node.isRequired,
};

export default InputHint;

Data Containers

Data containers are a concept unique to React components in the context of Meteor. A data container is a way to connect a reactive data source on the server to a component on the client, ensuring that as new data hits the client, the component is updated. In short: a container is the glue between data on the server and a React component on the client. A container "wraps around" a component, passing the data it gets from the server as props to the React component.

Server, container, component diagram.
Server, container, component diagram.

In Pup, containers are defined using a Meteor package called react-meteor-data. This package exports a method, withTracker() which allows us to define a container and wrap our component simultaneously. Thought it doesn't look like one, once our container is created, behind the scenes it returns a React component from itself. This means that our container is what ultimately gets rendered on screen and then the container renders the component it wraps.

What this means is that when we render a container, it can take in props just like a traditional React component. In the example on the right, we can see withTracker() taking a function which is passed an argument props—being destructured here, revealing a prop match which is passed to our container from React Router—that we can pull values from.

In this example, we pull the :_id parameter from the match.params prop passed down to our container (this happens automatically as our router is rendering the container which then renders our component). Once we have it, we pass it along to a call to Meteor.subscribe(), so that we can fetch the corresponding data in a publication on the server.

Once that data is received and our subscription is marked as ready—meaning, the data we requested from the server is now flowing to the client—we get the data we need on the client Documents.findOne()—and then pass that as a property on the object returned from our container. That object—and its properties—become props on the <ViewDocument /> component that we're wrapping. If we look in our component, we can see loading and doc being accessed as props in our component.

/imports/ui/pages/ViewDocument/ViewDocument.js
[...]

const handleRemove = (documentId, history) => {
  if (confirm('Are you sure? This is permanent!')) {
    Meteor.call('documents.remove', documentId, (error) => {
      if (error) {
        Bert.alert(error.reason, 'danger');
      } else {
        Bert.alert('Document deleted!', 'success');
        history.push('/documents');
      }
    });
  }
};

const renderDocument = (doc, match, history) => (doc ? (
  <div className="ViewDocument">
    <div className="page-header clearfix">
      <h4 className="pull-left">{ doc && doc.title }</h4>
      <ButtonToolbar className="pull-right">
        <ButtonGroup bsSize="small">
          <Button onClick={() => history.push(`${match.url}/edit`)}>Edit</Button>
          <Button onClick={() => handleRemove(doc._id, history)} className="text-danger">
            Delete
          </Button>
        </ButtonGroup>
      </ButtonToolbar>
    </div>
    { doc && doc.body }
  </div>
) : <NotFound />);

const ViewDocument = ({ loading, doc, match, history }) => (
  !loading ? renderDocument(doc, match, history) : <Loading />
);

ViewDocument.propTypes = {
  loading: PropTypes.bool.isRequired,
  doc: PropTypes.object.isRequired,
  match: PropTypes.object.isRequired,
  history: PropTypes.object.isRequired,
};

export default createContainer(({ match }) => {
  const documentId = match.params._id;
  const subscription = Meteor.subscribe('documents.view', documentId);

  return {
    loading: !subscription.ready(),
    doc: Documents.findOne(documentId),
  };
}, ViewDocument);

Bootstrap & React Bootstrap

In Pup, the UI framework of choice is Bootstrap. We selected Bootstrap due to its popularity, but also its simplicity. Unfortunately, as of now, Bootstrap does not offer an official solution for using Bootstrap in React. In order to do so, a developer is responsible for translating the vanilla HTML offered by Bootstrap into React components on their own.

Fortunately, a group of developers who also prefer to work with Bootstrap have taken the initiative to perform this translation and open source the end result as react-bootstrap. As the name implies, react-bootstrap offers all of the components in Bootstrap (v3) as React components.

For example, over on the right we can see the <PublicNavigation /> from Pup importing and using the <Nav /> and <NavItem /> components from react-bootstrap. So it's clear, the <NavItem /> component here is equivalent to this HTML in Bootstrap:

<li><a href="#">Link</a></li>

The point being to save us the time of rewriting this markup whenever we want to display a Bootstrap element—in this case, a navigation item in a nav bar—in our React component.

/imports/ui/components/PublicNavigation/PublicNavigation.js
import React from 'react';
import { LinkContainer } from 'react-router-bootstrap';
import { Nav, NavItem } from 'react-bootstrap';

const PublicNavigation = () => (
  <Nav pullRight>
    <LinkContainer to="/signup">
      <NavItem eventKey={1} href="/signup">Sign Up</NavItem>
    </LinkContainer>
    <LinkContainer to="/login">
      <NavItem eventKey={2} href="/login">Log In</NavItem>
    </LinkContainer>
  </Nav>
);

export default PublicNavigation;

Forms

One of the most common features of a web application is forms. Inside of React components, defining forms is a little bit different than what you might be used to when building an interface with vanilla HTML. Truth be told, working with forms in a React-based UI can be a bit of a nightmare; especially when you're just getting started with React.

We've spent a lot of time debating how best to approach forms—especially as a beginner—and have come up with the following solution. In Pup, forms are built using a mix of vanilla HTML form elements (e.g., <form></form> and <input />) alongside React Bootstrap components.

This serves two purposes: making validation easier to attach and making form values easier to retrieve. Some elements, for example, Bootstrap's <div class="form-group"></div> have zero involvement with validation or value retrieval and so it makes sense to use these. Validating and selecting values from inputs defined using React Bootstrap's <FormControl /> component, however, is difficult and confusing.

On the right, we can see an example form—Pup's <Signup /> form—following this pattern. Focusing on the render() method of the component, we can see a mix of React Bootstrap components like <ControlLabel /> with vanilla HTML <input />s. If we look close at those <input />s, we can see that each has an extra attribute name.

The name attribute allows us to reference that element as it's rendered by React later (we use this for grabbing the input's value via the form element wrapping it with form.<name>.value). This pattern suggests that all forms are defined with a <form></form> element which has a ref assigned to it, also called form.

When we define a ref, we assign a function that takes in the rendered element as an argument. We take that argument and assign it back to the component instance this so that we can access that element from anywhere within our component (e.g., this.form = form).

If we look inside of the componentDidMount() function here in <Signup /> we can see that we pass our form ref as component.form to our component's handleSubmit() method. Inside of handleSubmit(), this makes it easy for us to reference each field on the form using the aforementioned form.<name>.value pattern.

Another advantage to this approach is the ability to add client-side validation. Our current choice for this is the library jquery-validation, selected for its quality of user experience, ease of use, and the comprehensiveness of its option for validating values.

In Pup, we've wrapped the code required for attaching validation to a form in a module called validate located at /imports/modules/validate.js. It accepts two arguments: the form element the validation is being attached to and an options object to pass along to jquery-validation (documentation on this is available here).

As we hinted above, jquery-validation relies on the name attribute assigned to each element requiring validation in our form. So, in our example <Signup /> component, on the options object passed to validate(), the rules.firstName property corresponds to the element with the name="firstName" attribute.

Once the user completes a form successfully, submission of the form is handled via the submitHandler() method defined on the options object passed to the validate() method. This ensures that a user cannot submit a form until they've completed the form according to the rules we've defined. Note: to enable this, the <form></form> element inside of our component is assigned an onSubmit handler that simply calls the JavaScript event.preventDefault() method. This ensures that the form does not submit using its standard behavior, allowing our validation's submitHandler() method to take over.

FAQ

Isn't usage of jQuery frowned upon in React? - Yes, however, as we've worked with the jquery-validation library over the last couple of years, we haven't seen this having any adverse effects on React. If and when that changes—or we simply decide to move on—we'll likely look at some other solutions. For now, if it ain't broken, don't fix it!

Why didn't you use a component library like Formsy? - In short: unpredictability. Formsy's existence is dependent on its creators. If they stop working on it or meet their bus factor, you're stuck. Forms are one of the biggest—if not the biggest—components of your application. Any abstraction placed on top of them should either be highly decoupled (meaning if something does break, you can swap it out without major work), or, extremely trustworthy (though non-existent, an official forms library released by the Facebook/React team).

/imports/ui/pages/Signup/Signup.js
import React from 'react';
import { Row, Col, FormGroup, ControlLabel, Button } from 'react-bootstrap';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { Accounts } from 'meteor/accounts-base';
import { Bert } from 'meteor/themeteorchef:bert';
import OAuthLoginButtons from '../../components/OAuthLoginButtons/OAuthLoginButtons';
import InputHint from '../../components/InputHint/InputHint';
import AccountPageFooter from '../../components/AccountPageFooter/AccountPageFooter';
import validate from '../../../modules/validate';

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

  componentDidMount() {
    const component = this;

    validate(component.form, {
      rules: {
        firstName: {
          required: true,
        },
        lastName: {
          required: true,
        },
        emailAddress: {
          required: true,
          email: true,
        },
        password: {
          required: true,
          minlength: 6,
        },
      },
      messages: {
        firstName: {
          required: 'What\'s your first name?',
        },
        lastName: {
          required: 'What\'s your last name?',
        },
        emailAddress: {
          required: 'Need an email address here.',
          email: 'Is this email address correct?',
        },
        password: {
          required: 'Need a password here.',
          minlength: 'Please use at least six characters.',
        },
      },
      submitHandler() { component.handleSubmit(); },
    });
  }

  handleSubmit() {
    const { history } = this.props;

    Accounts.createUser({
      email: this.emailAddress.value,
      password: this.password.value,
      profile: {
        name: {
          first: this.firstName.value,
          last: this.lastName.value,
        },
      },
    }, (error) => {
      if (error) {
        Bert.alert(error.reason, 'danger');
      } else {
        Bert.alert('Welcome!', 'success');
        history.push('/documents');
      }
    });
  }

  render() {
    return (<div className="Signup">
      <Row>
        <Col xs={12} sm={6} md={5} lg={4}>
          <h4 className="page-header">Sign Up</h4>
          <Row>
            <Col xs={12}>
              <OAuthLoginButtons
                services={['facebook', 'github', 'google']}
                emailMessage={{
                  offset: 97,
                  text: 'Sign Up with an Email Address',
                }}
              />
            </Col>
          </Row>
          <form ref={form => (this.form = form)} onSubmit={event => event.preventDefault()}>
            <Row>
              <Col xs={6}>
                <FormGroup>
                  <ControlLabel>First Name</ControlLabel>
                  <input
                    type="text"
                    name="firstName"
                    ref={firstName => (this.firstName = firstName)}
                    className="form-control"
                  />
                </FormGroup>
              </Col>
              <Col xs={6}>
                <FormGroup>
                  <ControlLabel>Last Name</ControlLabel>
                  <input
                    type="text"
                    name="lastName"
                    ref={lastName => (this.lastName = lastName)}
                    className="form-control"
                  />
                </FormGroup>
              </Col>
            </Row>
            <FormGroup>
              <ControlLabel>Email Address</ControlLabel>
              <input
                type="email"
                name="emailAddress"
                ref={emailAddress => (this.emailAddress = emailAddress)}
                className="form-control"
              />
            </FormGroup>
            <FormGroup>
              <ControlLabel>Password</ControlLabel>
              <input
                type="password"
                name="password"
                ref={password => (this.password = password)}
                className="form-control"
              />
              <InputHint>Use at least six characters.</InputHint>
            </FormGroup>
            <Button type="submit" bsStyle="success">Sign Up</Button>
            <AccountPageFooter>
              <p>Already have an account? <Link to="/login">Log In</Link>.</p>
            </AccountPageFooter>
          </form>
        </Col>
      </Row>
    </div>);
  }
}

Signup.propTypes = {
  history: PropTypes.object.isRequired,
};

export default Signup;

Defining your own components and containers

When it comes to defining components in React, there are two ways to do it: ES2015 class-based components and stateless functional components. Depending on the complexity of the components you're building, you'll switch between using these two styles.

ES2015/ES6 Class Components

The most basic—and common—version of defining components in React is through the use of ES2015/ES6 classes. The example on the right shows a fictional component <Album /> being defined using an ES2015 class. Here, we can see that in order to define a component using a class, we do so by extending the base React.Component class which we import from the react package.

Technically speaking, the only required method for a class-based component is the render() method. It's here that we define the markup that will be rendered on screen. In this example, our render method uses destructuring to get access to the album prop passed to our component. Once it has it, it renders the values defined on album—assumed to be an object—to the page.

Notice that when we get to rendering the album.description value, we do so conditionally using our component's this.state value. Here, if this.state.showDescription is true, we display the album.description (using the this.renderDescription() method defined above). If not, we show a button to toggle the visibility of the description.

It's here that we learn the significance of defining React components with an ES2015 class. This example is known as a "stateful" component, meaning, it relies on having access to the component's this.state value (as well as the ability to change it). In addition to having access to state, we may also refer to a component as "stateful" when it has custom methods defined on it that interact with the component instance (this) in some form.

In our example, we've defined three custom methods on our component: hideDescription, showDescription, and renderDescription that all require access to the component instance. By default, when we define custom methods on an ES2015 class-based React component, those methods do not have access to the component instance. To give them access, in the constructor() method of our component, we need to add the binding of this to those methods directly.

We can see this taking place for each custom method, assigning the method name back to itself with its this value bound to the component instance (inside of the constructor() method, the keyword this refers to the component instance). Doing this is required because otherwise, if we were to reference this inside of the custom methods without binding them, we'd get an error when trying to access values defined on the component instance (by default, the value of this in a custom method refers to the method itself, not the component instance).

While there's technically no issue with always using ES2015 classes to define your components, in some cases it can be considered inefficient because your component won't need access to this.state or any of React's lifecycle methods. If this is the case, the alternative form of defining React components is to use stateless functional components.

Stateless Functional Components

Like the name implies, stateless functional components are React components that exist without state. Further, they also lack access to React's lifecycle methods. Also known as "dumb components," SFCs are best used when a component's only purpose is to render some markup and props.

In the second example on the right, we reuse the <Album /> concept from our ES2015 class-based example. Here, we have nearly the same exact component, however, we've removed any usage of this.state and instead just render the album's description directly. Notice that all we're doing in this version of the component is rendering out props—technically just one prop, album—and markup. That's it.

Where this starts to make a lot of sense is when we want to render multiple albums on a page. Because we're using the exact same markup to render each album, it doesn't make sense to re-write that markup over and over. Further, because our component doesn't need access to state or lifecycle methods, this makes it a perfect candidate for a stateless functional component! We want the reusability of a component, but not the full weight of an ES2015 class.

Example Class Component
import React from 'react';
import PropTypes from 'prop-types';

class Album extends React.Component {
  constructor(props) {
    super(props);

    this.state = { showDescription: false };

    this.showDescription = this.showDescription.bind(this);
    this.hideDescription = this.hideDescription.bind(this);
    this.renderDescription = this.renderDescription.bind(this);
  }

  hideDescription() {
    this.setState({ showDescription: false });
  }
  
  showDescription() {
    this.setState({ showDescription: true });
  }

  renderDescription(description) {
    return (<div className="Album-description">
      <div dangerouslySetInnerHTML={{ __html: description }} />
      <button onClick={this.hideDescription}>Hide Description</button>
    </div>)
  }

  render() {
    const { album } = this.props;
    const showingDescription = this.state.showDescription;
    return (<div className="Album">
      <h1>{album.title}</h1>
      <p>by {album.artist} | {album.year}</p>
      {this.state.showDescription ? this.renderDescription(album.description) : <button onClick={this.showDescription}>Show Description</button>}
    </div>);
  }
}

Album.propTypes = {
  album: PropTypes.object.isRequired,
};

export default Album;
Example Stateless Functional Component
import React from 'react';
import PropTypes from 'prop-types';

const Album = ({ album }) => (
  <div className="Album">
    <h1>{album.title}</h1>
    <p>by {album.artist} | {album.year}</p>
    <div className="Album-description">
      <div dangerouslySetInnerHTML={{ __html: album.description }} />
    </div>
  </div>
);

Album.propTypes = {
  album: PropTypes.object.isRequired,
};

export default Album;