Alex Fomin
Alex Fomin

Reputation: 193

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?

Upvotes: 17

Views: 3887

Answers (2)

ThaJay
ThaJay

Reputation: 1912

I found this because I had a sort of similar problem but not completely the same. The solution that works for me is just to put all the relevant code in shouldComponentUpdate:

(the if statement used to be in componentWillReceiveProps)

  shouldComponentUpdate (nextProps, nextState) { // no more random renders
    if (
      (nextProps.nightMode !== this.props.nightMode) ||
      (nextProps.language  !== this.props.language)
    ) {
      this.props.setRefresh(true)                       // setTimeout means after current operation
      setTimeout(() => this.props.setRefresh(false), 1) // so loading will show for longer than 1ms
    }

    return this.props.refresh !== nextProps.refresh
  }

Upvotes: 0

Dan Abramov
Dan Abramov

Reputation: 268235

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)}
      />;
  }
}

Upvotes: 19

Related Questions