Reputation: 23
I have a canvas, which uses the canvas.getContext( "2d" ) in ref. my problem is that when the page re-renders, bc a state changes, I get an error and idk how to fix it.
this is a minimal code example where the error appears:
import React, { useState } from "react";
import { render } from "react-dom";
const Test: React.FC<{}> = () => {
const [ counter, setCounter ] = useState<number>( 0 );
return (
<div>
<button onClick={ () => { setCounter( counter + 1 ) } }>Test</button>
<canvas ref={ canvas => {
let context = canvas.getContext( "2d" );
} }/>
</div>
);
}
render( <Test />, document.getElementById( "root" ) );
and this is the error:
canvas_test.component.tsx:11 Uncaught TypeError: Cannot read property 'getContext' of null
at ref (canvas_test.component.tsx:11)
at commitDetachRef (react-dom.development.js:20893)
at commitMutationEffects (react-dom.development.js:23348)
at HTMLUnknownElement.callCallback (react-dom.development.js:3945)
at Object.invokeGuardedCallbackDev (react-dom.development.js:3994)
at invokeGuardedCallback (react-dom.development.js:4056)
at commitRootImpl (react-dom.development.js:23121)
at unstable_runWithPriority (scheduler.development.js:468)
at runWithPriority$1 (react-dom.development.js:11276)
at commitRoot (react-dom.development.js:22990)
why does this error appear and how can I fix it?
Upvotes: 0
Views: 5675
Reputation: 608
you can init the canvas ref inside useEffect which allows calling functions right after the component did mount or component update. (Right after the canvas element is available in the dom for us)
import React, {useState, useEffect, useRef} from "react";
export default function App() {
const [counter, setCounter] = useState(0);
const canvasRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
const context = canvas.getContext("2d");
}, []);
return (
<div>
<button
onClick={() => {
setCounter(counter + 1);
}}
>
Test
</button>
<canvas ref={canvasRef} />
</div>
);
}
Upvotes: 2
Reputation: 576
As per Callback Refs:
React will call the
ref
callback with the DOM element when the component mounts, and call it withnull
when it unmounts.
And as per Caveats with callback refs
If the
ref
callback is defined as an inline function, it will get called twice during updates, first withnull
and then again with the DOM element.
I believe you are seeing the latter: each call to setCounter
triggers an update, which, as per the above, calls your callback with null
.
In your callback, handle the null
condition. I'd recommend to also extract the callback into its own function. Something like:
const Test: React.FC<{}> = () => {
const [ counter, setCounter ] = useState<number>( 0 );
const refHandler = (canvas) => {
if (!canvas) return;
let context = canvas.getContext( "2d" );
}
return (
<div>
<button onClick={ () => { setCounter( counter + 1 ) } }>Test</button>
<canvas ref={refHandler} />
</div>
);
}
Upvotes: 0