Home > Enterprise >  useEffect Running indefinitely
useEffect Running indefinitely

Time:12-29

I am using React with TypeScript, I am trying to draw a rectangle shape on the canvas, the shape is drawn on the canvas but after drawing it again it went into an infinite loop. Even I am passing firstClicks, lastClicks as the second argument in useEffect.

due to running indefinitely, my app keeps crashing after some time. here is my code:

import { useEffect, useRef, useState } from 'react';


interface ICoordinates{
    x: number
    y: number
}


const Canvas = ({height, width}: ICanvas) => {
    const canvasRef = useRef<HTMLCanvasElement>(null);
    const divRef = useRef<HTMLDivElement>(null);
    let [firstClicks, setFirstClicks] = useState<ICoordinates>();
    let [lastClicks, setLastClicks] = useState<ICoordinates>();

    useEffect(() => {
        const canvas = canvasRef.current?.getContext('2d');
        const context = canvasRef.current;
        let mousedown = false;
            function drawRectangle(){
                if(canvas){
                    canvas.beginPath();
                    if(firstClicks && lastClicks){
                        canvas.rect(firstClicks.x, firstClicks.y, lastClicks.x-firstClicks.x, lastClicks.y-firstClicks.y);
                    }
                    canvas.fillStyle = 'rgba(100,100,100,0.5)';
                    canvas.fill();
                    canvas.strokeStyle = "#df4b26";
                    canvas.lineWidth = 1;
                    canvas.stroke();
                }
              };

              function redraw(){ 
                if(context){
                    context.width = context.width;
                }
                drawRectangle();
              };

              if(context){
                  context.addEventListener("mousedown", function (e) {
                    setFirstClicks({
                        x: e.offsetX,
                        y: e.offsetY
                    })
                    mousedown = true;
                  });

                  context.addEventListener("mousemove", function (e) {
                    if (mousedown) {
                        setLastClicks({
                            x: e.offsetX,
                            y: e.offsetY
                        })
                      redraw();
                    }
                  });

                  context.addEventListener("mouseup", function (e) {
                    mousedown = false;
                    setLastClicks({
                        x: e.offsetX,
                        y: e.offsetY
                    })
                  });

                  context.addEventListener("mouseleave", function () {
                    mousedown = false;
                  });
              }
              
    },[firstClicks, lastClicks])
    return (
        <div ref={divRef}>
            <canvas className='canvas' ref={canvasRef}>
            </canvas>
        </div>
    )
}

export default Canvas

CodePudding user response:

Have you tried this without having [firstClicks, lastClicks] and instead, have an empty dependency array: [].

The dependency array means that this useEffect callback will run on the mounting and unmounting of the component and any change to the variables in the dependency array.

I see that you are using setLastClicks and setFirstClicks meaning once the this is executed, the useEffect hook will detect a change in firstClicks or lastClick and run the effect again causing an infinite loop.

You can double check this by adding a console.log just before the setLastClicks and setFirstClicks to see if it is changing the state on every run of the hook's callback.

Here is a working version:

import { useEffect, useRef } from "react";

interface ICoordinates {
  x: number;
  y: number;
}

const Canvas = ({ height, width }: { height: number; width: number }) => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const divRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    console.log("Rerunning");
    const canvas = canvasRef.current?.getContext("2d");
    const context = canvasRef.current;
    let mousedown = false;
    let firstClick: ICoordinates = { x: 0, y: 0 };
    let lastClick: ICoordinates = { x: 0, y: 0 };

    function drawRectangle(
      firstClicks: ICoordinates,
      lastClicks: ICoordinates
    ) {
      if (canvas) {
        canvas.beginPath();
        if (firstClicks && lastClicks) {
          canvas.rect(
            firstClicks.x,
            firstClicks.y,
            lastClicks.x - firstClicks.x,
            lastClicks.y - firstClicks.y
          );
        }
        canvas.fillStyle = "rgba(100,100,100,0.5)";
        canvas.fill();
        canvas.strokeStyle = "#df4b26";
        canvas.lineWidth = 1;
        canvas.stroke();
      }
    }

    if (context) {
      context.addEventListener("mousedown", function (e) {
        firstClick = {
          x: e.offsetX,
          y: e.offsetY,
        };
        mousedown = true;
      });

      context.addEventListener("mousemove", function (e) {
        if (mousedown) {
          lastClick = {
            x: e.offsetX,
            y: e.offsetY,
          };
          drawRectangle(firstClick, lastClick);
        }
      });

      context.addEventListener("mouseup", function (e) {
        mousedown = false;
        lastClick = {
          x: e.offsetX,
          y: e.offsetY,
        };
      });

      context.addEventListener("mouseleave", function () {
        mousedown = false;
      });
    }
  }, []);
  return (
    <div ref={divRef}>
      <canvas className="canvas" ref={canvasRef}></canvas>
    </div>
  );
};

export default Canvas;

Drawing rectangles :D

  • Related