React Gotchas, Anti-Patterns, and Best Practices

Good Things To Know For React Developers

Steven Li
9 min readOct 1, 2018
The React logo is an atom, but what does it represent? This article does not have the answer to that question, but it does have a lot of other useful tidbits of information for React developers.

When to Bind Component Methods

When a React Component’s method uses this , we need to be careful what this refers to. Namely, we want this to refer to the Component instance itself. This means we have to sometimes manually bind the function to the Component instance, but when?

Bind When Not Using Arrow Functions

If a Component was created using ES6 syntax and the method was defined without using an arrow function syntax, we need to manually bind the method’s this to the Component instance in the constructor method.

For example:

Without line 8, clicking the button would raise a undefined property error since `this` in line 12 would be null.

No Need to Bind When Using ES6 Component Classes with Arrow Functions

If a Component was created using ES6 syntax, but the method was defined using the arrow function syntax, you do not need to manually bind.

Avoid the Anonymous Function Hack if Possible

An alternative to binding is to pass an anonymous function to wrap around the method. This, however has performance issues around re-rendering. For example:

Here instead of manually binding `handleClick`, we wrap it in an anonymous function and pass that instead. Clicking the button would work and log “Invoked from inside an anonymous function!” but this has a performance issue.

Because we are passing anonymous functions, React will always re-render since it receives a new anonymous function as a prop which it is unable to compare to the previous anonymous function (since they are both anonymous). On the other hand, passing in a reference to the method like onClick={this.handleClick} lets React know when nothing has changed so it does not unnecessarily re-render. However, the anonymous function is sometimes unavoidable as when we need to pass in an argument available only in the context: onClick={() => {this.handleClick(argHereOnly)}} .

Adding Debuggers to Implicit Return Arrow Functions

Arrow functions are the de facto way of writing functions in React and Redux, and one variant of arrow functions is the implicit return arrow function, which is when the arrow is followed immediately by the return value wrapped in parenthesis, without a return. For example,

const addTag = tag => ({
type: "ADD_TAG",
tag
});

But note we can’t add a debugger in the return value as that would simply raise an error. So in order to add a debugger, we must first convert it into a regular arrow function, one where a block immediately follows the arrow:

const addTag = tag => {
debugger
return {
type: "ADD_TAG",
tag
};
};

Understanding Component’s Props vs State

When should we use a component’s props vs its state ?

Component Props

A component’s props should be used for data that is passed into the component from the outside, typically from a parent component or the redux store. As such, a component cannot change its props; change only comes from the outside when the component receives new props . In this regard, props can be thought of as a mechanism of communication between components. Typical props include:

  • a parent component’s state data
  • a handler function from a parent component if the function needs that parent component’s context
  • API or payload data from the redux store (via the mapStateToProps function)
  • dispatch functions from redux (via the mapDispatchToProps function)

Component State

A component’s state should be used for data whose changes are managed by the component. Object-oriented programmers can think of the state as the component’s member variables or instance variables. When a component mounts, its state is initiated to a certain value but the component can change this state over time through the setState method. Typical examples of state data include:

  • data which controls the UI of a component (ie. disable/enable button on form, show/hide modal)
  • data which originates from user triggered events (ie. entering input fields for controlled components — more on controlled components later)
  • the passage of time (ie. a clock component)

Minimize Number of Stateful Components

A good rule-of-thumb is we should try to minimize the amount of stateful components and instead rely on passing props into pure functional components whenever possible.

Don’t Rely on setState Being Synchronous

A gotcha with setState() is that it is an asynchronous method, meaning it returns before actually setting the state. As such, it’s advised against relying on the state to have changed immediately after invoking setState . For example:

As we type values into the input field, the console log won’t actually print out the new characters we type.

Because of its asynchronous nature, setState accepts a second argument that is a function that it invokes after the state has been updated. So the above example would work if we rewrote handleFirstNameChange as

handleFirstNameChange = (event) => {
this.setState({firstName: event.currentTarget.value}, () => {
console.log(this.state.firstName);
});
}

No Need to Always Initialize State in Constructor

If the initial state is dependent on props , then initialize the state in the constructor as we need access to the props .

class ProductRating extends Component {
constructor(props) {
super(props)
this.state = {
starsSelected: props.starsSelected || 0
}
}
}

But if the initial state is not dependent on props , we can just initialize by setting it directly.

class ProductRating extends Component {
this.state = {
starsSelected: 0
}
}

We don’t always need to initialize state in the constructor.

Component Lifecycle Gotchas and Deprecations

`Constructor` Gotchas

Much like the constructor method in other object oriented languages, constructor(props) is used to instantiate the component, which is still not mounted at this point. Typical use cases for the constructor is setting the initial state or binding methods to this. Common gotchas in the constructor method are:

  • Make sure super(props) is the first line in the constructor method.
  • Unless you’re setting the initial state or binding methods to this , there’s no need to even have the constructor method.
  • Set the initial state by this.state = { value: "foo" } instead of using setState as that would raise an error. This is because setState will also re-render the component and its children, but this is not applicable in the constructor as the component has been rendered yet.
  • Do not make AJAX calls here since a component may be instantiated but never rendered, for example loggedIn ? <Dashboard /> : <LogIn /> . If AJAX calls are made in the constructor for Dashboard but the user isn’t logged in, this would lead to unnecessary network calls and errors.

`componentWillMount` Has Been Deprecated

componentWillMount() is invoked after the constructor(props) but before render() , meaning invoking setState() in here will not cause an additional render .

This method has been deprecated as of React 16.3, and after version 17, will be removed and replaced with UNSAFE_componentWillMount . The reason is React 17 will introduce async rendering, which componentWillMount may introduce bugs. For example, suppose a subscription was set in componentWillMount and removed in componentWillUnmount . Async rendering may cause the rendering to be interrupted before it completes, meaning componentWillUnmount will not to be called and the subscription won’t be removed. This effectively makes componentWillMount unsafe to use.

