One year with React

This is the final post in Bytesized’s A year with React.js series, which explores increasingly complex aspects of being a React developer. This post covers your first year with React components––component testing, future-proofing, and introducing data flow to your application, with Redux. It’s strongly recommended that readers start this series from the beginning, with the first post, “One hour with React”.

In this series, we’ve covered the basics of React components, but we pretty quickly moved past the basics of rendering HTML and writing component classes into the more complicated parts of the framework: component lifecycles, structuring your application, and looking towards the future with high-quality open-source tooling. In this final part of the series, we’ll continue to delve more into what it means to build a React application, one year in, that feels well-crafted. It might seem like over-optimizing to worry so much about well-crafted code. Clean Code, referenced in the previous post, has this to say about code that isn’t well-crafted:

If you have been a programmer for more than two or three years, you have probably been significantly slowed down by someone else’s messy code. If you have been a programmer for longer than two or three years, you have probably been slowed down by messy code. The degree of the slowdown can be significant. Over the span of a year or two, teams that were moving very fast at the beginning of a project can find themselves moving at a snail’s pace. Every change they make to the code breaks two or three other parts of the code. No change is trivial. Every addition or modification to the system requires that the tangles, twists, and knots be “understood” so that more tangles, twists, and knots can be added. Over time the mess becomes so big and so deep and so tall, they can not clean it up. There is no way at all.

As the mess builds, the productivity of the team continues to decrease, asymptotically approaching zero. As productivity decreases, management does the only thing they can; they add more staff to the project in hopes of increasing productivity. But that new staff is not versed in the design of the system. They don’t know the difference between a change that matches the design intent and a change that thwarts the design intent. Furthermore, they, and everyone else on the team, are under horrific pressure to increase productivity. So they all make more and more messes, driving the productivity ever further toward zero.

Every programmer would like to think that their code is well-crafted. But bad code doesn’t happen with huge, sweeping decisions about features. It happens each time we skimp on cleanly separating logic between modules. It happens when we leave a variable poorly named, or when we don’t implement error checking because it takes just a bit too long. It’s easy to point at a particularly complicated feature that was implemented quickly and say “This is why our application is bad!” My experience has always been quite the opposite: it’s when we revisit an old part of our application and decide that it’s not worth it to use the same coding style as our new work; when we see lazy code in code review and make a mental note to re-think it later, but don’t ever leave actual time to handle tech debt.

This problem has come to a head in what is now likely the most popular and fastest growing language in the world: JavaScript. The unique combination of the explosion of the internet and the deceptively simple style of the language has made web development a singularly approachable avenue for new programmers. The result is a generation of code that missed Clean Code; one where shipping is prioritized over crafting something with a long life.

Future-proofing

JavaScript is infamous as a fast-moving language. It’s a common jab on Hacker News to suggest that because a new hobbyist re-implementation of React has been released on GitHub, that every React team will drop everything and move to the new hotness. Consider Vue.js. In the last few years, Vue.js has become known as “the React killer”. Of course, React itself was “the Angular killer” or “the Ember killer” only a couple years ago! Vue itself is closer to Angular in design, but because of React’s current status as the most prominent or hyped framework in the JavaScript developer community, Vue has been touted as the next hot thing.

So while I audibly groan when I see someone make fun of JavaScript developers for jumping from bandwagon to bandwagon, this joke has its merits: the community has seen a huge amount of innovation in the last decade, and being a JavaScript developer in 2017 means something quite different than what it meant in 2007. A JavaScript developer pre-Node, pre-Angular, pre-React was someone using jQuery or something similar to enrich their HTML and CSS websites. When AJAX came along, JS developers began scratching the surface of fully interactive applications. In 2017, a JavaScript developer could be entirely removed from the browser, the application JS was designed for! A Node developer is handling the same complexity that backend developers were fifteen years ago, but with a programming language built with entirely different concerns.

It’s because of this unique combination of JavaScript’s increased scope and responsibility in the programming community and, frankly, its shortcomings as a language, that makes the idea of “future-proofing” your JavaScript applications such a pressing concern. It’s hard to say what the future of the language is: a few years ago, many JS developers were ready to move entirely to CoffeeScript full-time. When ES6, the newest version of JavaScript, began making its way into the hands of development teams, tools like Babel replaced CoffeeScript seemingly overnight. The developer experience for JavaScript can change constantly. I’m sure that at this point, even I’m a bit behind on what the latest and greatest is! But how does this affect React? Assuming that we stick with React and don’t jump ship to the hottest new framework, is this idea of churn, or the rate of change in technologies and frameworks, going to cause our React apps to stop working overnight?

