# React component to animate planets on a canvas

I wrote some JavaScript to generate some graphics on a canvas, on a regular HTML page.

I now want the same code to run in a component that is part of a React app. I've done this:

  componentDidMount() {
const canvas = this.refs.firstCanvas
const ctx = canvas.getContext('2d')
const bgCanvas = this.refs.firstbgCanvas
const bgCtx = bgCanvas.getContext('2d')

function generateStarfield() {
bgCtx.clearRect(0, 0, bgCanvas.width, bgCanvas.height)
for (let i = 0; i < 2000; i++) {
bgCtx.beginPath()
const x = Math.random()*bgCanvas.width
const y = Math.random()*bgCanvas.height
bgCtx.arc(x, y, 0.35, 0, 2*Math.PI, 'anticlockwise')
bgCtx.closePath()
bgCtx.fillStyle = 'white'
bgCtx.fill()
}
}
generateStarfield()

const origin_x = 1000
const origin_y = 1000
const scale = 1000

class planet {
constructor(orbital_velocity, offset_theta, orbital_radius, radius, colour) {
this.orbital_velocity = orbital_velocity
this.orbital_radius = orbital_radius
this.colour = colour
this.radius = radius
this.offset_theta = offset_theta
this.draw()
}

draw() {
const theta = this.offset_theta + (t*this.orbital_velocity)
const x = origin_x + (this.orbital_radius*Math.cos(theta))
const y = origin_y + (this.orbital_radius*Math.sin(theta))
ctx.beginPath()
ctx.arc(x, y, this.radius, 0, 2*Math.PI, 'anticlockwise')
ctx.closePath()
ctx.fillStyle = this.colour
ctx.fill()
}
}

let t = 0

const a = new planet((0.2+0.4*Math.random())*Math.PI, 2*Math.random()*Math.PI, scale*0.1, 10, 'white')
const b = new planet((0.2+0.4*Math.random())*Math.PI, 2*Math.random()*Math.PI, scale*0.2, 16, 'white')
const c = new planet((0.2+0.4*Math.random())*Math.PI, 2*Math.random()*Math.PI, scale*0.3, 18, 'white')
const d = new planet((0.2+0.4*Math.random())*Math.PI, 2*Math.random()*Math.PI, scale*0.4, 14, 'white')
const e = new planet((0.2+0.4*Math.random())*Math.PI, 2*Math.random()*Math.PI, scale*0.5, 12, 'white')
const f = new planet((0.2+0.4*Math.random())*Math.PI, 2*Math.random()*Math.PI, scale*0.6, 28, 'white')
const g = new planet((0.2+0.4*Math.random())*Math.PI, 2*Math.random()*Math.PI, scale*0.7, 22, 'white')
const h = new planet((0.2+0.4*Math.random())*Math.PI, 2*Math.random()*Math.PI, scale*0.8, 20, 'white')

setInterval(function() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.beginPath()
a.draw()
b.draw()
c.draw()
d.draw()
e.draw()
f.draw()
g.draw()
h.draw()
t += 0.05
}, 40)
}


Now, this works (!), but I've just lifted it directly from the original JavaScript, and modified the document.getElementByID calls to use refs. Is it bad practice to stick all of this stuff into componentDidMount?

Note - I'm aware that the code itself could be tidied up, I'm mainly asking about putting all of this (especially the definitions for planet and generateStarField) into componentDidMount.

## 1 Answer

You can do this, if it works... but it's not really using the React architecture or patterns.

• Most of this code that is drawing should be in the render method.

• You'll want to move some of the state (canvas, ctx, bgCanvas, bgCtx) into the class's state, or just as instance variables.

• You can probably do most of the creation in the constructor, although doing it in componentDidMount will work.

• You can integrate the animation with React, setInterval(() => this.forceUpdate(() => this.t += 0.05)). This makes sense in componentDidMount. You'll also want to turn this off in the unmount method.

## EDIT/ADDED LATER

...maybe you just want to leave it the way it is, although it's quite non-React... it's working fine, though, and there aren't really problems...

To make it "React" you're probably going to have to re-write it completely-- because just moving pieces around like I suggested may make you end up fighting the framework more than leveraging. Looking more, you'd probably make separate components for the different canvases and perhaps pass in t as a property, so there's a container component that functions as the "clock". Doing something like that would allow the regular React rendering mechanisms work.

• I'm a bit confused about the order of this - the canvas, ctx, bgCanvas, and bgCtx can't be defined until after render has completed (as render creates the <canvas /> elements), so how can any drawing be done until the componentDidMount? – Alex Jun 1 '19 at 17:10
• Yeah, there's a bit of a start-up ordering issue, but I think having React.createRef() in the constructor and then you can just check in the render if it's set will work... – ndp Jun 1 '19 at 21:04
• No you should not render animated content via setInterval and worse as canvas context is completely independent of the DOM you should never animate its content via reacts forceUpdate. Should use requestAnimationFrame in componentDidMount and stop it in componentWillUnmount() – Blindman67 Jun 2 '19 at 3:47