DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 970,177 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Brian Neville-O'Neill
Brian Neville-O'Neill

Posted on • Originally published at blog.logrocket.com on

Returning null from setState in React 16

Overview

React 16 lets you decide whether state gets updated via .setState to prevent unnecessary DOM updates. Calling .setState with null no longer triggers an update in React 16.

We’ll explore how this works by refactoring a mocktail selection app that updates even if we choose the same mocktail twice.

Our mocktail selection app.

The folder structure might look like this:

**src**  
 |-> App.js
 |-> Mocktail.js
 |-> index.js
 |-> index.css
 |-> Spinner.js

How our application works

Our application will render a selected mocktail. We can select/switch the mocktail by clicking on one of the buttons. When we do that, a new mocktail is loaded and a new mocktail image is rendered after loading is complete.

The App componentβ€Šβ€”β€Šthe parent component hereβ€Šβ€”β€Šhas a mocktail state and an updateMocktail method that handles updating the mocktail.

import React, { Component } from 'react';

import Mocktail from './Mocktail';

class App extends Component {

  state = {
    mocktail: ''
  }

  updateMocktail = mocktail => this.setState({ mocktail })

  render() {

    const mocktails = ['Cosmopolitan', 'Mojito', 'Blue Lagoon'];

    return (
      <React.Fragment>
        <header>
          <h1>Select Your Mocktail</h1>
          <nav>
            {
              mocktails.map((mocktail) => {
                return <button 
                  key={mocktail}
                  value={mocktail}
                  type="button"
                  onClick={e => this.updateMocktail(e.target.value)}>{mocktail}</button>
              })
            }
          </nav>
        </header>
        <main>
            <Mocktail mocktail={this.state.mocktail} />
        </main>
      </React.Fragment>
    );
  }
}

export default App;

The updateMocktail method is called on the button element’s onClick event, and the mocktail state is being passed down to the child component Mocktail.

The Mocktail component has a loading state called isLoading that, when true, renders the Spinner component.

import React, { Component } from 'react';

import Spinner from './Spinner';

class Mocktail extends Component {

    state = {
        isLoading: false
    }

    componentWillReceiveProps() {
        this.setState({ isLoading: true });
        setTimeout(() => 
            this.setState({
                isLoading: false
            }), 500);
    }

    render() {

        if (this.state.isLoading) {
            return <Spinner/>
        }

        return (
            <React.Fragment>
                <div className="mocktail-image">
                    <img src={`img/${this.props.mocktail.replace(/ +/g, "").toLowerCase()}.png`} alt={this.props.mocktail} />
                </div>
            </React.Fragment>
        );
    }
}

export default Mocktail;

setTimeout is called in the Mocktail component’s componentWillReceiveProps lifecycle method to set the loading state to true for 500 milliseconds.

This displays the loading spinner for half a second each time the Mocktail component’s props get updated with the new mocktail state, then it renders the mocktail image.

The problem

Now, the problem with this is that the mocktail state gets updated and triggers a re-render of the Mocktail component no matter whatβ€Šβ€”β€Ševen if the state doesn’t actually change.

For example, each time I click the Mojito button, we see the app unnecessarily re-render the Mojito image. React 16 provides state performance improvements that enable us to prevent an update from being triggered by returning null in setState if the state’s new value is the same as its existing value.

The solution

Here are the steps we will follow to prevent unnecessary re-render:

  1. Check whether the new value of the state is the same as the existing value
  2. If the values are the same, we will return null
  3. Returning null will not update state and trigger a component re-render

So first, in the updateMocktail method of the App component, we’ll create a constant called newMocktail and assign it the value being passed in for the mocktail.

updateMocktail = mocktail => {  
  const newMocktail = mocktail;    
  this.setState({     
    mocktail  
  })  
}

Since we’re going to be checking and setting state based on a previous state, rather than passing setState and object, we’ll pass it a function that takes the previous state as a parameter. Then we’ll check if the new value of the mocktail state is the same as the existing one.

If the values are the same, setState will return null. Otherwise, if the values are different, setState will return the updated mocktail state, which will trigger a re-render of the Mocktail component with the new state.

updateMocktail = mocktail => {
  const newMocktail = mocktail;  
  this.setState(state => {
    if ( state.mocktail === newMocktail ) {
      return  null;
    } else {
      return { mocktail };
    }  
  })  
}

Now, clicking a button still loads its respective mocktail image. However, if we click the button again for the same mocktail, React does not re-render the Mocktail component; because setState is returning null, there is no state change to trigger an update.

I’ve highlighted the updates in React DevTools in the two gifs below:

Before returning null (left) vs. after returning null (right).

Note: I have used a dark theme here so it is easier to observe the update in React DOM using the React DevTools highlight updates feature.

Conclusion

Now we have covered returning null from setState in React 16. I’ve added the full code for the mocktail selection app in the CodeSandbox below for you to play around with and fork.

Preventing unnecessary state updates and re-renders with null can make our application perform faster, and the whole point of making application perform faster is to improve our app’s user experience.

Users don’t stumble on a product for no reason. How users feel about a product directly reflects their views of the company and its products, so we need to make sure we build an experience around our users’ expectations in a way that feels natural and intuitive.

I hope you’ve found this post informative and helpful. I would love to hear your feedback!

Thank you for reading!


Plug: LogRocket, a DVR for web apps

https://logrocket.com/signup/

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

Try it for free.


The post Returning null from setState in React 16 appeared first on LogRocket Blog.

Top comments (0)

In defense of the modern web

I expect I'll annoy everyone with this post: the anti-JavaScript crusaders, justly aghast at how much of the stuff we slather onto modern websites; the people arguing the web is a broken platform for interactive applications anyway and we should start over;

React users; the old guard with their artisanal JS and hand authored HTML; and Tom MacWright, someone I've admired from afar since I first became aware of his work on Mapbox many years ago. But I guess that's the price of having opinions.