One week with React

This is part two in Bytesized’s A year with React.js series, which explores increasingly complex aspects of being a React developer. This post covers your first week with React––component lifecycle methods, and writing effective, re-usable components. It’s strongly recommended that readers start this series from the beginning: read the first post, “One hour with React”, here.

React components can often be thought of as being as simple as their input. In the previous post in this series, the NameComponent, a component defined with a single name prop, was pretty straightforward. What happens when the data being passed into a component is more complex? What about components that render other components? These patterns fall into intermediate to advanced React usage, and it’s in this area that many new React developers tend to become confused. If you’ve ever written a React component that should update as props change, but find that the component sits there doing nothing, this is the post for you.

In this post, we’ll cover more complex component trees, such as components that render other components. We’ll also explore React’s “prop types” feature, which leads to more correct and safe code. Finally, we’ll explore component lifecycle methods–knowing when and how a component is updating, and some common gotchas run into by new React developers.

Complex component trees

In the previous post in this series, I mentioned the “rewiring” that React does to your brain:

As you write more React code, you’ll find that it’s easiest to think in components: it’s an Arrival-esque “rewiring” of your brain, where you begin to internally modularize your planned code by default.

Larger React applications contribute to this behavior even more strongly–over time, a “tree” of components, that is, components with children components, becomes a familiar and even desirable structure for React developers to build. Take, for instance, this website:

While it’s a fairly simple design, there are a number of discrete elements on the page. We can break them down into components thusly:

These components represent large portions of the website’s design. We can imagine that each of these sections would also contain smaller components–for instance, a singular Post component may be comprised of additional, smaller components:

This can proceed ad-nauseum, until you’re completely exhausted by the number of components in your directory structure. It seems silly, but consider the advantages from a design and development perspective: a Text component can be used both by the Post component, and by any other place where paragraph-level text is required.

This practice is out there in the wild, used by high-functioning React.js development teams. Take react-bootstrap, for instance, a popular open-source React component wrapper around the Twitter Bootstrap framework. The Button component implements a component that simplifies generating different button types:

<Button>Default</Button>
// <button class="btn btn-default">Default</button>

<Button bsStyle="success">Successful action</Button>
// <button class="btn btn-success">Successful action</button>

<Button bsStyle="danger">Dangerous action</Button>
// <button class="btn btn-danger">Dangerous action</button>

By abstracting components out, application structure changes from a mess of inconsistent HTML to a consistent set of familiar patterns. Consider the following pseudo-trees for a Bytesized blog post, the first as a HTML, and the second as React components:

HTML:

div.post
  h2
    (Post date)
  h1
    (Post title)
  div.content
    p
      a
    p

React:

Post
  PostDate
    SmallHeaderText
  PostTitle
    HeaderText
  PostContent
    Paragraph
      Link
    Paragraph

Given these two trees, it may not be clear why the second is considered more ideal. Imagine a scenario where a designer on your team sends you a feature request: “Add the letter-spacing css class to all anchor/link tags”. In the HTML tree, this might be as simple as a find-and-replace. In the React tree, it’s simple too–after all, there’s only one Link component in our two paragraph post. We might do something like this to our Link component.

class Link extends React.Component {
  render() {
    return (
      // old
      // <a href={this.props.url}>{this.props.text}</a>

      // new
      <a className="letter-spacing" href={this.props.url}>{this.props.text}</a>
    )
  }
}

The advantages become clear when the second tree is viewed as a subsection of the larger blog tree:

Blog
  Header
    MenuItem
      Link
    MenuItem
      Link
  Posts
    Post
      PostDate
        SmallHeaderText
      PostTitle
        HeaderText
          Link
      PostContent
        Paragraph
          Link
        Paragraph
  Sidebar
    SmallHeader
      List
        Link
        Link
    SmallHeader
      List
        Link
  Footer
    Paragraph
      Link
    Paragraph
      Link

With a large number of links (nine in this tree, and likely to grow with time), used in a variety of places around the blog, it becomes a losing battle to do find-and-replace in an HTML-based component tree for this blog. Updating a single React component means that the change is propagated to every possible Link in the blog. This is a great example of why developers who have been writing React for a while proudly exclaim:

Prop Types

JavaScript is pretty well-known as a haphazard language. That’s pretty generous–it’s very easy to write JS code that doesn’t work. Gary Bernhardt’s well-known “Wat” lightning talk is a great demo of all the bad ways that JavaScript handles your code. The maintainers behind React are aware of this: with the project being actively used on the most popular website in the world, any and all additions to the project to bring a level of safety and rigor to JS should be considered a win.

Enter propTypes. React’s propTypes functionality provides validation around what props components should accept: given NameComponent, with a name prop, what should the passed-in name be? A number? An object? Most likely, it should be a string; propTypes allows you to check this:

