React: Making Properties Robust

15 Feb 2015

I’ve sometimes seen components assuming a particular property wouldn’t ever change. It is easy to feel like we can make this assumption when we control the
rest of an application’s ecosystem. However, this makes the component fragile,
because future changes outside of the component may break this assumption. In
other words, the component is not self-contained anymore.

Assumptions that break

Consider a fictional component PostList. It displays an array of article items
(title and date) provided as the property posts. On top of the list, we’ll
show a search input, that will let us filter the list. The list should also be
sorted anti-chronologically.

We know posts will not change because the array is loaded at the start of the
application. So, we decide to sort once and for all in componentWillMount,
hoping it will make subsequent renderings more performant.

Weeks later, a fellow developper, who joined the project recently, implements a
feature to live-reload the newest articles from the server. That breaks the
assumption, the new articles never show up on the page, and time is lost
debugging.

Of course, this is a trivial example. There could be many more layers
in-between the innocent changes and the component that made an assumption. The
investigation could be harder, the consequences worse. And it could be happening
in production.

These considerations lead to an important rule: never make assumptions about
properties that you do not enforce. Be kind to the component users. Instead,
either enforce the property to be constant, or take changes into account.

Enforcing constantness

In our example case, enforcing the posts to be constant would have avoided the issue. In React, this can be done in
componentWillReceiveProps,
lifecycle function called only on updates, not on mount. Let’s have first try:

This forces the caller to pass the same array object every time it
renders the component; not just the same post contents. In other words, the
posts object shall be immutable. This constraint too must also be enforced,
because the array could have been mutated! One way of doing that is using
immutable data constructs, such as provided by
immutable-js. We’ll accept
any Iterable instead of an array:

With this version of PostList, the new live-reload feature now immediately
triggers an exception, pinpointing the issue with clarity.

Transitioning to a changing property

We now no longer want to assume posts is constant, as to support the
live-reload feature. One easy solution, of course, is to make the data-loader do
the sorting, instead of the component. However, for the sake of the exercise,
we’ll add a control allowing the user to chose between alphabetic or
chronological sort, directly into our component. As such, it makes sense to keep
sorting local to the component.

Each time the user enters a new character in the search input, we set
the state, triggering a new render. This means we sort the same list over
and over again. On a large array, this could cause small delays, noticeable
enough to make the experience unpleasant.

Memoizing the sorted array

Memoization is a classic
optimisation technique, where we store the result of expensive functions into a cache. The structure of the cache can be very diverse. In some cases, results
are stored forever. For most cases, a smarter cache algorithm will be used, such
as an LRU cache.

In our specific case, it is very unlikely for the immutable Iterable to be
reverted to an older value. We can simply store the latest result: