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;