So I'm trying to modify the state of a ponent 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});
}
ponentDidMount() {
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)
So I'm trying to modify the state of a ponent 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});
}
ponentDidMount() {
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)
Share
Improve this question
asked Dec 10, 2017 at 12:37
Sara DoeSara Doe
1451 gold badge2 silver badges9 bronze badges
0
3 Answers
Reset to default 1React ponent 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 ponent 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 });
}
ponentDidMount() {
this.ctx.fillRect(50, 50, 100, 100);
}
render() {
return (
<div id="container">
<canvas
width={900}
height={500}
ref={this.setContext}
onClick={() => this.handleClick()} />
/>
</div>
);
}
}
Move the Canvas to it's own class based ponent then use shouldComponentUpdate as mentioned above, if you're rendering Canvas directly in the render method of this ponent 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 ponents and have should ponent update. You can then pass callback functions to get ref and onClick methods.
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 municate 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");
}
ponentDidMount() {
this.ctx.fillRect(50, 50, 100, 100);
}
render() {
return (
<canvas
width={900}
height={500}
ref={this.setRef}
onClick={() => this.handleClick()}
/>
);
}
}