class NameComponent extends React.Component {
  render() {
    return ( <h1>Hello, {this.props.name}!</h1> )
  }
}

NameComponent.propTypes = {
  name: PropTypes.string
}

Given this definition, we can explore what errors we’ll see in the console given bad input:

<NameComponent name={3} />

// Console output:
// Warning: Failed prop type: Invalid prop `name` of type `number` supplied to `NameComponent`, expected `string`.
//    in NameComponent

propTypes also can be used to notate when a prop is required:

NameComponent.propTypes = {
  name: PropTypes.string.isRequired
}

<NameComponent />

// Console output:
// Warning: Failed prop type: The prop `name` is marked as required in `NameComponent`, but its value is `undefined`.
//    in NameComponent

A particularly neat propTypes function is shape, which validates not only that a prop is an object, but allows nested validation for attributes inside the object:

NameComponent.propTypes = {
  name: React.PropTypes.shape({
    firstName: React.PropTypes.string
  })
}

const myName = { firstName: 3 }

<NameComponent name={myName} />

// Console output:
// Warning: Failed prop type: Invalid prop `name.firstName` of type `number` supplied to `NameComponent`, expected `string`.
//    in NameComponent

There are a number of useful functions as part of React’s propTypes library: from simple object/class validation to oneOf, an enum-check, and entirely custom validations, via passed-in functions. I highly encourage you check out the documentation: I have it bookmarked for quick reference as I write components.

React Component Lifecycle

React components have a number of “lifecycle” methods that can be hooked into by developers. These methods will be executed throughout the component’s normal operation. Some methods, like componentWillMount, happen before the component actually appears in the application. Others, like componentWillUnmount, happen right before a component is removed from the application. There is a variety of places to “plug in” with your application logic, and reading the “Component Lifecycle” section of the React component documentation will give you a better sense of where best to implement your application logic. Let’s look at a couple of examples of lifecycle methods in action.

There are some actions that need to happen with a component after it’s loaded. A common one is loading data via JSON. In the below example, we’ll display the current weather in San Diego, California, hooking into the componentDidMount method to fire a JSON request after the component is already displayed on the screen. You should notice brief (loading...) text before the temp state value is updated with the current temperature.

componentDidMount is commonly where XHR requests and other “preparatory” actions happen for a component. Initializing component-level validations, setting timers; things like this can’t happen when the component doesn’t yet exist, but if they happen too late (via a timer or another buggy solution), they may be inaccurate or out-of-sync with the component itself.

If you’re using jQuery or other DOM-level JS libraries, you’ll need to attach and detach handlers from your React components. Many developers will attach handlers to their components, but when the component disappears, they forget to detach the handlers–this often leads to messy consoles and, sometimes, complete application failure. componentWillUnmount is the place to do this: it allows one last place to perform logic before the component is destroyed by React’s DOM manager. In this simple example, we detach any jQuery events from our React component’s internal myElement, which is targetable using the ref prop:

Without the componentWillUnmount, we’ll leave a click event hanging until the page is either closed or refreshed. This isn’t a particularly egregious example of improper event handling, but without diligence, this kind of code can spiral out of control and lead to awful debugging sessions.

The props-related component lifecycle methods often are related to your component’s state: the default behavior usually works for most components, but if you find yourself having issues with components updating (or not updating) as new props are passed in, you may want to take a look at componentWillReceiveProps and shouldComponentUpdate:

componentWillReceiveProps(nextProps) {
  // compare the old props to the new props:
  if (this.props != nextProps) {
    // act on new props, for example
    this.setState({ temp: nextProps.temp })
  }
}

shouldComponentUpdate(nextProps, nextState) {
  // Force the component to update and re-render
  // if the new props or state are different

  // For instance, re-render if the `temp` value in
  // the new state has been changed
  return nextState.temp != this.state.temp
}

shouldComponentUpdate is a particularly interesting method that can be used to optimize components: by default, React components re-render on every state change. React is incredibly efficient and this behavior usually does not lead to performance issues, but if you have slow components, you might want to re-visit and strictly determine when your components re-render.

Component lifecycle methods can be tricky to wrap your head around, and they often are a good first stop in debugging your components if you find they aren’t behaving as expected. The mental model of a React developer is pretty straightforward: data usually flows one direction–down and through components–and it’s usually obvious how and when a component will render. If you find that to not be the case, define lifecycle methods for your component, and at the least, try inspecting the props and state being passed through via console.log.

Want to receive this course in your email inbox? Join the A year with React.js mailing list–it’s instant and easy.

Bytesized offers technical training for companies. Contact us to schedule React.js training for your team today.

Leave a Reply

Your email address will not be published. Required fields are marked *