Pup

v2.x

ReactDefining Components

In Pup, two types of components are currently used: class-based and function.

When you're building your product's UI, you will want to know how to utilize both types—each has its own advantages and purpose. At a high level, the largest difference between the two are that class-based components can support React's state and lifecycle method features while function components cannot.

Though it ultimately comes down to preference, a good rule of thumb is to start your components off as function components, expanding to a class-based component if and when you need access to state or lifecycle methods.

The advantage to selecting a function component over a class-based one is performance. A function component needs to load less into memory than a class-based one. As your component library grows, this can have a significant impact on the load of your application on your user's browser.

Class Components

Class components are React components implemented using a JavaScript class. The class part of that equation is a purely JavaScript convention, not to be confused with an earlier implementation of React which suggested defining components with a method called React.createClass() (now deprecated).

On the right, we can see a class component being created. The pattern for this is to define your component with class ExampleComponent extends React.Component where React.Component is itself a JavaScript class which implements the React component API. By extending from that component, our new ExampleComponent inherits all of the capabilities of React.Component.

As noted above, the advantage to using this type of React component is the ability to add state and lifecycle methods. The second example on the right showcases this, adding state as a class property and a componentDidMount() lifecycle method (a special type of method defined internally in React that helps you interact with the lifecycle of your component).

What's <React.Fragment>?

When returning a value to render, React requires that you have one root element (i.e., your return statement can't have one element immediately followed by another). In the past, the workaround for this was to place an empty <div> tag in your code and then nest your elements inside of it.

Because this often introduced unwanted markup in the DOM, in React 16, <React.Fragment> was introduced to allow you to specify a single root element without adding unwanted markup.

Example Class Component
import React from 'react';

class ExampleComponent extends React.Component {
  render() {
    return (
      <React.Fragment>
        <p>Hot diggity dog, I'm a class component!</p>
      </React.Fragment>
    );
  }
}
Example Class Component with State and a Lifecycle Method
import React from 'react';
import { Button } from 'react-bootstrap';

class ExampleComponent extends React.Component {
  state = { lightsOn: true };

  componentDidMount() {
    alert(`Looks like the lights are ${this.state.lightsOn ? 'on' : 'off'} in this room!`);
  }

  render() {
    return (
      <React.Fragment>
        <p>Are the lights on? <strong>{this.state.lightsOn ? 'Yep!' : 'Nope.'}</p>
        <Button bsStyle="success" onClick={() => this.setState({ lightsOn: !this.state.lightsOn })}>Turn Lights {this.state.lightsOn ? 'Off' : 'On'}</Button>
      </React.Fragment>
    );
  }
}

Function Components

Function components are React components implemented using a plain JavaScript function. A function component receives a single props argument and is expected to return some markup for React to render.

For example, the <NotFound /> component in Pup is a great example of (and use case) for a function component.

Here, <NotFound /> is intend to render a message to the user when the URL they type in doesn't match a route in the application—that's it.

Because there's nothing interactive about it—meaning, it doesn't need state or lifecycle methods—we use a function component definition to avoid loading unnecessary functionality.

The second example on the right showcases a function component taking in props to render along with the markup. Here, we use JavaScript destructuring to "pluck off" the name prop passed to the component.

In this example, our usage may look like this:

<UserName name={{ first: 'Andy', last: 'Warhol' }} />

/ui/pages/NotFound/index.js
import React from 'react';
import { Alert } from 'react-bootstrap';
import { Meteor } from 'meteor/meteor';

const NotFound = () => (
  <div className="NotFound">
    <Alert bsStyle="danger">
      <p>
        <strong>Error [404]</strong>: {Meteor.isClient ? window.location.pathname : ''} does not
        exist.
      </p>
    </Alert>
  </div>
);

export default NotFound;
Function Component with Props
import React from 'react';

const UserName = ({ name }) => (
  <div className="UserName">
    <p>Howdy, {name.first} {name.last}!</p>
  </div>
);

Prop Types

Prop Types are a convention in React designed to help developers better understand the API of their own components and those they use from third-party developers.

In the simplest sense, Prop Types help us to answer "what props can I pass to this component, and for each prop, what type of data must I pass?"

Consider the example on the right. Here, we take our <UserName /> example from above, adding Prop Types to define the type of data we expect for our name prop.

Here, by defining UserName.propTypes, any property we add to that object is specifying a prop we expect. Here, name is set to the PropTypes.object.isRequired which says "we expect name to be passed as an object and it's required."

Notice, too, that PropTypes is imported as a value from the prop-types packaged (maintained by the React team, separated from React for the sake of purity/convenience).

To get more familiar with PropTypes, it's recommended that you read the documentation for the package.

Prop Types Example
import React from 'react';
import PropTypes from 'prop-types';

const UserName = ({ name }) => (
  <div className="UserName">
    <p>Howdy, {name.first} {name.last}!</p>
  </div>
);

UserName.propTypes = {
  name: PropTypes.object.isRequired,
};