Component Brick and Mortar
The React documentation I wish I had a year ago
When I started learning React, I immediately took to its simplicity. One cursory look at the React doc page had me writing my first component. Within a day or two I had a functional todo app.
And then I hit a wall.
The basics made sense to me. I understood unidirectional data flow. I had a working knowledge of what the virtual DOM was for. Props and state made sense. What really gave me fits was how to build a reusable component. I found that I was writing components that were very similar behaviour-wise and subsequently was writing lots of duplicate and hard to maintain code.
After a bunch of research, discussions, and experimentation I was able to uncover a few invaluable patterns, most of which are not documented well. Here’s the documentation I wish I had when I first started learning React.
Setting The Stage
My first (and only) project at MetaLab has been built with React. I’ve been on the project for about 7 months now, and as a team we’re still shipping features on a weekly, if not daily basis. The team I’m on is small, but because we follow good patterns we’re able to build features that are relatively bug-free, easy to understand, and fairly easy to extend in the future (by any developer).
Virtual DOMs, Flux/Redux, “Data down, actions up”, etc are hot topics at React meetups. Each of those are very interesting concepts and deserve discussion. However, time and time again the problem that I always run into is how to get a team to be able to maintain a project of y size over x time (months if not years). I love working with everyone on my team. And I really enjoy teaching and learning from some really smart pals. You should too.
You know what’s OK to not enjoy? Spending hours or days tracking down hard to re-create bugs. Really hard to diagnose bugs that seem trivial but somehow aren’t (remember how for years Facebook couldn’t get “Mark All As Read” in the notifications dropdown to work properly?).
React is a great tool for reducing the amount of complexity in your app. I can’t promise you’re going to write bug-free code. But, what React does do is offer patterns that make it much easier for you and your team to take a bunch of different chunks of code, which are each responsible for some feature, and to glue them together in a maintainable, scalable fashion. The sections below outline these patterns.
Let’s Make Some Happy Little Trees
The light bulb went on for me when I started thinking of my app as a tree. Now, this isn’t a novel concept to front end developers. The DOM is a tree of HTML elements. And really, that’s all React is. It’s an abstraction that targets the DOM with its own lightweight JavaScript objects. You can read more about it here.
In most JavaScript UI frameworks you have a concept of a mount point. This is usually an empty DOM node that you render your application to. React is no different. Thinking in terms of a tree, I like to imagine this mount point as the root, or trunk of the tree. Any components rendered by the root component can be considered branches. For example your app might render a header, which in turn may render a logo, a search bar, some notifications, and a profile dropdown (e.g. once a user is logged into their Facebook profile). Each of those may then render one or more components and on and on until you hit a leaf.
I get that this isn’t a really complicated concept once you hear it. It took a while for me to come across the tree analogy. Once I started to break down disparate parts of my app into reuseable and composable components, I was able to build Good UI™.
Good UI™
OK, here comes our first and probably only formal definition:
“Composability is a system design principle that deals with the inter-relationships of components. A highly composable system provides recombinant components that can be selected and assembled in various combinations to satisfy specific user requirements.”
Composability. That’s a term that computer scientists use to describe how to build Good UI™ (probably).
To satisfy the definition of composability, you need to make sure your components are self-contained and stateless.
For a component to be self-contained it cannot rely on any other specific components. A parent component is going to contain children components. But to be truly composable, your components must not rely on the existence of specific components.
This is really just another way of saying your components should be pure functions.
Additionally, a component should be stateless. In React, most components should not maintain its own state. Instead it should defer state-management to a parent container sometimes called container components.
Here’s an example to illustrate what I mean by this.
The above example adheres to our definition as each component (pure function) does not maintain state and they do not rely on each other. f(x) could be used anywhere else in our application as it does not rely on g(x) being available.
This is the great thing about React. When you define each component, you define the parameters (props) that each component expects to receive and then pass on to child components. This creates a very easy to understand interface between your various components.
Single Responsibility Principle
You’ll find that you’re going to be writing a lot of components real quick. This leads to more code and knowing you, the ~lazy~ 10x developer, you’re going to want to get clever. You’re going to have to fight this feeling.
Laziness leads to cleverness. Cleverness leads to mistakes. Mistakes lead to suffering.
— Me, 2015
I avoid suffering by sticking to the Single Responsibility Principle (SRP). I use it as a guideline for making sure my components follow composition guidelines. Each component should only be doing one thing. One thing is a loose guideline, but with experience you’ll understand when there is too much within one component and when to split into multiple components.
Data Down…
The first way to split up your components is into business logic and display logic. In React circles you may sometimes see this referred to as smart and dumb components or container and presentation components. The container components handle any state and changes to state (ViewController). That state is then passed down as props to your presentation components (View). Then, in a read-only like fashion, presentation components render DOM elements based on the props they receive.
A few code smells when doing data down:
- your containers require any type of styling
- your presentation component is comparing this.props and this.state
- your presentation component is using an iterator other than map or reduce
- you’re passing anything other than a function, shallow-array, shallow-object, string, boolean, or integers
Remember, React components don’t have to actually emit any HTML. It is possible and quite useful to only render another React component.
Here’s an example of a container and presentation component.
… Actions Up
Nice, we can take some data and turn it into pretty HTML/CSS. What about handling user interactions?
If you’re familiar with Backbone events you’ll probably know the pain of global event busses. React doesn’t have any concept of a global event system. Instead, you should be passing a function as a prop from your container down to the presentation component. This function prop is then attached to an event handler, much like you’re used to with document.addEventListener. When a user interacts with your your app (e.g. clicking a button), the event handler is called.
The reason for doing this is again to separate logic and presentation. By keeping state management logic in the container component and not the presentation component, you’re able to build very composable components.
Reusable
We understand how to break up our components so they adhere to our earlier definition of composability. But composable components are not necessarily very reusable. It’s quite easy to have different sets of container/presentation components that share some kind of business logic or display rules. Even worse, you may have built your components in such a way that they don’t quite wire up in a general way.
PropTypes
Non-Production builds of React come with what are called PropTypes for defining the props that your component expect. PropTypes are a set of validators to check if a prop is valid (proper type, if it’s being set).
PropTypes are a great tool for initial component architecture. They setup clear interfaces (props) that your component either exposes or expects to interact with. I’ve found this is especially true for making generic container components that can handle a broad spectrum of presentation components.
On top of that, PropType definitions are self-documenting. Remember how I said our small team can dive into generally any part of our app’s UI and have a good understanding of what’s going on? Well, that’s in part because of the clearly defined contract between components declared by PropTypes.
A great abstract idea for thinking of the types of props that need to be passed around is the idea of an adapter. As you get more experienced, you’ll find yourself making more generic props to allow more different components to wire up to each other.
In certain scenarios, you might find you create components that act as adapters to bridge the gap between two previously defined components. Take note that this could be a code smell and that really you should refactor the components involved. But with experience you’ll know when this is a valid-use case.
Pass explicit Props
Don’t get clever and pass a giant object which is consumed by a child component. If a component expects a first and last name, pass those in. Don’t pass in name={{firstName: ‘Paul’, lastName: ‘Ruescher’}}. Instead pass down firstName={‘Paul’} lastName={‘Ruescher’}.
This is especially true when you’re using other libraries with React. I’ve used Backbone to handle my model concerns before. In this scenario, I was making a component to handle loading up a collection. My first attempt was to create a component that received a collection object, then on componentWillMount I’d call the fetch method on the collection object. A much better approach would be to have passed in the fetch method as a prop and then called that prop within componentWillMount.
Strap In. I’m About To Open Some F*****g Windows.
Once you’ve thought through the requirements of each component, you need to create them in ways that allow for composition. There are a few ways to go about this.
Depending on your background, you might be inclined to use mixins. Don’t. Using mixins in React is fraught with issues. Instead, use the composability patterns we already know. If different parts of your app require fetching a model, create one container for fetching data, then pass that state down into any number of different presentation components. From there, handle any interactions your user might cause.
Creating good container components in React took me a while to really get the hang of. The key was to understanding how to use any number of presentation components within a given container component. One of the earliest examples of container components I came across was by Michael Chan. The example does a great job of satisfying our definition of composition. State and presentation are separated according the patterns we outlined earlier. But it doesn’t explicitly show how to use different presentation component within a single container component.
To make this component more reusable we have two options. One is to pass in our presentation component as a prop and use that prop in our render function:
Remember that JSX is just syntactic sugar. JSX is just syntactic sugar for a React function (<Component /> → React.createElement(‘Component’);). Now, we could pass in any presentation component assuming that component expects to receive whichever props the container passes to component it renders.
This works, but just feels awkward to me. Whenever you want to use this container/presentation combination, you will need to require two files and somewhat clunkily include one component into the other. I find this to be frail.
Enter higher-order components. HoCs are very similar to higher-order functions. In higher-order functions you pass one function to another function which returns a function. How does that help us? Well, with higher-order components, you pass a component (which as we know is just a function) to another function, which returns a component (again, is just a function).
This really confused me at first, especially since a lot of the examples were using decorators which are an ES7 idea taken from Python (funny, I know zero Python).
Let’s see a higher-order component as an example
As you can see, we still have the concept of container/presentation components, but we utilize a slightly different pattern for passing props to our presentation component. Whenever we want to show a notification list we used the HoC-ized presentation component within our app.
As with everything in programming, these patterns are not a silver bullet. All they are are guidelines. It’s still up to the developer to write the code that puts pixels on the screen.
That being said, I’ve found these patterns to be very forgiving when mistakes are made. As I’ve said, our code is less error prone, more maintainable, and a wider range of experience can dive into any part of the project.
If you have any questions or have any feedback, you can find me on Twitter.
Shout out to Jason & the MetaLab team, the React & Ember communities (especially Dan Abramov who is linked to extensively), and so many other people that contributed without even knowing it. Thanks all of you ❤