React Component to draw on a page using Hooks and Typescript
Tags: typescript, showdev, react
Published: 2019-08-08 04:00:06

---
title: React Component to draw on a page using Hooks and Typescript
published: true
description: Can we recreate the drawing interaction on DEV's offline page as a React Component using Hooks and Typescript? Let's find out.
tags: #typescript #showdev #react
canonical_url: https://www.ankursheel.com/blog/react-component-draw-page-hooks-typescript
---

Recently, [How to Create the Drawing Interaction on DEV's Offline Page](https://dev.to/aspittel/how-to-create-the-drawing-interaction-on-dev-s-offline-page-1mbe) by [Ali Spittel](https://dev.to/aspittel) showed up in my feed and it looked quite cool. This got me wondering if I could create the same thing as a React component using Hooks and typescript. Well, the fact that I am writing this post means I was able to recreate it. So let's see how I did it.

If you are interested in the final product, you can check out the [Github repository](https://github.com/AnkurSheel/react-drawing-interaction). There is also a [sandbox](#sandbox) you can play with at the end of this post.

_This post assumes you already know how to work with TypeScript and hooks._

---

## Creating the Component

The first thing we need to do is to create a __Canvas__ component. The canvas needs to take up some space which we will want any parent component to be able to override so we will add `width` and `height` as props. But, We want to add a sensible default so that We don't have to add these props every time we want to use this component. We will add some _defaultProps_ to set these values to _window.innerWidth_ and _window.innerHeight_ respectively.

```javascript 
import React from 'react';

interface CanvasProps {
	width: number;
	height: number;
}

const Canvas = ({ width, height }: CanvasProps) => {
	 return <canvas height={height} width={width} />;
};

Canvas.defaultProps = {
	width: window.innerWidth,
	height: window.innerHeight,
};

export default Canvas;
```

---

## Lets Draw

Since we need to modify the canvas element, we will need to add a ref to it. We can do this by using `useRef` hook and modifying our _canvas_ element to set the ref.

```javascript
const canvasRef = useRef<HTMLCanvasElement>(null);
return <canvas ref={canvasRef} height={height} width={width} />;
```

### Set state

We need to keep track of some variables
* the mouse position. 
* whether we are painting or not.

We can do this by adding the `useState` hook. 
We will also create a `Coordinate` type to help with keeping track of mouse positions.

```javascript
type Coordinate = {
    x: number;
    y: number;
};

const Canvas = ({ width, height }: CanvasProps) => {
const [isPainting, setIsPainting] = useState(false);
const [mousePosition, setMousePosition] = useState<Coordinate | undefined>(undefined);
// ... other stuff here
```

#### Start drawing when the mouse is pressed.<a name="mousedown"></a>

We will add the event listener in the `useEffect` hook. If we have a valid reference to the canvas, we add an event listener to the _mouseDown_ event. We also remove the event listener when we unmount. 

```javascript
 useEffect(() => {
        if (!canvasRef.current) {
            return;
        }
        const canvas: HTMLCanvasElement = canvasRef.current;
        canvas.addEventListener('mousedown', startPaint);
        return () => {
            canvas.removeEventListener('mousedown', startPaint);
        };
    }, [startPaint]);
```

_startPaint_ needs to get the current coordinates of the mouse and set `isPainting` to true. We will also wrap it in a `useCallback` hook so that we can use it inside the `useEffect` hook.

```javascript
 const startPaint = useCallback((event: MouseEvent) => {
        const coordinates = getCoordinates(event);
        if (coordinates) {
            setIsPainting(true);
            setMousePosition(coordinates);
        }
    }, []);

// ...other stuff here

const getCoordinates = (event: MouseEvent): Coordinate | undefined => {
    if (!canvasRef.current) {
        return;
    }

    const canvas: HTMLCanvasElement = canvasRef.current;
    return {event.pageX - canvas.offsetLeft, event.pageY - canvas.offsetTop};
};
```

#### Draw the line on mouse move
Similar to the [_mouseDown_ event listener](#mousedown) we will use the `useEffect` hook to add the _mousemove_ event.
```javascript
useEffect(() => {
        if (!canvasRef.current) {
            return;
        }
        const canvas: HTMLCanvasElement = canvasRef.current;
        canvas.addEventListener('mousemove', paint);
        return () => {
            canvas.removeEventListener('mousemove', paint);
        };
    }, [paint]);
```
_paint_ needs to 
* Check if we are painting.
* Get the new mouse coordinates.
* Draw a line from the old coordinates to the new one by getting the rendering context from the canvas.
* Update the old coordinates.

```javascript
const paint = useCallback(
        (event: MouseEvent) => {
            if (isPainting) {
                const newMousePosition = getCoordinates(event);
                if (mousePosition && newMousePosition) {
                    drawLine(mousePosition, newMousePosition);
                    setMousePosition(newMousePosition);
                }
            }
        },
        [isPainting, mousePosition]
    );

// ...other stuff here

const drawLine = (originalMousePosition: Coordinate, newMousePosition: Coordinate) => {
        if (!canvasRef.current) {
            return;
        }
        const canvas: HTMLCanvasElement = canvasRef.current;
        const context = canvas.getContext('2d');
        if (context) {
            context.strokeStyle = 'red';
            context.lineJoin = 'round';
            context.lineWidth = 5;

            context.beginPath();
            context.moveTo(originalMousePosition.x, originalMousePosition.y);
            context.lineTo(newMousePosition.x, newMousePosition.y);
            context.closePath();

            context.stroke();
        }
    };
```

#### Stop drawing on mouse release
We want to stop drawing when either the user releases the mouse or they move the mouse out of the canvas area
```javascript
useEffect(() => {
        if (!canvasRef.current) {
            return;
        }
        const canvas: HTMLCanvasElement = canvasRef.current;
        canvas.addEventListener('mouseup', exitPaint);
        canvas.addEventListener('mouseleave', exitPaint);
        return () => {
            canvas.removeEventListener('mouseup', exitPaint);
            canvas.removeEventListener('mouseleave', exitPaint);
        };
    }, [exitPaint]);
```
In _exitPaint_ we just set the `isPainting` to `false`
```javascript
const exitPaint = useCallback(() => {
        setIsPainting(false);
    }, []);
```

---

And, we have a React component that we can reuse. You can see the final code in either the [Github repository](https://github.com/AnkurSheel/react-drawing-interaction). Play with the sandbox below.

---

## Sandbox <a name="sandbox"></a>

{% codesandbox react-drawing-interaction-bo1ph runonclick=1 %}

---

Let me if you have any questions in the comments :)

---

Updates:
 - Updated 24 Sep 2019: Fixed incorrect code in `getCoordinates`. Thanks, Jibin for pointing it out.