The truth is that React has remained quite consistent in its lifetime. The original premise of React–providing a performant rendering engine for lightweight components–has stayed constant from version 0.1 to version 15, the current version at time of writing. The definitions and naming, like all software projects, saw some changes in the first couple years. Facebook’s wonderful “Our First 50,000 Stars” explores the first three-and-a-half years of the project, and acknowledges this problem–no doubt because of React’s status in the JS community, this rollout process was closely examined, and perhaps mocked by the community:

During the first year of React, internal adoption was growing quickly but there was quite a lot of churn in the component APIs and naming conventions:

  • project was renamed to declare then to structure and finally to render.
  • Componentize was renamed to createComponent and finally to createClass.

Notably, the render method, arguably the most important method for React components, went through four iterations to settle into what it is today. These kind of changes are to be expected in the initial life of a project. Compare that to the increasingly detailed and helpful CHANGELOG file for React releases, which almost always contains many multiples more of deprecation warnings and warnings over actual breaking changes. The framework itself is quite stable!

As for your applications themselves, building applications that can weather the storm of JavaScript churn can almost be reduced to simply focusing on writing clean, maintainable code. Given the immensity of the JavaScript developer community and ecosystem, it’s not surprising that there are dedicated resources to this very topic. Addy Osmani’s “Learning JavaScript Design Patterns” is a great resource, and also is entirely free on his website. Facebook’s own “Thinking in React” serves as almost a bible on the React state of mind. React, of course, also benefits from an incredibly active developer community. Projects like React Newsletter collect some of the best articles and projects in the React community every week, and can be thought of as taking the pulse of the current state of the ecosystem.

Component testing

Clean code, as we discussed in the previous post in this series, is about building things that work long-term. There is likely no better strategy for testing the long-term viability of your application than testing. Writing tests for your components is the best thing you can do to stabilize your application, and the React ecosystem provides an incredible variety of really effective tools to help writing tests not be a chore.

The most prominent of these is Jest, created by Facebook. Jest is designed to work out-of-the-box: in a world of complicated testing tools and developer experience, Jest is a breath of fresh air and makes beginning to write tests trivial. To confirm this, we’ll build a component using Test-Driven Development, and ensure that as we update it, the tests continue to pass, and provide us a stable application.

Earlier in this series, we used the concept of a blog application to model various components–the most important of these being, as you can imagine, a Post. A post, in our simple example, consists of just a title and content.

We’ll use the tool create-react-app to prototype components and their associated tests in this section. I’ve created a GitLab repo where you can download the source code–given a functioning npm install, you should be able to run npm install and npm test to run the tests locally. In the interest of brevity, I’ve removed import statements and npm install directives from the below code samples: please check the GitLab repository for a pre-made package.json file, and components with all the necessary modules imported. As we proceed through this example, relevant pieces of code will also be included below.

Post and HeaderText

class Post extends React.Component {
  render() {
    return (
      <div>
        <h1>Post title</h1>
        <p>Post content</p>
      </div>
    )
  }
}

To begin testing this component, we should first clarify what the desired behavior is. In the above example, there are two important pieces to the post: the title, and the content. Given the file Post.js, we’ll create a matching Post.spec.js test file, and populate it with our first test:

import React from 'react'
import ReactDOM from 'react-dom'
import Post from './Post'

it('renders a post', () => {
  const container = document.createElement('div')
  ReactDOM.render(<Post />, container)
})

Running npm test should confirm that the tests pass. For most machines, this command will setup functionality to re-run tests whenever a file changes in this directory: if this isn’t working for you, simply re-run npm test whenever we mention updates to files or tests.

In this simple test, we’ve just ensured that the Post component renders; it doesn’t check for any particular data, just that it doesn’t “blow up”, because of syntax errors or similar issues.

In the previous parts of this series, we’ve explored breaking down components into sub-components, for later re-use. To begin doing this, we’ll remove the h1 tag from Post, and replace it with a HeaderText component, which accepts text to display via the title prop.

class HeaderText extends React.Component {
  render() {
    return (
      <h1>{this.props.title}</h1>
    )
  }
}

