React & Performance

Balram Singh

 

React Scotland

Bring Your Own Project

Venue: CR5Lloyds Banking Group, Sighthill, Edinburgh

Date: 12th April 2018

Time: 18:00 to 21:00

For latest updates please join on meetup

https://www.meetup.com/React-Scotland/

Agenda

  • 18:00 - 18:15 Pizza and soft drinks
  • 18:15 - 18:45 Integration Testing of React apps and APIs using Cypress.io - Sugandha Kumari
  • 18:45 - 19:15 ES6 and ES7 features - Vijay Rana
  • 19:15 - 19:45 Creating accessible React apps with React 16  - Balram Singh
  • 19:45 - 20:00 Networking team

React

Re-render everything on every update

Sounds expensive?

Wasted renders

Problem

Re-render everything on every update.

But renders even waste components

Solution

  • ShouldComponentUpdate
  • HOC
  • Use shouldComponentUpdate

  • Make shouldComponentUpdate check fast
  • Make shouldComponentUpdate check easy
  • Initial state

    Proposed state

    Green = node that rerendered

    Ideal update

    Default behaviour

    Orange = waster renderer

    How to get this ideal update

    Return false from shouldComponentUpdate as high up in your application as you can.

    Make shouldComponentUpdate checks fast

    shouldComponentUpdate(nextProps) {
      // expensive!
      return isDeepEqual(this.props, nextProps);
    }
    
    
    const newValue = {
        ...oldValue
        // any modifications you want to do
    };
    
    
    // fast check - only need to check references
    newValue === oldValue; // false
    
    // you can also use the Object.assign syntax if you prefer
    const newValue2 = Object.assign({}, oldValue);
    
    newValue2 === oldValue; // false

    Bad practice

    Good Practice

    Make shouldComponentUpdate checks easy

    Bad practice

    
    shouldComponentUpdate (nextProps) {
        // have any of the items changed?
        if(!isArrayEqual(this.props.items, nextProps.items)){
            return true;
        }
        // everything from here is horrible.
    
        // if interaction has not changed at all then when can return false (yay!)
        if(isObjectEqual(this.props.interaction, nextProps.interaction)){
            return false;
        }
    
        // at this point we know:
        //      1. the items have not changed
        //      2. the interaction has changed
        // we need to find out if the interaction change was relevant for us
    
        const wasItemSelected = this.props.items.any(item => {
            return item.id === this.props.interaction.selectedId
        });
        const isItemSelected = nextProps.items.any(item => {
            return item.id === nextProps.interaction.selectedId
        });
    
        // return true when something has changed
        // return false when nothing has changed
        return wasItemSelected !== isItemSelected;
    }
    // Data structure with good separation of concerns (normalised data)
    const state = {
        items: [
            {
                id: 5,
                description: 'some really cool item'
            }
        ],
    
        // an object to represent the users interaction with the system
        interaction: {
            selectedId: 5
        }
    };

    Good Practice

    Use Higher Order Components

    • A higher-order component (HOC) is an advanced technique in React for reusing component logic.
    • HOCs are not part of the React API, per se. They are a pattern that emerges from React's compositional nature.
    • Concretely, a higher-order component is a function that takes a component and returns a new component
    • Use HOCs like Recompose and connect

     

     

     

    • Use Normalised state

    • Use lot of Connected components
    
    export class TestContainer extends Component {
      constructor(props) {
        super(props);
      }
    
      componentDidMount() {
        this.props.callApi();
      }
    
      render() {
        return (
          <div>{this.props.data}</div>
        );
      }
    }
    
    
    const mapStateToProps = state => ({
      data: state.data
    });
    
    const mapDispatchToProps = dispatch => ({
      callApi: () => dispatch(callApi())
    });
    
    
    export default connect(mapStateToProps, mapDispatchToProps)(TestContainer);
    

    Optimizing React Builds

    • Bundling

    • Minification

    • AOT (Ahead of Time compilation)

    • Tree Shaking (removing unused code)

    • Code Splitting (loading chunk of code on demand) 

    Where should I Code Split

    • When part of application is highly unlikely to be used

    • Component is heavy and can be used in multiple apps

    Caution

    Code splitting is cool, but don’t get carried away. If your entire application is a bunch of lazy components, you are a) defeating the purpose of the bundler, b)prematurely optimizing and c) making your code legitimately harder to read and debug.

    class Heroes extends Component {
      constructor(props) {
        super(props);
        this.state = { 
          ...
          lazyEditHero: null 
        }
      }
      async LoadEditForm() {
        const { default : EditHero } = await import('./EditHero');
        this.setState({ lazyEditHero: EditHero })
      }
      handleSelect = async hero => {
        await this.LoadEditForm();
        this.setState({ selectedHero: hero });
      }
      
      handleEnableAddMode = async () => {
        await this.LoadEditForm();
      this.setState({
          addingHero: true,
          selectedHero: { id: '', name: '', saying: '' }
        });
      }
      ...
      render() { 
        const EditHero = this.state.lazyEditHero;
        return (
          <div>
            ...
            <div className="editarea">
              <button onClick={this.handleEnableAddMode}>Add</button>
              {EditHero ? (
                <EditHero
                  addingHero={this.state.addingHero}
                  onChange={this.handleOnChange}
                  selectedHero={this.state.selectedHero}
                  onSave={this.handleSave}
                  onCancel={this.handleCancel}
                />
              ) : (
                <div></div>
              )}
            </div>
          </div>
        );
      }
    }

    ReactJS Anti Patterns

    • Pure renderer Functions

    • Pure renderer Immutability

    Pure renderer Functions

    •  
    // BAD
    
    export default (props, context) => {
        // ... do expensive compute on props ...
    
        return <SomeComponent {...props} />
    }
    // GOOD
    
    import { pure } from 'recompose';
    
    // See: https://github.com/acdlite/recompose#composition
    
    // This won't be called when the props DONT change
    export default pure((props, context) => {
        // ... do expensive compute on props ...
    
        return <SomeComponent someProp={props.someProp} />
    })

    Pure renderer Immutability

     

    In order to preserve performance one needs to consider the creation of new entities in the render method.

    Array methods such as slice, filter, map and reduce are also good example of immutability

    Use spread operator (Do not use push() for array)

    Spread operator for Objects

    Spread operator for Objects

    Best Practise 

    // NEVER do this
    render() {
      return <MyInput onChange={this.props.update.bind(this)} />;
    }
    // NEVER do this
    render() {
      return <MyInput onChange={() => this.props.update()} />;
    }
    // Instead do this
    onChange() {
        this.props.doUpdate()
    }
    render() {
      return <MyInput onChange={this.onChange}/>;
    }
    // NEVER do this, if there are no items, SubComponent will render every time!
    render() {
        return <SubComponent items={this.props.items || []}/>
    }
    // This will avoid re-rendering
    const EMPTY_ARRAY = []
    render() {
        return <SubComponent items={this.props.items || EMPTY_ARRAY}/>
    }

    Virtualize Long Lists

    • React Virtualized
    • Ag-grid
    •  

    How to measure Performace

    • React Perf

    • React Opt

    • Profiling Components with the Chrome Performance Tab

    References

    • React Anti Pattern https://github.com/nfour/js-structures/blob/master/guides/react-anti-patterns.md
    • Debugging React Performance with React 16 and Chrome dev tools https://building.calibreapp.com/debugging-react-performance-with-react-16-and-chrome-devtools-c90698a522ad
    • ReactCasre#9 - Immutability in Java Script https://www.youtube.com/watch?v=4LzcQyZ9JOU
    • React.js Conf 2015 - Making your app fast with high-performance components https://www.youtube.com/watch?v=KYzlpRvWZ6c
    • Impress Your Friends With Code Splitting in React https://hackernoon.com/impress-your-friends-with-code-splitting-in-react-9f9a3ca2ae6e
    • React performance tips https://medium.com/@joomiguelcunha/react-performance-tips-5fa199a450b2
    • React/Redux Performance Tuning Tips https://medium.com/@arikmaor/react-redux-performance-tuning-tips-cef1a6c50759
    • Performance optimizations for React applications: Round 2 https://medium.com/@alexandereardon/performance-optimisations-for-react-applications-round-2-2042e5c9af97
    • Server Rendering, Code Splitting, and Lazy Loading with React Router v4 https://medium.com/airbnb-engineering/server-rendering-code-splitting-and-lazy-loading-with-react-router-v4-bfe596a6af70
    • React performance optimisation tool https://github.com/reactopt/reactopt
    • React.js pure render performance anti-pattern https://medium.com/@esamatti/react-js-pure-render-performance-anti-pattern-fb88c101332f
    • Analyzing Critical Rendering Path Performance    https://developers.google.com/web/fundamentals/performance/critical-rendering-path/analyzing-crp?hl=en
    • Why did you update https://github.com/maicki/why-did-you-update
    • High Performance React: 3 New Tools to Speed Up Your Apps https://medium.freecodecamp.org/make-react-fast-again-tools-and-techniques-for-speeding-up-your-react-app-7ad39d3c1b82

     

     

     

    React & Performance

    By Balram Singh

    React & Performance

    If you care about performance, it's fairly easy to make any React application super fast. Here is how.

    • 1,784