I tried to assemble your code as possible, it seems you have buffer overflow (blue js heap), you need to investigate here, these are the root cause.
The initial approach is to create circle just once, then animate the child from parent, by this way you avoid intensive memory and CPU computing.
Add how many circles by clicking on the canvas, canvas credit goes to Martin
Update
Following for alexander discussion it is possible to use setTimeout, or Timeinterval (Solution 2)
Soltion #1
App.js
import React from 'react';
import { useCircle } from './useCircle';
import './App.css';
const useAnimationFrame = callback => {
// Use useRef for mutable variables that we want to persist
// without triggering a re-render on their change
const requestRef = React.useRef();
const previousTimeRef = React.useRef();
const animate = time => {
if (previousTimeRef.current != undefined) {
const deltaTime = time - previousTimeRef.current;
callback(deltaTime)
}
previousTimeRef.current = time;
requestRef.current = requestAnimationFrame(animate);
}
React.useEffect(() => {
requestRef.current = requestAnimationFrame(animate);
return () => cancelAnimationFrame(requestRef.current);
}, []); // Make sure the effect runs only once
}
function App() {
const [count, setCount] = React.useState(0)
const [coordinates, setCoordinates, canvasRef, canvasWidth, canvasHeight, counts] = useCircle();
const speedX = 1 // tunne performance by changing this
const speedY = 1 // tunne performance by changing this
const requestRef = React.useRef();
const previousTimeRef = React.useRef();
const handleCanvasClick = (event) => {
// on each click get current mouse location
const currentCoord = { x: event.clientX, y: event.clientY ,directionX:"right",directionY:"down"};
// add the newest mouse location to an array in state
setCoordinates([...coordinates, currentCoord]);
// query.push(currentCoord)
//query.push(currentCoord)
};
const move = () => {
let q = [...coordinates]
q.map(coordinate => { return { x: coordinate.x + 10, y: coordinate.y + 10 } })
setCoordinates(q)
}
const handleClearCanvas = (event) => {
setCoordinates([]);
};
const animate = time => {
//if (time % 2===0){
setCount(time)
if (previousTimeRef.current != undefined) {
const deltaTime = time - previousTimeRef.current;
setCoordinates(coordinates => coordinates.map((coordinate)=> {
let x=coordinate.x;
let y=coordinate.y;
let directionX=coordinate.directionX
let directionY=coordinate.directionY
if (x < 0) directionX = "right"
if (x > canvasWidth) directionX = "left"
if (y < 0) directionY = "down"
if (y > canvasHeight) directionY = "up"
if (directionX === "left") x -= speedX
else x += speedX
if (directionY === "up") y -= speedY
else y += speedY
return { x:x,y:y,directionX:directionX,directionY:directionX}
}))
// }
}
previousTimeRef.current = time;
requestRef.current = requestAnimationFrame(animate);
}
React.useEffect(() => {
requestRef.current = requestAnimationFrame(animate);
return () => cancelAnimationFrame(requestRef.current);
}, []); // Make sure the effect runs only once
return (
<main className="App-main" >
<div>{Math.round(count)}</div>
<canvas
className="App-canvas"
ref={canvasRef}
width={canvasWidth}
height={canvasHeight}
onClick={handleCanvasClick}
/>
<div className="button" >
<button onClick={handleClearCanvas} > CLEAR </button>
</div>
</main>
);
};
export default App;
userCircle.js
import React, { useState, useEffect, useRef } from 'react';
var circle = new Path2D();
circle.arc(100, 100, 50, 0, 2 * Math.PI);
const SCALE = 1;
const OFFSET = 80;
export const canvasWidth = window.innerWidth * .5;
export const canvasHeight = window.innerHeight * .5;
export const counts=0;
export function draw(ctx, location) {
console.log("attempting to draw")
ctx.fillStyle = 'red';
ctx.shadowColor = 'blue';
ctx.shadowBlur = 15;
ctx.save();
ctx.scale(SCALE, SCALE);
ctx.translate(location.x / SCALE - OFFSET, location.y / SCALE - OFFSET);
ctx.rotate(225 * Math.PI / 180);
ctx.fill(circle);
ctx.restore();
};
export function useCircle() {
const canvasRef = useRef(null);
const [coordinates, setCoordinates] = useState([]);
useEffect(() => {
const canvasObj = canvasRef.current;
const ctx = canvasObj.getContext('2d');
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
coordinates.forEach((coordinate) => {
draw(ctx, coordinate)
}
);
});
return [coordinates, setCoordinates, canvasRef, canvasWidth, canvasHeight,counts];
}
Soltion #2 Using Interval
IntervalExample.js (app) 9 sample circle
import React, { useState, useEffect } from 'react';
import Circlo from './Circlo'
const IntervalExample = () => {
const [seconds, setSeconds] = useState(0);
const [circules, setCircules] = useState([]);
let arr =[
{x:19,y:15, r:3,directionX:'left',directionY:'down'},
{x:30,y:10,r:4,directionX:'left',directionY:'down'},
{x:35,y:20,r:5,directionX:'left',directionY:'down'},
{x:0,y:15, r:3,directionX:'left',directionY:'down'},
{x:10,y:30,r:4,directionX:'left',directionY:'down'},
{x:20,y:50,r:5,directionX:'left',directionY:'down'},
{x:70,y:70, r:3,directionX:'left',directionY:'down'},
{x:80,y:80,r:4,directionX:'left',directionY:'down'},
{x:10,y:20,r:5,directionX:'left',directionY:'down'},
]
const reno =(arr)=>{
const table = Array.isArray(arr) && arr.map(item => <Circlo x={item.x} y={item.y} r={item.r} />);
return(table)
}
const speedX = 0.1 // tunne performance by changing this
const speedY = o.1 // tunne performance by changing this
const move = (canvasHeight,canvasWidth) => {
let xarr= arr.map(((coordinate)=> {
let x=coordinate.x;
let y=coordinate.y;
let directionX=coordinate.directionX
let directionY=coordinate.directionY
let r=coordinate.r
if (x < 0) directionX = "right"
if (x > canvasWidth) directionX = "left"
if (y < 0) directionY = "down"
if (y > canvasHeight) directionY = "up"
if (directionX === "left") x -= speedX
else x += speedX
if (directionY === "up") y -= speedY
else y += speedY
return { x:x,y:y,directionX:directionX,directionY:directionY,r:r}
}))
return xarr;
}
useEffect(() => {
const interval = setInterval(() => {
arr =move(100,100)
setCircules( arr)
setSeconds(seconds => seconds + 1);
}, 10);
return () => clearInterval(interval);
}, []);
return (
<div className="App">
<p>
{seconds} seconds have elapsed since mounting.
</p>
<svg viewBox="0 0 100 100">
{ reno(circules)}
</svg>
</div>
);
};
export default IntervalExample;
Circlo.js
import React from 'react';
export default function Circlo(props) {
return (
<circle cx={props.x} cy={props.y} r={props.r} fill="red" />
)
}