class Post extends React.Component {
  render() {
    return (
      <div>
        <HeaderText title="My title" />
        <p>Post content</p>
      </div>
    )
  }
}

With the addition of HeaderText.js, we can also add specs, at HeaderText.spec.js, to ensure that the component renders as expected:

it('renders header text', () => {
  const container = document.createElement('div')
  ReactDOM.render(<HeaderText title="My title" />, container)
})

Snapshots

One of the most intriguing features recently added to Jest is snapshots. Snapshots read the structure of your component at the time of testing, and write the structure to a JSON file in your test directory. In doing this, you essentially get a free test in the future–a simple check to compare the new structure at the time of the test running, to the previously stored component structure. We’ll add tests to Post and HeaderText by including the renderer function from the react-test-renderer package, capturing the component structure, and matching it:

// Post.spec.js
it('matches the snapshot of a post', () => {
  const tree = renderer.create(<Post />).toJSON()
  expect(tree).toMatchSnapshot()
})

// HeaderText.spec.js
it('matches the snapshot of header text', () => {
  const tree = renderer.create(<HeaderText title="My title" />).toJSON()
  expect(tree).toMatchSnapshot()
})

This additional test should pass without incident. To test the snapshot behavior, we can change the title prop passed in:

// HeaderText.spec.js
it('matches the snapshot of header text', () => {
  const tree = renderer.create(<HeaderText title="My new title" />).toJSON()
  expect(tree).toMatchSnapshot()
})

In doing so, we can see that the new structure does not match the previously stored snapshot:

FAIL  src/HeaderText.spec.js
 - matches the snapshot of header text

   expect(value).toMatchSnapshot()

   Received value does not match stored snapshot 1.

   - Snapshot
   + Received

   @@ -1,3 +1,3 @@
    <h1>
   -  My title
   +  My new title
    </h1>

     at Object.<anonymous>.it (src/HeaderText.spec.js:20:16)
     at process._tickCallback (internal/process/next_tick.js:103:7)

This failure, luckily, is expected. In this case, we can simply press the “u” key, to update our failing snapshots. Given a different instance, this test might be unexpected, and simply having a snapshot saved us from headaches in the future.

Content

Finally, we’ll implement the Content component, which will accept a text prop and be the body of our blog post:

class Content extends React.Component {
  render() {
    return (
      <p>{this.props.text}</p>
    )
  }
}

Our Post component now contains two sub-components, each accepting dynamic values via props:

class Post extends React.Component {
  render() {
    return (
      <div>
        <HeaderText title="My title" />
        <Content text="My post content. Testing, testing!" />
      </div>
    )
  }
}

We’ll essentially copy and paste over the tests for Content.spec.js, building a simple “it renders” test, and a snapshot test:

it('renders content', () => {
  const container = document.createElement('div')
  ReactDOM.render(<Content text="My post content. Testing, testing!" />, container)
})    

it('matches the snapshot of content', () => {
  const tree = renderer.create(<Content text="My post content. Testing, testing!" />).toJSON()
  expect(tree).toMatchSnapshot()
})

Our new Content test passes, but now our previous Post snapshot test fails! This is, again, to be expected: internally, the structure of the Post component hasn’t actually changed (Content is, after all, just a p tag), but the text has changed. We’ll update the snapshots again with the “u” key.

Finally, I’d like to add one more function to the HeaderText component–the ability to pass in a CSS class name, to style the h1 tag depending on where it is being used. For instance, header text for a blog post will be much different than the header text used for a sidebar, or for the title of the blog itself. Let’s add an additional cssClass prop to HeaderText, passing it into the h1 tag or defaulting to the empty string "" if no cssClass is received:

class HeaderText extends React.Component {
  render() {
    return (
      <h1 className={this.props.cssClass || ""}>{this.props.title}</h1>
    )
  }
}

(Note-this code could be refactored by using React’s defaultProps functionality, to set cssClass to an empty string if it isn’t passed in directly. I’ll leave this as an exercise to the reader.)

When we save this file, we should see two test failures. Can you guess what they are? Both the Post component and the HeaderText component now fail, given that the structure of the h1 tag in HeaderText now includes a className prop. Update the snapshots to see that the tests should pass again.

We should actually ensure that the prop cssClass is being passed through. To do this, we’ll make use of the tool enzyme, a collection of React testing utilities from Airbnb. The most useful part of enzyme is the shallow function, which allows unit testing of individual components, as opposed to the entire child structure of a component. From the project’s README:

