How to use HTML Canvas with React Hooks
Tags: react, canvas
Published: 2020-12-09 02:49:15

I'm making a color picker web app with React. Drawing a raster image like the color picker on the web requires a `<canvas>` HTML element. But the HTML canvas and React do not easily go hand in hand. 

I've found a bunch of web articles on the topic, most of which are outdated as they use React class components. Those with React hooks are helpful but not fully accurate. So it took quite a while for me to make it work in my web dev project.

To help you (and my future self) save time to set up a canvas element in React app, let me share the definitive version of how to use the HTML canvas with React hooks (with a link to my demo).

## TL;DR
First, create a React component out of the `<canvas>` element: 
```javascript
// src/components/Canvas.js

import React from 'react';
import PropTypes from 'prop-types';

const Canvas = ({draw, height, width}) => {
  const canvas = React.useRef();

  React.useEffect(() => {
    const context = canvas.current.getContext('2d');
    draw(context);
  });

  return (
    <canvas ref={canvas} height={height} width={width} />
  );
};

Canvas.propTypes = {
  draw: PropTypes.func.isRequired,
  height: PropTypes.number.isRequired,
  width: PropTypes.number.isRequired,
};

export default Canvas;
```
Then, use this component with the `props` referring to the function to draw an image (`draw`) and to the image resolution and aspect ratio (`width` and `height`):
```javascript
// src/App.js

import Canvas from './components/Canvas';

const draw = context => {
  // Insert your canvas API code to draw an image
};

function App() {
  return (
    <Canvas draw={draw} height={100} width={100} />
  );
}

export default App;
```
Demo for the above code is available at [my CodeSandbox](https://codesandbox.io/s/infallible-meitner-d9f6h?file=/src/components/Canvas.js).

Below I break down the above code into 6 steps, to help you understand what is going on. ;-)

NOTE: To learn how to draw an image with the canvas element, I recommend MDN's tutorial ([MDN Contributors 2019](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial)).

## Step 1: Render a canvas element
```js
// src/components/Canvas.js

import React from 'react';

const Canvas = () => {
  return (
    <canvas
      width="100"
      height="100"
    />
  )
};

export default Canvas;
```
The `width` and `height` attributes determine two things about the image created by the canvas element: the image resolution and the aspect ratio. 

