We go so that we can tell others about it! Photo by NeONBRAND on Unsplash

How to NOT React: Common Anti-Patterns and Gotchas in React

Arfat Salman
codeburst

--

What is an anti-pattern? Anti-patterns are certain patterns in software development that are considered bad programming practices. The same pattern may have been considered correct at one point in the past, but now developers have realised that they cause more pain and hard-to-track bugs in long term.

React has matured as an UI library and with that a lot of best development practices have evolved over the years. We are going to learn from the collective wisdom of thousands of programmers and developers who learnt those things the hard way.

Truly said!

Let’s begin!

1. bind() and arrow functions in Components

You must have bound your custom functions in the constructor function before using them as props for components. If you declare components using the extends keyword, then the custom functions (such as updateValue below) lose their this bindings. So, if you want to access this.state, or this.props or this.setState then you need to re-bind them.

Demo

class app extends Component {
constructor(props) {
super(props);
this.state = {
name: ''
};
this.updateValue = this.updateValue.bind(this);
}
updateValue(evt) {
this.setState({
name: evt.target.value
});
}
render() {
return (
<form>
<input onChange={this.updateValue} value={this.state.name} />
</form>
)
}
}

Problems

There are two ways to bind the custom functions to the component’s this. One way is to bind them in the constructor as done above. The other way is to bind at the time of passing as prop value —

<input onChange={this.updateValue.bind(this)} value={this.state.name} />

This method suffers from a problem. Since .bind() creates a new function each time it is run, this method would lead to a new function being created every time the render function executes. This has some performance implications. However, in a small app it may not be noticeable. As the app grows large, the difference will start to materialise. One case study is here.

Arrow functions entails the same performance concerns that were there with bind.

<input onChange={ (evt) => this.setState({ name: evt.target.value }) } value={this.state.name} />

This way of writing is definitely clearer. You can see what’s going on in the onChange prop itself. But, this also creates new anonymous function every time input renders. So it has the same performance penalty as above.

Edit — I came across this article thanks to Rob Wise. Turns out, there hasn’t been any performance measurements as to how slow inline functions are. Also, most of the time, they are equally performant. Check out the article for a more in-depth discussion on this.

Solutions

The best way to avoid the above performance penalty is to bind the functions in the constructor itself. This way only one extra function is created at the time of component creation, and that function is used even when render is executed again.

It often happens that you forget to bind your functions in the constructor, and then you get an error (Cannot find X on undefined.). Babel has a plugin that let’s you write auto-bound function using the fat-arrow syntax. The plugin is Class properties transform. Now you can write components like this —

class App extends Component {
constructor(props) {
super(props);
this.state = {
name: ''
};
// Look ma! No functions to bind!}updateValue = (evt) => {
this.setState({
name: evt.target.value
});
}
render() {
return (
<form>
<input onChange={this.updateValue} value={this.state.name} />
</form>
)
}
}

Read More —

2. Using indexes in key Prop

Key is an essential prop when you iterate over a collection of elements. Keys should be stable, predictable, and unique so that React can keep track of elements. Keys are used to help React easily reconcile(read: update) the differences between the virtual DOM and the real DOM. However, using certain set of values such as array indexes may break your application or render wrong data.

Demo

{elements.map((element, index) =>
<Display
{...element}
key={index}
/>
)
}

Problems

When children have keys, React uses the key to match children in the original tree with children in the subsequent tree. The keys are used for identification. If two elements have same keys, React considers them same. When the keys collide, that is, more than 2 elements have the same keys, React shows a warning.

Warning for duplicate keys

Here is an example of the issues that can be caused by using indexes as keys on CodePen.

Solutions

Any key that you are going to use should be —

  • Unique — The key of an element should be unique among its siblings. It is not necessary to have globally unique keys.
  • Stable — The key for the same element should not change with time, or page refresh, or re-ordering of elements.
  • Predictable — You can always get the same key again if you want. That is, the key should not be generated randomly.

Array indexes are unique, and predictable. However, they are not stable. In the same vein, random numbers or timestamps should not be used as keys.

Using random number is equivalent to not using keys at all since random numbers are not unique or stable. The components will be re-rendered every time even if the content inside the element has not changed.

