I have a next application for creating animations. The frames are saved onto a timeline where the user can scroll older frames. I am trying to add a simple functionality of deleting one of the frames by clicking an attached button, but even though the syntax is the same as in any other function and there are no errors, the function just isn't called. Here is the component, the parent component, and package.json:
import React from "react";
import { useCanvas } from "./CanvasContext";
import { settings } from "./Signals";
import ReusableLayer from "../components/utility/ResuableLayer";
import { timeline } from "./Signals";
import { v4 as uuidv4 } from "uuid";
import IndexLabel from "../components/utility/IndexLabel";
import JSZip from "jszip";
import { saveAs } from "file-saver";
const Timeline: React.FC = () => {
const handleDownloadImages = () => {
const zip = new JSZip();
// Add each image to the zip file
timeline.value.forEach((imageDataURL, index) => {
const base64Data = imageDataURL.split(",")[1];
zip.file(`image_${index + 1}.png`, base64Data, { base64: true });
});
// Generate the zip file and trigger download
zip.generateAsync({ type: "blob" }).then((blob) => {
saveAs(blob, "timeline_images.zip");
});
};
const handleDeleteImage = (index: number, e: React.MouseEvent) => {
e.stopPropagation();
console.log("click");
// Create a copy of the timeline array
const updatedImages = [...timeline.value];
// Modify the copy
updatedImages.splice(index, 1);
// Update the timeline array with the modified copy
timeline.value = updatedImages;
console.log("splicing");
};
const timelineImageSize = 400
return (
<div className="relative w-full overflow-x-auto bg-gray-200">
<div
className="flex h-auto overflow-y-hidden flex-row-reverse gap-1 py-1"
style={{
width:
(timelineImageSize) * timeline.value.length - 0
// settings.value.canvasSize.x,
}}
>
{timeline.value.map((imageDataURL, index) => (
<div
className="relative border-2"
key={uuidv4()}
style={{
border: "black 1px solid",
width: (timelineImageSize),
height: (timelineImageSize),
}}
>
<button
onClick={(e) => handleDeleteImage(index, e)}
className="z-50 top-0 right-0 p-2 bg-red-500 text-white rounded-md cursor-pointer"
>
Delete
</button>
<button
onClick={(e) => handleDeleteImage(index, e)}
className=" "
>
Delete
</button>
<img
src={imageDataURL}
width={timelineImageSize} // Render two times smaller
height={timelineImageSize} // Render two times smaller
alt={`Image ${index}`}
/>
{/* <IndexLabel
label={(index + 1).toString()}
customClasses="absolute top-0 left-0 text-lg"
/> */}
</div>
))}
</div>
{timeline.value.length > 0 && (
<button
// onClick={handleDownloadImages}
className="mt-4 p-2 bg-blue-500 text-white rounded-md cursor-pointer absolute bottom-0 right-0"
>
Download Images
</button>
)}
<button
onClick={
(e) => handleDeleteImage(0, e )
}
className="z-10 top-0 right-0 p-2 bg-red-500 text-white rounded-md cursor-pointer"
>
Delete
</button>
</div>
);
};
parent component:
const Page: React.FC = () => {
const { canvasRef, frontlineCanvasRef, markerCanvasRef,backgroundCanvasRef } =
useCanvas();
const { GlobalData, updateGlobalData } = useGlobalValue();
const [mouseDownTimeStamp, setMouseDownTimeStamp] = useState<number | null>(
null
);
const [elapsedTime, setElapsedTime] = useState<number>(0);
const handleMouseDown = (e: MouseEvent) => {
if (e.button === 2) {
e.preventDefault();
}
setMouseDownTimeStamp(Date.now());
};
const handleMouseUp = (e: MouseEvent) => {
e.preventDefault();
// Using requestAnimationFrame to delay the execution until the next frame
requestAnimationFrame(() => {
setElapsedTime(0);
setMouseDownTimeStamp(null);
});
};
useEffect(() => {
// Add event listeners when the component mounts
document.addEventListener("mousedown", handleMouseDown);
document.addEventListener("mouseup", handleMouseUp, true);
// Remove event listeners when the component unmounts
return () => {
document.removeEventListener("mousedown", handleMouseDown);
document.removeEventListener("mouseup", handleMouseUp, true);
};
}, []);
useEffect(() => {
// Update elapsed time while the button is pressed
if (mouseDownTimeStamp !== null) {
const intervalId = setInterval(() => {
setElapsedTime((prevElapsedTime) => prevElapsedTime + 100);
updateGlobalData("mouseDownTime", elapsedTime + 100);
}, 100);
return () => clearInterval(intervalId);
}
}, [mouseDownTimeStamp, elapsedTime]);
useEffect(() => {
updateGlobalData("mouseDownTime", elapsedTime);
}, [elapsedTime]);
return (
< >
<CanvasSettings />
<CanvasEditor />
<Timeline />
</>
);
};
package.json
{
"name": "mapping-software",
"version": "0.1.0",
"private": true,
"configurations": [
{
"name": "Next: Chrome",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"webpack:///./*": "${webRoot}/*"
}
}
],
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/material": "^5.14.18",
"@mui/system": "^5.14.18",
"@preact/signals": "^1.2.1",
"file-saver": "^2.0.5",
"html2canvas": "^1.4.1",
"jszip": "^3.10.1",
"next": "14.0.0",
"react": "^18",
"react-dom": "^18",
"react-dropzone": "^14.2.3",
"uuidv4": "^6.2.13"
},
"devDependencies": {
"@types/file-saver": "^2.0.7",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/tinycolor2": "^1.4.6",
"@wixc3/react-board": "^2.3.0",
"autoprefixer": "^10",
"eslint": "^8",
"eslint-config-next": "14.0.0",
"postcss": "^8",
"postcss-preset-env": "^9.2.0",
"tailwindcss": "^3.3.5",
"typescript": "^5"
}
}
The behavior I described is happening only inside the timeline. value.map block. Every button outside it works. Additionally, when I comment out this block it also starts working:
useEffect(() => {
// Add event listeners when the component mounts
document.addEventListener("mousedown", handleMouseDown);
document.addEventListener("mouseup", handleMouseUp, true);
// Remove event listeners when the component unmounts
return () => {
document.removeEventListener("mousedown", handleMouseDown);
document.removeEventListener("mouseup", handleMouseUp, true);
};
}, []);
for some reason, when I remove the uuidv4 as a keyprop the delete button starts working