### Image resolution
In the above example, the image has 100 x 100 pixels. In this case, drawing a line thinner than 1/100 of the image width ends up in sub-pixel rendering, which should be avoided for the performance reason (see [MDN Contributors 2019b](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas#Avoid_floating-point_coordinates_and_use_integers_instead)). If the thinnest line is, say, 1/200 of the image width, then you should set `width="200"`.

### Aspect ratio
The above example also defines the aspect ratio of the image as 1 to 1 (i.e. a square). If we fail to specify the `width` and `height` attributes (as so many articles on HTML canvas do), the default aspect ratio of 2:1 (300px wide and 150px high) will apply. This can cause a stretched image, depending on how you style it with CSS (see [MDN Contributors 2019a](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_usage#The_%3Ccanvas%3E_element)). [Corey's (2019)](http://www.petecorey.com/blog/2019/08/19/animating-a-canvas-with-react-hooks/) helpful article on how to use React hooks to render a canvas element  appears to fall this trap by failing to specify `width` and `height` attributes.

Up until now, it has nothing to do with React. Anytime you use the HTML canvas, you should set `width` and `height` attributes.

## Step 2: Refer to the canvas element
To draw an image with a `<canvas>` element, you first need to refer to it in JavaScript. An introductory tutorial to the HTML canvas (e.g. [MDN Contributors 2019a](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_usage#The_rendering_context)) tells you to use `document.getElementById(id)` where `id` is the `id` attribute value of the canvas element. 

In React, however, using the `useRef` hook is the way to go (see [Farmer 2018](https://www.javascriptstuff.com/use-refs-not-ids/) for why).

Create a variable pointing to `useRef()`, and then use this variable as the value of the `ref` attribute of the canvas element:
```js
// src/components/Canvas.js

import React from 'react';

const Canvas = () => {
  const canvas = React.useRef(); // ADDED

  return (
    <canvas
      ref={canvas} // ADDED
      width="100"
      height="100"
    />
  )
}

export default Canvas;
```
 This way, once the canvas element is rendered on the screen, we can refer to it as `canvas.current` in our JavaScript code. See [React (2020a)](https://reactjs.org/docs/hooks-reference.html#useref) for more detail.

## Step 3: Create the canvas context
To draw an image in the canvas element, you then need to create the `CanvasRenderingContext2D` object (often assigned a variable name like `context` or `ctx` in the code). 

This step is the trickiest part of using the HTML canvas with React. The solution is the `useEffect` hook:

```js
// src/components/Canvas.js

import React from 'react';

const Canvas = () => {
  const canvas = React.useRef();
  
  // ADDED
  React.useEffect(() => {                             
    const context = canvas.current.getContext('2d'); 
  });

  return (
    <canvas
      ref={canvas}
      width="100"
      height="100"
    />
  )
}

export default Canvas;
```
As explained in the previous step, the `canvas.current` refers to the `<canvas>` element in the above code. But it's `null` until React actually renders the canvas element on the screen. To run a set of code after React renders a component, we need to enclose it with the `useEffect` hook (see [West 2019](https://github.com/donavon/hook-flow) for when the `useEffect` code block runs during the React component life cycle). 

Within its code block, therefore, the `canvas.current` does refer to the `<canvas>` element. This is the technique I've learned from [Corey (2019)](http://www.petecorey.com/blog/2019/08/19/animating-a-canvas-with-react-hooks/), [Nanda 2020](https://stackoverflow.com/a/63212430/11847654) and [van Gilst (2019)](https://blog.koenvangilst.nl/react-hooks-with-canvas/).

## Step 4: Draw an image
Now we're ready to draw an image with various methods of the `context` object (see [MDN Contributors 2020](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes)).

To reuse the code that we have written so far, however, it's best to separate it from the code for drawing an image. So we pass a function to draw an image as a prop to the `Canvas` component (I borrow this idea from [Nanda 2020](https://stackoverflow.com/a/63212430/11847654)):
```javascript
// src/components/Canvas.js

import React from 'react';
import PropTypes from 'prop-types'; // ADDED

const Canvas = ( {draw} ) => { // CHANGED
  const canvas = React.useRef();
  
  React.useEffect(() => {                             
    const context = canvas.current.getContext('2d'); 
    draw(context); // ADDED
  });

  return (
    <canvas
      ref={canvas}
      width="100"
      height="100"
    />
  )
};

// ADDED
Canvas.propTypes = {
  draw: PropTypes.func.isRequired,
};

export default Canvas;
```
The `draw()` function draws the image, to be defined in another file. To access to various drawing methods, it takes `context` as its argument.

As the `Canvas` component now takes props, I add `PropTypes` to make explicit the data type of each prop (see [React 2020b](https://reactjs.org/docs/typechecking-with-proptypes.html)).

## Step 5: Make the component reusable
Now if we want to reuse this `Canvas` component, we do not want to hard-code its `width` and `height` attributes. Different images have different resolutions and aspect ratios.

So convert these two values into additional props:
```javascript
// src/components/Canvas.js

import React from 'react';
import PropTypes from 'prop-types';

const Canvas = ( {draw, height, width} ) => { // CHANGED
  const canvas = React.useRef();
  
  React.useEffect(() => {                             
    const context = canvas.current.getContext('2d'); 
    draw(context);
  });

  return (
    <canvas
      ref={canvas}
      width={width}   // CHANGED
      height={height} // CHANGED
    />
  )
}

// ADDED
Canvas.propTypes = {
  draw: PropTypes.func.isRequired,
  height: PropTypes.number.isRequired, // ADDED
  width: PropTypes.number.isRequired, // ADDED
};

export default Canvas;
```
One benefit of using `PropTypes` is that, by adding `.isRequired`, we will be alerted in the console in case we forget setting the prop values. As mentioned above (see Step 1), the `width` and `height` attributes are best specified for performance and for avoiding image distortion. With the above code, we will be alerted when we forget specifying their values.

## Step 6: Render the canvas component
Finally, in a parent component, render the `Canvas` component together with specifying the `draw()` function:
```javascript
// src/App.js

import React from 'react';
import Canvas from './components/Canvas'; // Change the path according to the directory structure of your project

const draw = context => {
  // Insert your code to draw an image
};

function App() {
  return (
    <Canvas draw={draw} height={100} width={100} />
  );
}

export default App;
```

## Demo

Check out how it actually works with [my CodeSandbox demo](https://codesandbox.io/s/how-to-use-html-canvas-with-react-hooks-d9f6h?file=/src/App.js).

Hope this article and the above demo help you kickstart drawing canvas images with React in your web app project!

---

This article is part of [Web Dev Survey from Kyoto](https://masakudamatsu.medium.com/web-dev-survey-from-kyoto-introduction-9a8928972cc0), a series of my blog posts on web development. It intends to simulate that the reader is invited to Kyoto, Japan, to attend a web dev conference. So the article ends with a photo of Kyoto in the current season, as if you were sightseeing after the conference was over. :-)

Today I take you to the entrance garden of Seigen-in, a sub-temple of Ryoan-ji of the rock garden fame:
![A photo of Seigen-in entrance garden in autumn](https://dev-to-uploads.s3.amazonaws.com/i/4t4l0bxsho16wvgmi64a.jpg)
*Seigen-ji Sub-temple Entrance Garden at 9:54 am on 1 December, 2020. Photographed by Masa Kudamatsu (the author of this article)*

---

Hope you have learned something today! Happy coding!

## Footnote
I use the [Author-Date referencing system](https://www.hup.harvard.edu/resources/authors/pdf/hup-author-guidelines-author-date-citations-and-reference-lists.pdf) in this article, to refer to various articles on web development.

## References

Corey (2019) “[Animating a Canvas with React Hooks](http://www.petecorey.com/blog/2019/08/19/animating-a-canvas-with-react-hooks/)”,  *petecorey.com*, Aug. 19, 2019. 

Farmer, Andrew H. (2018) “[Why to use refs instead of IDs](https://www.javascriptstuff.com/use-refs-not-ids/)”, *JavaScript Stuff*, Jan 27, 2018.

MDN Contributors (2019a) “[Basic usage of canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_usage)”, *MDN Web Docs*, Dec 5, 2019.

MDN Contributors (2019b) “[Optimizing canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas)”, *MDN Web Docs*, Apr 7, 2019.  

MDN Contributors (2019c) “[Canvas tutorial](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial)”, *MDN Web Docs*, Dec 1, 2019.

MDN Contributors (2020) “[Drawing shapes with canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes)”, *MDN Web Docs*, Aug 5, 2020.

Nanda, Souradeep (2020) “[An answer to ‘Rendering / Returning HTML5 Canvas in ReactJS’](https://stackoverflow.com/a/63212430/11847654)”, *Stack Overflow*, Aug 2, 2020.

React (2020a) "[Hooks API Reference](https://reactjs.org/docs/hooks-reference.html)", *React Docs*, Mar 9, 2020.

React (2020b) “[Typechecking with PropTypes](https://reactjs.org/docs/typechecking-with-proptypes.html)”, *React Docs*, Nov 25, 2020.

van Gilst (2019) “[Using React Hooks with canvas](https://blog.koenvangilst.nl/react-hooks-with-canvas/)”, *blog.koenvangilst.nl*, Mar 16, 2019.

West, Donavon (2019) "[React Hook Flow Diagram](https://github.com/donavon/hook-flow)", *GitHub*, Mar 12, 2019.