Shallow rendering is useful to constrain yourself to testing a component as a unit, and to ensure that your tests aren’t indirectly asserting on behavior of child components.

In this case, shallow rendering will allow us to simply check the first layer of HeaderText: although it doesn’t have any child components now, this may change in the future. With our shallowly-rendered component, we can find the h1 tag and confirm that the cssClass prop has been passed to the h1 tag as a class:

it('passes the cssClass prop to the header text class', () => {
  const component = shallow(<HeaderText cssClass="post-title" title="My title" />)
  expect(component.find('h1').hasClass('post-title')).toBe(true)
})

These tests give us a great starting point to continue to build out our blog application. As we add new features or components, our snapshots in particular will indicate how the application is changing from a UI perspective: we can either update the snapshots when known and expected test failures occur, or fix accordingly when a failed snapshot comparison is surprising or unexpected. Additionally, adding this testing process to a continuous integration tool, or as part of your code review process, can be an incredibly effective way to work with a team and build stable applications.

Introducing data flow to your application

Prototyping an application in React is really easy–building static components to template out how things will look can be done in even a matter of hours. When you begin to add dynamic pieces to your application that require retrieving and sending data, it’s very likely you’ll end up exploring tools like Redux or MobX. Both of these tools deal with the problem of managing state. State can be simple: instead of our above Post example having a hard-coded title and content text, it could be a JavaScript object:

{
  title: "My post title",
  content: "My post content"
}

A collection of posts is no different–instead of a single JavaScript object, our state becomes an array of objects:

[
  {
    title: "My post title",
    content: "My post content"
  },
  {
    title: "My newer title",
    content: "My newer post content"
  }
]

Dealing with this kind of state is straightforward: you could probably figure out how to render a collection of Post components fairly intuitively given this state, maybe doing something like:

const posts = getState(posts)
return posts.map(post => <Post title={post.title} content={post.content} />)

The above code, with the exception of the abstracted getState call, just creates a collection of Post components, filling in each title and content prop with the relevant field from the current post.

Given a more complicated application, managing state isn’t so clear. Consider this example, adapted from a React-based task manager application I was building last year:

{
  tasks: [
    {
      id: "5ec862a1-caca-4b45-b594-1fad523bc046",
      title: "Take the garbage out",
      context_id: "9a1d3479-ff8a-4f54-ae33-ffbca6b81760",
      project_id: "c1547e80-db77-4d32-8a80-00a8cac55122",
      created_at: "2017-03-12T09:14:43+00:00",
      updated_at: "2017-05-14T10:23:01+00:00",
      due_at: "2017-05-15T22:00:00+00:00",
    }
    // ... additional tasks
  ],
  contexts: [
    {
      id: "9a1d3479-ff8a-4f54-ae33-ffbca6b81760",
      name: "Home",
      created_at: "2017-03-01T09:10:01+00:00",
      updated_at: "2017-03-01T09:13:45+00:00",
    }
  ],
  projects: [
    {
      id: "c1547e80-db77-4d32-8a80-00a8cac55122",
      name: "Daily tasks",
      created_at: "2017-03-02T12:35:15+00:00",
      updated_at: "2017-03-02T12:35:15+00:00",
    }
  ],
  config: {
    hideSidebar: true,
    shiftReturnToConfirm: false
  },
  user: {
    authentication: {
      token: "<token>",
      expires_at: "2017-05-21T00:00:00+00:00",
    },
    id: "b9369559-ed1f-40a1-ab7f-32749766f351",
    name: "John Smith",
    email: "[email protected]",
  }
}

This example contains a great deal of complexity: the state itself can be represented as an object, but even one level deep we can already see a combination of arrays (tasks, contexts, projects) and objects (config, user). Abstracting getState before seemed easy; now, it’s worrying to not know how we’re retrieving state. Let’s briefly touch on Redux, to see how the basics of how it would handle managing something like the above state.

The first thing you’ll notice, out of neccesity, is the sheer size of the above state object. This object, condensed in the above example, contains all of a user’s tasks, projects, and contexts for a Getting Things Done application, as well as the authentication and configuration logic for the user themselves. For even a light user of this application, the state object consumed by the React application is quite large.

Redux is built to contain that state, and interface with it in a predictable way. In fact, predictability is likely the most important element of Redux, with the project’s homepage referring to it almost immediately:

