React Component to Draw using Hooks and Typescript

React Component to Draw using Hooks and Typescript

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

Recently, How to Create the Drawing Interaction on DEV's Offline Page by Ali Spittel 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. There is also a 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.

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.

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.

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.

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.

 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.

 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 we will use the useEffect hook to add the mousemove event.

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

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

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. Play with the sandbox below.

Sandbox


Let me if you have any questions in the comments, thanks for reading!

typescript reactjs react-native web-development

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

How native is React Native? | React Native vs Native App Development

Article covers: How native is react native?, React Native vs (Ionic, Cordova), Similarities and difference between React Native and Native App Development.

Hire Dedicated React Native Developer

Have you ever thought of having your own app that runs smoothly over multiple platforms? React Native is an open-source cross-platform mobile application framework which is a great option to create mobile apps for both Android and iOS. **[Hire...

Hire Dedicated React Native Developer in India | React Native Development

Hire dedicated React Native developers for your next project. As the top react native development company we offer you the best service as per your business needs.

Which is the best React Native app development company in New York?

Hire top react native app development company in New York to build and develop custom react native mobile apps for Android & iOS with the latest features.

Top React Native Mobile App Development Companies in USA

Looking for top React Native mobile app development company in USA for Startups & Enterprise? Find out the top list of React Native mobile app development company in USA.