简体   繁体   中英

Updating a component including a canvas with React

So I'm trying to modify the state of a component which contains a canvas element. The canvas itself should not update since the state affected does not affect the rendering of the canvas ?

import React from 'react';

export default class App extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            isPlaying: false
        }
    }

    handleClick() {
        this.setState({isPlaying: true});
    }

    componentDidMount() {
        this.ctx.fillRect(50,50, 100, 100);
    }

    render() {
        return(
            <div id='container'>
                <canvas width={900} height={500}
                    ref={r => this.ctx = r.getContext('2d')}
                    onClick={() => this.handleClick()} />
            </div>
        );
    }
}

Yet an error shows up when I trigger the event onClick of the canvas :

Uncaught TypeError: Cannot read property 'getContext' of null
at ref (App.js:52)

React component will re-render itself on any of its state property change. If you want control on this behavior, consider overriding shouldComponentUpdate method. If you return false from this method for any state condition, your component will not re-render for that condition.

Now, regarding the error, you should move the arrow function definition of ref into a function reference.

The reason is, arrow function will always be passed as new instance while re-rendering, while the function reference will be passed only once during first time render.

Read more from here to know about this in more detail.

Your implementation should be as follows:

import React from "react";

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isPlaying: false
    };
    this.setContext = this.setContext.bind(this);
  }

  setContext(r) {
    console.log(r);
    this.ctx = r.getContext("2d");
  }

  handleClick(e) {
    this.setState({ isPlaying: true });

  }

  componentDidMount() {
    this.ctx.fillRect(50, 50, 100, 100);
  }

  render() {
    return (
      <div id="container">
        <canvas
          width={900}
          height={500}
          ref={this.setContext}
          onClick={() => this.handleClick()} />
        />
      </div>
    );
  }
}

React docs Refs Caveats explain why you get the getContext() of null .

If the ref callback is defined as an inline function, it will get called twice during updates, first with null and then again with the DOM element. This is because a new instance of the function is created with each render, so React needs to clear the old ref and set up the new one. You can avoid this by defining the ref callback as a bound method on the class, but note that it shouldn't matter in most cases. In your case it matters since you're calling ctx.getContext("2d") .

For getting rid of unnecessary renders of canvas , as already mentioned from other answers, encapsulate isPlaying in a React.PureComponent and communicate changes through an onChange prop.

import * as React from 'react';

export class Canvas extends React.PureComponent {
  state = {
    isPlaying: false
  }

  handleClick(e) {
    const isPlaying = !this.state.isPlaying;
    this.setState({isPlaying});
    this.props.onChange && this.props.onChange(isPlaying)
  }

  setRef = (ctx) => {
    this.ctx = ctx.getContext("2d");
  }

  componentDidMount() {
    this.ctx.fillRect(50, 50, 100, 100);
  }

  render() {
    return (
     <canvas
        width={900}
        height={500}
        ref={this.setRef}
        onClick={() => this.handleClick()}
      />
    );
  }
}

Move the Canvas to it's own class based component then use shouldComponentUpdate as mentioned above, if you're rendering Canvas directly in the render method of this component it will re-render every time something is changed, but since you need the state to be updated you can not specify which elements need to be re-rendered unless these elements are own components and have should component update. You can then pass callback functions to get ref and onClick methods.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM