5

I'm creating a speedometer in reactJS and using a little vanilla syntax, however canvas is returning null. If I run

const canvas = document.getElementById('dial__container');

in the console the div pops right up.

Is this merely a timing issue where the canvas hasn't yet rendered or some weird React behavior?

import React from 'react';

const canvas = document.getElementById('dial__container');
const ctx = canvas.getContext('2d');
const radius = canvas.height / 2;

const Speedometer = (props: any) => {

 console.log(canvas); // null
 return (
       <div className="dial__wrapper">
          <canvas id="dial__container" width="150" height="150" />
       </div>
    );
 };

export default Speedometer;
iRohitBhatia
  • 3,808
  • 6
  • 38
  • 88
  • 2
    you are trying to get it before it actually exists. It is like `alert(a); a = 2` – Jonas Wilms Feb 27 '19 at 17:22
  • That's what I trying to fix. Normally I could use .onLoad() but that's not applicable. Is there a way to delay the declaration of the variables until the canvas has rendered? – Austin Callaghan Feb 27 '19 at 17:26
  • @AustinCallaghan One way would be to create it as a state and use `componentDidMount()` lifecycle? – iRohitBhatia Feb 27 '19 at 17:27
  • I threw it in a component did mount and that solved the current issue. I'm just curious if theres a better way now. (PS, Thanks for your help) – Austin Callaghan Feb 27 '19 at 17:30
  • @AustinCallaghan Happy to help. I have edited the title for your preference. But just to share an advice, You should read about state in general (not just state in react). Helps you a lot to figure out what things should be in state and what things shouldn't not. – iRohitBhatia Feb 27 '19 at 17:36

2 Answers2

2

You are trying to get a node that has not been rendered yet.

You could use the componentDidMount lifecycle method by converting your component to a stateful (class component) one before trying to get your canvas. But keep in mind that this is considered really bad practice and should never be necessary :

componentDidMount(){
    this.canvas = document.getElementById('dial__container');
}

Another solution would imply using refs, as linked in the other answer, which are also considered a bad practice.

Treycos
  • 6,780
  • 3
  • 19
  • 36
  • 1
    *you mean by converting your component to a stateful component. You have currently used the word stateless. Functions are stateless, classes are stateful component. – iRohitBhatia Feb 27 '19 at 17:29
  • Woops, edited it – Treycos Feb 27 '19 at 17:30
  • I think using the `ref` attribute is the only option to access context of the canvas and therefore isn't a bad practise. – user3210641 Feb 27 '19 at 17:32
  • 1
    Well, it is the only option to a problem that should never happen, as you should not really need to access an element's context except in extremely rare cases. In those cases, you should use them, but also try not to : https://reactjs.org/docs/refs-and-the-dom.html#dont-overuse-refs – Treycos Feb 27 '19 at 17:34
  • See, that's what I've read is that refs are only for rare cases. If I use refs here there will probably be a lot of them and that seems janky. – Austin Callaghan Feb 27 '19 at 17:43
  • If you want to stick to function components (which seems to be the recommended way), then you can use the 'useEffect' hook. This will be run every time a component is rendered and is the function component equivalent of 'componentDidMount'. https://reactjs.org/docs/hooks-effect.html – Manokaran K Jan 08 '21 at 01:43
0

The problem is, that you're trying to access the element before it appears in the DOM (try to console.log(document.body.innerHTML) right under the React import and check what does the DOM look like at the time of calling the getElemenetById function.

If you need the element's reference you should use ref.

user3210641
  • 1,427
  • 9
  • 13