...

/

Utilising ImmutableJS for top performance

Utilising ImmutableJS for top performance

Let's roll up our sleeves and apply our learnings from ImmutableJS to our React/Redux weather app.

We'll cover the following...

As a short experiment, try putting a console.log('RENDER PLOT') into the render method of the Plot component:

class Plot extends React.Component {
	/* … */
	render() {
		console.log('RENDER PLOT');
    return (
      <div id="plot" ref="plot"></div>
    );
  }
}

Now try using the app for a bit, clicking around, request data for different cities. What you might notice is that the Plot rerenders even if we only change the location field and the plot itself stays the exact same! (Look at your browser console).

import React from 'react';
import './App.css';
import { connect } from 'react-redux';

import Plot from './Plot';
import {
  changeLocation,
  setData,
  setDates,
  setTemps,
  setSelectedDate,
  setSelectedTemp,
  fetchData
} from './actions';

class App extends React.Component {
  fetchData = (evt) => {
    evt.preventDefault();

    var location = encodeURIComponent(this.props.location);

    var urlPrefix = 'https://api.openweathermap.org/data/2.5/forecast?q=';
    var urlSuffix = '&APPID=dbe69e56e7ee5f981d76c3e77bbb45c0&units=metric';
    var url = urlPrefix + location + urlSuffix;
    
    this.props.dispatch(fetchData(url));
    
  };

  onPlotClick = (data) => {
    if (data.points) {
      var number = data.points[0].pointNumber;
      this.props.dispatch(setSelectedDate(this.props.dates[number]));
      this.props.dispatch(setSelectedTemp(this.props.temps[number]))
    }
  };

  changeLocation = (evt) => {
    this.props.dispatch(changeLocation(evt.target.value));
  };

  render() {
    var currentTemp = 'not loaded yet';
    if (this.props.data.list) {
      currentTemp = this.props.data.list[0].main.temp;
    }
    return (
      <div>
        <h1>Weather</h1>
        <form onSubmit={this.fetchData}>
          <label>City, Country
            <input
              placeholder={"City, Country"}
              type="text"
              value={this.props.location}
              onChange={this.changeLocation}
            />
          </label>
        </form>
        {/*
          Render the current temperature and the forecast if we have data
          otherwise return null
        */}
        {(this.props.data.list) ? (
          <div>
            {/* Render the current temperature if no specific date is selected */}
            {(this.props.selected.temp) ? (
              <p>The temperature on { this.props.selected.date } will be { this.props.selected.temp }°C</p>
            ) : (
              <p>The current temperature is { currentTemp }°C!</p>
            )}
            <h2>Forecast</h2>
            <Plot
              xData={this.props.dates}
              yData={this.props.temps}
              onPlotClick={this.onPlotClick}
              type="scatter"
            />
          </div>
        ) : null}

      </div>
    );
  }
}

// Since we want to have the entire state anyway, we can simply return it as is!
function mapStateToProps(state) {
  return state.toJS();
}

export default connect(mapStateToProps)(App);

This is a react feature, react rerenders your entire app whenever something changes. This doesn’t necessarily have a massive performance impact on our current application, but it’ll definitely bite you in a production application! So, what can we do against that?

shouldComponentUpdate

React provides us with a nice lifecycle method called shouldComponentUpdate which allows us to regulate when our components should rerender. As an example, try putting this into your Plot:

class Plot extends React.Component {
	shouldComponentUpdate(nextProps) {
		return false;
	}
	/* … */
}

Now try loading some data and rendering a plot. What you see is that the plot never renders. This is because we’re basically telling react above that no matter what data comes into our component, it should never render the Plot! On the other hand, if we return true from there we’d have the default behaviour back, i.e. rerender whenever new data comes in.

import React from 'react';

class Plot extends React.Component {
  shouldComponentUpdate(nextProps) {
		return false;
	}
  
  drawPlot = () => {
    Plotly.newPlot('plot', [{
      x: this.props.xData,
      y: this.props.yData,
      type: this.props.type
    }], {
      margin: {
        t: 0, r: 0, l: 30
      },
      xaxis: {
        gridcolor: 'transparent'
      }
    }, {
      displayModeBar: false
    });
    
    document.getElementById('plot').on(
      'plotly_click', this.props.onPlotClick);
  }
  
  componentDidMount() {
    this.drawPlot();
  }

  componentDidUpdate() {
    this.drawPlot();
  }

  render() {
    console.log('RENDER PLOT');
    return (
      <div id="plot"></div>
    );
  }
}


export default Plot;

As I’ve hinted with the variable above, shouldComponentUpdate gets passed ...