React Tips and Best Practices
I've spent a good deal of the past year working with React. I've written, refactored, and re-written many components in that time, and I've seen some best practices and anti-patterns emerge.
I'm not going to get into what React is or why you should use it — there are plenty of articles about React floating around the internet. I'm also going to assume you know the basics of working with React and have written a component or two yourself.
Use the PureRenderMixin
The PureRenderMixin is a mixin that overrides shouldComponentUpdate
and only re-renders the component if the props
or state
have actually changed. It is a pretty big optimization on top of React's already good performance. It also means you can call setState
often without worrying about spurious re-renders. You don't have to litter your code with checks like these:
if (this.state.someVal !== computedVal) {
this.setState({someVal: computedVal})
}
The mixin requires that your render()
method must be "pure". It must output the exact same markup given the exact same props
and state
. This means you can't use direct class properties in render()
, or render something differently when a property first changes to a value.
render: function () {
//…
if (this._previousFoo !== this.props.foo) { // <-- IMPURE
return renderSomethingDifferent();
}
}
Using the PureRenderMixin
usually isn't a problem in practice. I've added it to several components after the fact and they continued to work normally. If you do add it and things break, you should refactor the component until it works — typically you're doing something odd.
One important detail is that it only does a shallow comparison of nextProps
and nextState
. If you have fields that are Objects or Arrays, modifying a property of one or pushing a new value will not trigger a component update, since the original reference does not change. Instead, you must make it so changing an object in your props
returns a new reference to a new, updated object.
There are many strategies for accomplishing this. Immutable.js and mori introduce new immutable data types. If you want to work with plain JavaScript objects, you can use React's immutability helpers or my own icepick (toot toot) to pretend that frozen JavaScript objects are persistent data collections.
These libraries do add extra overhead to working with objects in props
, but being able to efficiently detect spurious re-renders is usually worth it. It also is an argument to avoid using objects in props
or state
if you can manage it. Fewer props mean fewer things that can trigger re-renders.
If you're not using the PureRenderMixin, you're missing out on one of React's best features. It simplifies code and is a good performance enhancement.
Use Prop Types
As your application grows and you start composing components into complicated hierarchies, hunting down missing or improper props
can be a pain. Luckily, React offers a way to specify which types a component expects for each of its props
and which props
are required: PropTypes
. Each React class can define a propTypes
map that specifies a validation function for each prop
it is set up to receive. If the validation function fails, a warning will be logged in console. If you forget a prop that isRequired
or pass a string
when an object
is expected, you'll get a helpful message in the console.
propTypes
are also a convenient way to document what props
your component expects and what they do.
Also, the propType
checks are only done outside of production builds, so be sure to set NODE_ENV="production"
to prevent needlessly slowing down your final application. The envify Browserify transform coupled with uglify --compress
is a handy way to do this.
Also, every propType
that is not isRequired
should have a corresponding field in getDefaultProps()
. (This is not always feasible since getDefaultProps()
is only called once per createClass
, not once per instance, meaning any object instances will be shared. getDefaultProps()
only works well for primitive types.) If a prop
is not required and has no sensible default, is it needed at all?
Avoid State
"Avoid State" is a mantra that holds true to all of programming, and React components are no exception. Even though React commendably gives you facilities to work explicitly with state (setState
, etc…) you still should avoid it.
State does not compose well. It can lead to complicated components, convoluted components, and spurious re-renders, even with the PureRenderMixin
. Probably the worst thing you can do it call setState
right after a render in componentDidMount()
or componentDidUpdate()
. While it is tempting to read things from the generated DOM at this point, it means most render()
calls will actually call render
twice. If a child component does the same thing, it could have up to four render()
calls: two from its parent's render, and 2 from its own internal setState()
. As you compose components this can lead to property/layout thrashing and exponentially more re-renders as your component hierarchy depth increases.
Granted, there still are cases where you do need state, but when you do, only use state when it can be perfectly encapsulated by the component. If a parent component needs to know too much about some state on a child component, something has broken down in your component architecture — your abstraction has just become leaky. You should refactor.
Centralize State
So what do you do when you have component state that is required, can't be fully encapsulated, and needs to be communicated up to parent components? In this case, you should pull the state completely out of the component hierarchy and into a central state object. Have the state flow down through your components purely as props
— have no state actually namaged by components. This is a central theme of the Flux architecture — have a set of Stores that hold your application state and event-driven Actions that modify the Stores — and Om — your entire application state is stored in a single atom, with any change to it re-rendering the entire application. Uni-directional data-flow is the goal.
In practice, this means that your components must primarily rely on props
. Instead of calling setState
, they communicate with a central data store of some sort, either through events, streams, channels, callbacks, or calling methods directly. The central data store contains a hierarchical representation of the application state, and triggers a re-render on the entire application on every change. Uni-directional data flow. This may seem inefficient, but one of the mantras of React is “Pure JavaScript is faster than you think it is.” Remember that React is built around intelligent and efficient updates to the DOM. If you have pure components, you can optimize updates to the virtual DOM though the PureRenderMixin
on top of React's already stellar DOM optimization.
This is all a but counter-intuitive, and is not really obvious reading the docs. But having tried it other ways, I realize it is the best way to create anything larger than a trivial application.
The benefit of centralized state and uni-directional data is that your application is easier to reason about. There is a single place where a piece of data can come from. Your app is simpler.
Here is a crude diagram to illustrate:
This can be thought of as a simplified Flux architecture diagram (I've glossed over how exactly messaged from components get to the store and the store triggers re-renders). All you need to know is that whenever the central store is updated, it calls render()
with the new properties on the root component, and the updates propagate through its component hierarchy.
I've found that even if you do not set out to follow the Flux architecture directly, best practices will lead you to something that looks quite similar.
This means your components are simple functions of props
to DOM elements in most cases. They are mostly declarative. Testing them is trivial — just verify the right DOM output given the right props
, and verify the right messages sent after the right DOM events. No need for complicated mocking schemes. Bugs are easy to find — either a component doesn't render properly, doesn't trigger the right events, or your central object doesn't update properly.
When we call setState
or have intra-component communication, the diagram is not so pretty:
The data flow loops back on itself, each prop
no longer has a single origin. The application is fundamentally more complicated — whorls of confusion instead of a simple cycle. Components can re-render needlessly, and property thrashing is possible.
I also would say the pairs of components that communicate through callbacks and events directly are too tightly coupled — the parent component has to know too much about a child component down the line. This is a sign of leaky component abstraction. The lines between components probably should have been drawn differently.
You may also conjecture that with uni-directional data flow, the parent component is already coupled to its child component, since it has to manage the props
it will need. I would say that this "one-way coupling" is okay, it is in fact cohesion. A <li>
has to be told whether it is in an <ol>
or an <ul>
. Also, if your central state object has a hierarchy that mirrors the component hierarchy, you can further avoid coupling — the parent component only has to pass the sub-trees of state that correspond to its child subtrees — it does not need to know what the properties actually are.
render: function () {
return React.DOM.div(
{
ref: "container"
className: this.props.foo
},
this.props.childProps.map(function (props) {
return React.creatElement(Child, props);
});
);
}
Do More in render()
If you find yourself putting lots of logic into componentWillReceiveProps
or componentWillMount
, instead, see if you can move that logic to render()
. If you find yourself wanting to do pre-processing on props
, or write a method named setComputedState()
, simply do that processing in render()
. This again ties into the mantra of "pure JavaScript is faster than you think it is". It's okay to repetitively recompute some things in certain cases. The PureRenderMixin
will already minimize render()
calls, so trying to work around it is a stereotypical premature optimization. It will lead to fewer and more-obvious bugs, and will also prevent you from duplicating code in componentWillMount
and componentWillReceiveProps
.
// bad
componentWillMount: function () {
this.setState({
computedFoo: compute(this.props.foo)
});
},
componentWillReceiveProps: function (nextProps) {
this.setState({
computedFoo: compute(nextProps.foo)
});
},
render: function () {
return React.DOM.div({className: this.state.computedFoo});
}
// better
render: function () {
var computedFoo = compute(this.props.foo);
return React.DOM.div({className: computedFoo});
}
This strategy can also help you minimize the number of fields on props
an state
, and even help you avoid state entirely. Having fewer fields leads to simpler components and makes the job of the PureRenderMixin
easier.
Also, don't be afraid to have your render()
method call other methods that return components. Do more in the render phase, but avoid having long functions by having the actual render()
method delegate out.
Mixins are awesome
Mixins are handy ways to create re-usable chunks of functionality that multiple components will use. I've already talked at length about how the PureRenderMixin
overrides shouldComponentUpdate()
. One cool feature of mixins is that they do not override component lifecycle methods, but rather, they are called in addition. This means a mixin can do setup in its own componentWillMount()
method and clean up in componentWillUnmount()
without conflicting with the host component's componentWillMount
. In general, mixins are useful for isolating related chunks of functionality.
One thing I am experimenting with is putting all state-handling logic in a mixin, even if it is only used by one component. You can then mixin this logic to a parent component (or a central store, if you don't use lifecycle methods) and keep the underlying component stateless. You can then create a standalone stateful component with the mixin that is a thin wrapper around the stateless component. Use the stateful wrapper when it makes sense, and the state-less component with the mixin when you need to compose the component in a larger system.
Additionally, you can test mixins in isolation pretty easily. Either test the methods directly or use the mixin in a dummy React class.
It's Okay to use Instance Properties
Even though components should mostly be pure functions of props
and the occasional piece of state
, it is okay to use direct instance properties from time to time. (By "instance properties" I mean things like this.foo
vs this.props.foo
.) Be careful, because due to the restrictions of the PureRenderMixin
you cannot use them in render()
. They become handy when you have state that will not affect how the component renders, but state that determines how events or messages will be dispatched. For example, I've used a instance property to store a timer that measures how long an image takes to load (dispatching the load time as an event), and another to store an instance of a third-party class that managed scrolling events. Those are both cases where I would not want changes to them to trigger a re-render.
componentWillReceiveProps: function () {
this._timer = Date.now();
},
onLoadHandler: function () {
this.trigger("load:time", Date.now() - this._timer);
},
render: function () {
return React.DOM.img({
src: this.props.src,
onLoad: this.onLoadHandler
});
}
In short, instance properties are handy for managing state that controls how a component communicates with the rest of the application, but do not use the for managing state that controls how a component renders.
Think in Elements
Taking all of these guidelines into account when designing components can be overwhelming. Knowing where to draw the lines between components, what props
to define, and when to use state
can be a daunting challenge. However, as a meta-guideline: Think in Elements. How would your component look you were creating a standard HTML DOM Element? Elements take in a set of attributes (some required), optionally have child elements, and dispatch events that bubble up the DOM hierarchy. They always manage their own state.
Think about an <img>
element. It has the standard base Element properties, like id
, class
or style
. It has a required src
attribute, and width
and height
properties. It does have some state that it manages: initially, it displays nothing (or a placeholder), and the progressive display of the image as it loads. When the image is fully loaded, it dispatches a load
event.
Think about an <option>
element. It has the standard base attributes such as id
, class
or style
. It has a name
attribute that gives it an identifier in a <form>
, and a multiple
attribute that enables an alternate mode. It is expected to have a series of <option>
elements as children, each with a value
attribute, and one with a selected
attribute. It has some state: is the drop-down open or closed? When the selected value change, the <option>
element dispatches a change
event with the new value.
Note the commonalities: declarative attributes (or props
), internal state that is intimately tied to the element and not generally useful outside of the element, and events to communicate with the rest of the DOM. Also note that the vast majority of HTML elements are purely declarative: <div>
, <span>
, <ul>
.
These are aspects you can emulate with your own React components. A small set of declarative props
, some required. Mostly stateless, except when it can be encapsulated by the component. Events to communicate changes and updates, that ultimately can cause property changes. Thinking in elements can help lead to a simple, stable, fast, and predictable application.