Search code examples
javascriptreactjscanvaslifecycle

What is correct lifecycle method in React 16.3 to update canvas from props?


I have a Canvas component, which looks approximately like this:

class Canvas extends React.Component{

    saveRef = node => {
        this._canvas = node;
    }
    
    shouldComponentUpdate(){
        /*I will never re-render this component*/
        return false;
    }
    
    componentWillReceiveProps( nextProps ){
        /*Here I do manipulations with this._ctx, when new props come*/
    }
    
    render(){
        return (
            <canvas ref={this.saveRef} />
        );
    }
    
    componentDidMount(){
        this._ctx = this._canvas.getContext( "2d" );
    }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

React community began to deprecate componentWillReceiveProps in order to replace it with getDerivedStateFromProps. I can use componentDidUpdate to perform my drawings, but then I need to remove shouldComponentUpdate and I will have a lot of useless render calls. What is the correct performant way to update my component in react 16.3, when new props come?


Solution

  • Use componentDidUpdate for DOM manipulations like this. A shouldComponentUpdate won’t really make a difference for a component with a single child that always has the same props. So you should be able to remove it without a significant difference in performance.

    If you've profiled the application and determined that in this particular case it does make a difference, you can hoist the element into constructor.

    This way React will skip over it completely (which effectively works the same way as shouldComponentUpdate):

    class Canvas extends React.Component {
      constructor(props) {
        super(props);
        this._ctx = null;
        this._child = <canvas ref={node => {
          this._ctx = node ? node.getContext('2d') : null
        } />;
      }
    
      componentDidUpdate(prevProps){
        // Manipulate this._ctx here
      }
    
      render() {
        // A constant element tells React to never re-render
        return this._child;
      }
    }
    

    You could also split it into two components:

    class Canvas extends React.Component {
      saveContext = ctx => {
        this._ctx = ctx;
      }
    
      componentDidUpdate(prevProps){
        // Manipulate this._ctx here
      }
    
      render() {
        return <PureCanvas contextRef={this.saveContext} />;
      }
    }
    
    
    class PureCanvas extends React.Component {
      shouldComponentUpdate() {
        return false;
      }
    
      render() {
        return (
          <canvas
            ref={node => node ? this.props.contextRef(node.getContext('2d') : null)}
          />;
      }
    }