I wrote Redux while working on my React Europe talk called “Hot Reloading with Time Travel”. My goal was to create a state management library with minimal API but completely predictable behavior, so it is possible to implement logging, hot reloading, time travel, universal apps, record and replay, without any buy-in from the developer.

Redux introduces a new aspect to React components–the concept of containers. Containers are components that interface with the application’s Redux store, which contains the application state. A Tasks container, for instance, would retrieve all of a user’s tasks from the store, and use them to render the individual Task components.

If you’re familiar with Getting Things Done or are just particularly quick, you might also notice that a task has a corresponding context and project. Therefore, it isn’t enough just for a Tasks container to know about tasks; it also needs to know about projects and contexts. Clearly, the problem of state management becomes complex almost immediately.

Redux is designed to be unidirectional: data flows a single direction, down. This means that data isn’t passed back and forth between components–a TaskForm component, where a user can edit a task’s name or other information, isn’t the “source of truth” for that task’s data across the application.

Imagine a simple Task component, with a checkbox to indicate the task is complete:

When you update the task to complete, where does the fact that this task is now complete get stored? Do other components, for instance, one that indicates how many incomplete tasks are available, now know that this task is complete? To solve this, there are two potential solutions:

  1. Each component listens to every other component, as needed. A IncompleteTasks component, which shows how many tasks are left to complete, listens to every Task component. When a Task component performs an update, every component watching that component also updates as needed.
  2. Each component receives state as needed, and when a change happens, any relevant updates come through the entire component structure, causing re-renders as needed.

My gut feeling when I began using Redux was that the first option was faster. Re-rendering sound expensive, after all! It turns out that Redux, which is described by the second option, is not only incredibly efficient, but a lot easier to reason about, and to work with on a day-to-day basis.

We’ve spent a lot of time talking about component trees: a Blog component contains many smaller components. In the same way, Redux state can be thought of as basically a tree, since it fundamentally is just a JavaScript object. Because of this, putting together components and state is like tying certain parts of the app to certain parts of the state tree. The aforementioned Tasks container needs to know about tasks, projects, and contexts: when we initialize the Tasks container, we simply pull out those parts of state, and pass them in as props:

class Tasks extends React.Component {
  retrieveProjectForTask(task, projects) {
    return projects.find(projects => project.id == task.project_id)
  }

  retrieveContextForTask(task, contexts) {
    return contexts.find(context => context.id == task.context_id)
  }

  render() {
    return this.props.tasks.map(task => {
      return (
        <Task
          context={this.retrieveContextForTask(task, this.props.contexts)}
          project={this.retrieveProjectForTask(task, this.props.projects)}
          task={task}
        />
      )
    })
  }
}

(Side-note: the savvy among you will recognize the above code as very inefficient. Don’t worry: I wouldn’t write something like this in production, and would substitute the retrieveProject/ContextForTask functions for a more efficient, likely hashtable-based solution. Take a deep breath!)

Our container knows about state, and knows what things the Task component needs to properly render. Instead of Task also needing to know about state, and how tasks, contexts, and projects all fit together, it simply receives task, context, and project, and takes what it needs from them:

Updating a task is conceptually simple, but perhaps out-of-scope for a detailed explanation at this point in the series (good thing we have a Redux series too–see below!). The gist is this: an action is passed into the Task component, which, when called, updates the Redux store at the top-level. When the update is successful, the new version of state is passed all the way down the component tree, ultimately resulting in the Task component that called the original action being updated. Instead of having components watch each other, all components, keeping in the common React theme, simply the output of a render function and their input, which is the updated state.

Looking for a deeper dive into Redux? Still not quite clicking? Be sure to check out our new Redux introduction series, Redux manufacturing.

Conclusion

This series has explored some of the lesser-talked about parts of React–there are a number of tutorials covering the basics of components, and build tooling, but not as many about why React is built the way it is, or how it lends itself to sturdy, long-lived code. This series is somewhat abstract in that way; we’ve looked at actual code in a variety of situations, from the basics, to writing tests, to our most recent peek at why we would consider Redux in more complex applications.

Still have questions? Check out the comment section below, or engage with the truly excellent React.js community on Twitter, GitHub, Stack Overflow, and a number of other places.

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. If you or your company want to take a more comprehensive dive into React.js, including hands-on workshops, lectures, and take-home exercises, contact us to schedule React.js training. We’re excited to work with you.

Leave a Reply

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