How to use HTML Canvas with React Hooks

masakudamatsu

Masa Kudamatsu

Posted on December 9, 2020

How to use HTML Canvas with React Hooks

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:

// 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;
Enter fullscreen mode Exit fullscreen mode

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):

// 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;
Enter fullscreen mode Exit fullscreen mode

Demo for the above code is available at my CodeSandbox.

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).

Step 1: Render a canvas element

// src/components/Canvas.js

import React from 'react';

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

export default Canvas;
Enter fullscreen mode Exit fullscreen mode

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). 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). Corey's (2019) 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) 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 for why).

Create a variable pointing to useRef(), and then use this variable as the value of the ref attribute of the canvas element:

// 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;
Enter fullscreen mode Exit fullscreen mode

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) 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:

// 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;
Enter fullscreen mode Exit fullscreen mode

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 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), Nanda 2020 and van Gilst (2019).

Step 4: Draw an image

Now we're ready to draw an image with various methods of the context object (see MDN Contributors 2020).

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):

// 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;
Enter fullscreen mode Exit fullscreen mode

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).

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:

// 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;
Enter fullscreen mode Exit fullscreen mode

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:

// 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;
Enter fullscreen mode Exit fullscreen mode

Demo

Check out how it actually works with my CodeSandbox demo.

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, 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
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 in this article, to refer to various articles on web development.

References

Corey (2019) “Animating a Canvas with React Hooks”, petecorey.com, Aug. 19, 2019.

Farmer, Andrew H. (2018) “Why to use refs instead of IDs”, JavaScript Stuff, Jan 27, 2018.

MDN Contributors (2019a) “Basic usage of canvas”, MDN Web Docs, Dec 5, 2019.

MDN Contributors (2019b) “Optimizing canvas”, MDN Web Docs, Apr 7, 2019.

MDN Contributors (2019c) “Canvas tutorial”, MDN Web Docs, Dec 1, 2019.

MDN Contributors (2020) “Drawing shapes with canvas”, MDN Web Docs, Aug 5, 2020.

Nanda, Souradeep (2020) “An answer to ‘Rendering / Returning HTML5 Canvas in ReactJS’”, Stack Overflow, Aug 2, 2020.

React (2020a) "Hooks API Reference", React Docs, Mar 9, 2020.

React (2020b) “Typechecking with PropTypes”, React Docs, Nov 25, 2020.

van Gilst (2019) “Using React Hooks with canvas”, blog.koenvangilst.nl, Mar 16, 2019.

West, Donavon (2019) "React Hook Flow Diagram", GitHub, Mar 12, 2019.

💖 💪 🙅 🚩
masakudamatsu
Masa Kudamatsu

Posted on December 9, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related