The general rule for migrating away from componentWillMount is to move whatever was done in it into componentDidMount .

`componentDidMount` Gotchas

componentDidMount is invoked after the component has mounted and render() is invoked, so this method can be thought of as initialization logic which requires the DOM to be present. Typical use-cases for componentDidMount include making AJAX calls and setting up subscriptions.

A potential anti-pattern is calling setState in this method as it would cause an additional re-render. If you find yourself setting the state in this method, consider doing that in the constructor instead. Setting the initial state pretty much never depends on the DOM being present.

`componentWillReceiveProps` Has Been Deprecated

componentWillReceiveProps(nextProps) is invoked right before a mounted component receives new props . A typical use-case is to check if the nextProps are different from the current props and then set the state or make an AJAX call.

This method has been deprecated as of React 16.3, and after version 17, will be removed and replaced with UNSAFE_componentWillReceiveProps . The reason is that componentWillReceiveProps will be unsafe due to async rendering. The suggestion is to use getDerivedStateFromProps(props, state) for updating the state based on new props . But note that getDerivedStateFromProps is a static method meaning it won’t have access to the component’s this . Instead the state is updated from its return value . This is intentional as it forces us to write pure functional methods without side-effects. For side-effects like making AJAX calls, use componentDidUpdate(prevProps, prevState, snapshot) instead.

Controlled Forms, Uncontrolled Forms, and Redux-Controlled Forms

A Controlled Form is a form whose input values are determined by the Component’s state . An Uncontrolled Form is a form whose input values are managed by the DOM instead. An Redux-controlled Form is a form whose input values are managed by the Redux store.

For example, consider a simple form with input values and default values written as a Controlled Form:

This is a Controlled Component because the values of the inputs are determined by the `state` of the Component.

Now consider this form written as an Uncontrolled Form:

The form input values are not tied to the Component `state`. Instead, we use `refs` to access the input values in the submit handler. Also, instead of using the `state` to handle default values, we use the `defaultValue` form input attribute.

We could have also written this as a Redux-Controlled Form, where the input values are controlled by the redux store.

Here the form input values are connected to the redux store. Why would we do this? To persist partially-filled form data even after the user closes the form.

When To Use Controlled vs. Uncontrolled Form vs Redux-Controlled Forms

Uncontrolled Forms are simpler to write as it doesn’t need the synchronization code between state and input values . However, Controlled Forms support more features such as dynamically validating input data or changing the UI based on input data such as validation errors that appear as the user types, or buttons being disabled depending on selection values.

Redux-Controlled Forms have the advantage of persisting form input data even after the user closes the form. This can enhance UI as when a user accidentally closes a partially-filled form. In this case, because the form input data is persisted in the redux store, the user does not have to re-enter the input fields upon re-opening the form.

The general rule is that simple forms can use Uncontrolled Forms. Forms that require on-the-fly UI changes should go with Controlled Forms. For forms that persist input values even after being closed, use Redux-Controlled Forms.

Optimizing Performance Through Pure Components

The rationale for `shouldComponentUpdate` and Pure Components

By default, a React Component always renders when setState is invoked or when its Parent renders. But if the new state and new props are the same as before, re-rendering is wasteful. This is why components have a method called shouldComponentUpdate which defaults to return true but which could be monkey patched to improve performance by reducing un-necessary renders.

shouldComponentUpdate receives two arguments nextProps and nextState and returns a boolean determining dictating whether or not the Component should re-render. A very reasonable choice for shouldComponentUpdate would be to shallow compare the current state with the next state and the current props and the next props, and only render if they are different. Why only a shallow compare instead of a deep compare? Because a deep compare can be even costlier than re-rendering, which defeats the purpose.

Performing a shallow comparison in shouldComponentUpdate would look something like:

Note: This was taken from the PureComponent source code, which we will discuss immediately below.

But React already provides a type of Component called Pure Components which implements the shouldComponentUpdate method shown above. We use Pure Components by extending them, much like regular Components:

class UserComponent extends React.PureComponent

These Pure Components can improve app performance by reducing un-necessary re-renders but must be used carefully because it only performs shallow comparisons, which is why immutability of state is important.

Immutability

Because Pure Components perform only a shallow comparison we need to work with immutable state , meaning we should never change the existing state, but always create a new copy of the state with updated values.

This is because equality in Javascript depends on the data type. Primitive types like numbers, strings, booleans are considered equal if their values are equal. Reference types like objects and arrays are considered equal if their references are equal, even if those references have changed in value. As such, we SHOULD NOT MUTATE STATE BY DIRECTLY MANIPULATING THE STATE OBJECT LIKE THIS:

By modifying the `state` directly instead of using `setState`, it would not re-render. Furthermore, we are mutating properties are in the `state` which the PureComponent shallow comparison would not detect.

Because user is an object and items is an array, and equality of them is determined referentially, React would consider the user and items unchanged and would not re-render causing hard to find bugs. Instead, we must use the setState and the spread operator.

Using `setState` ensures the Component will attempt a render. The spread operator on the object and array returns a new object and array with the updated values, ensuring `shouldComponentUpdate` will return true since the object and array references are now different.

setState will ensure the Component re-renders while the spread operator returns a new object/array with the updated value(s), instead of modifying the old object/array. As such, when shouldComponentUpdate does the shallow comparison, it will notice the object/array reference has changed and will re-render as we would expect.

Feel free to leave comments, questions, suggestions, corrections below. — S

--

--

Steven Li

Writing About Rails, React, Web Application Technology, Databases, and Software Engineering