Adrian Seidel
Adrian Seidel

Reputation: 23

React canvas TypeError: Cannot read property 'getContext' of null after re-render

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

Answers (2)

SebMaz
SebMaz

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

rsmeral
rsmeral

Reputation: 576

As per Callback Refs:

React will call the ref callback with the DOM element when the component mounts, and call it with null 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 with null 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.

Solution

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

Related Questions