Timestamps are unique but not stable or predictable. They are also always increasing. So on every page refresh, you are going to get new timestamps.

In general, you should rely on the ID generated by databases such as primary key in Relational databases, and Object IDs in Mongo. If a database ID is not available, you can generate a hash of the content and use that as a key. You can read about more about hashes here.

Read More —

3. setState() is async

React components essentially comprises 3 things: state ,props and markup (or other components). Props are immutable. However, the state is mutable. Changing the state causes the component to re-render. If the state is managed internally by the component, this.setState function is employed to update the state. There are a few important things to note about this function. Let’s look —

Demo

Problems

Focus on line 11. If you mutate the state directly, the component will not be re-rendered and the changes will not be reflected. This is because the state is compared shallowly. You should always use setState for changing the value of the state.

Now, in setState if you use the value of current state to update to the next state (as done in line 15), React may or may not re-render. This is because, state and props are updated asynchronously. That is, the DOM is not updated as soon as setState is invoked. Rather, React batches multiple updates into one update and then renders the DOM. You may receive outdated values while querying the state object. The docs also mention this —

Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.

Another problem is when you have multiple setState calls in a single function, as shown above on line 16 and 20. Initial value of the counter is 350. Assume the value of this.props.increment is 10. You might think that after the first setState invocation on line 16, the counter’s value will change to 350+10 = 360. And, when the next setState is called on line 20, the counter’s value will change to 360+10 = 370. However, this does not happen. The second call still sees the value of counter as 350. This is because setState is async. The counter’s value does not change until the next update cycle. The execution of setState is waiting in the event loop and until updateCounter finishes execution, setState won’t run and hence won’t update the state.

Solution

You should use the other form of setState as done on line 27 and 31. In this form, you can pass a function to setState which receives currentState and currentProps as arguments. The return value of this function is merged in with the existing state to form the new state.

Read More —

4. Props in Initial State

The React docs mention this anti-pattern as —

Using props to generate state in getInitialState often leads to duplication of “source of truth”, i.e. where the real data is. This is because getInitialState is only invoked when the component is first created.

Demo

Problems

The constructor or (getInitialState) is called only at the time of component creation. That is, constructor is invoked only once. Hence, when you change the props next time, the state won’t be updated and will retain its previous value.

Young developers often assume that the props values will be in sync with the state, and as props change, the state will reflect those values. However, that is not true.

Solutions

You can use this pattern if you want a specific behaviour. That is, you want the state to be seeded by the values of props only once. The state will be managed internally by the component.

In other cases, you can use componentWillReceiveProps lifecycle method to keep the state and props in sync, as shown here.

Beware that using componentWillReceiveProps has it own caveats. You can read about it the Docs.

The best approach would be to use a state management library such as Redux to connect the state and the component.

Read More —

5. Components Name

In React, if you are rendering your component using JSX, the name of that component has to begin with a capital letter.

Demo

<MyComponent>
<app /> // Will not work :(
</MyComponent>
<MyComponent>
<App /> // Will work!
</MyComponent>

Problems

If you create a component app and render it using JSX as <app label="Save" />, React will throw an error.

Warning when using non-capitalised custom components.

The error says that <app> is not recognised. Only HTML elements and SVG tags can begin with a lowercase. Hence, <div /> is okay but <app> is not.

Solution

You need to make sure that while using custom component in JSX, it should begin with a capital letter.

But, also understand that declaring components does not adhere to this rule. Hence, you can do this —

// Here lowercase is fine.
class primaryButton extends Component {
render() {
return <div />;
}
}
export default primaryButton;// In a different file, import the button. However, make sure to give a name starting with capital letter.import PrimaryButton from 'primaryButton';<PrimaryButton />

Read More —

These were some unintuitive hard-to-understand bug-makers in React. If you know about any other anti-pattern, respond to this article. 😀

I write about JavaScript, web development, and Computer Science. Follow me for weekly articles. Share this article if you like it.

Reach out to me on @ Facebook @ Linkedin @ Twitter.

✉️ Subscribe to CodeBurst’s once-weekly Email Blast, 🐦 Follow CodeBurst on Twitter, view 🗺️ The 2018 Web Developer Roadmap, and 🕸️ Learn Full Stack Web Development.

--

--