Reputation: 1700
I have seen similar questions on SO but have not been able to identify the issue I'm having.
I have a useState variable that I set in useEffect on first load. Then, at a much later time an event fires and within the event I check the value of the variable and it's always an empty array []. However, I added a second useEffect to track the changes in my state variable and I don't see where it's being emptied. Here's some code to highlight my issue:
import React, { useState, useEffect } from "react";
import { DragAndDrop } from "../components";
const AddKidPage = () => {
const [files, setFiles] = useState([]);
useEffect(() => {
var f = [
"nice.pdf",
"verycool.jpg",
"amazing.png",
"goodstuff.mp3",
"thankyou.doc",
];
setFiles(f);
}, []);
useEffect(() => {
console.log(files);
}, [files]);
const handleDrop = (newFiles) => {
let fileList = files;
// HERE FILES = []
for (var i = 0; i < newFiles.length; i++) {
if (!newFiles[i].name) return;
fileList.push(newFiles[i].name);
}
setFiles(fileList);
};
return (
<main>
<DragAndDrop handleDrop={handleDrop}>
<div style={{ height: 300, width: 250 }}>
{files.map((file, i) => (
<div key={i}>{file}</div>
))}
</div>
</DragAndDrop>
</main>
);
};
export default AddKidPage;
I have 1 console.log in the file-tracking useEffect. Here is the output when the component mounts:
[]
["nice.pdf", "verycool.jpg", "amazing.png", "goodstuff.mp3", "thankyou.doc"]
This makes sense - I'm initializing the array as empty then I immediately fill it with values which then triggers the second output. However, here is the output AFTER I drop a file (this causes handleDrop() to execute):
["newFile.png"]
As you can see ALL other values were wiped out. My comment in code "HERE FILES = []" demonstrates where the files useState is still empty even though my output shows it was filled. The useEffect never outputs an empty array a second time so where is it being emptied? Am I retrieving the value wrong? Any help with this mystery is greatly appreciated. I opted to not show the code for DragAndDrop component but it seems solid and calls the handleDrop method at the right time and with correct values.
EDIT:
Although DragAndDrop seems solid, this could be some sort of issue with how I'm calling the passed in function... or my use of {children}. I'm not sure. So, here is the code for DragAndDrop which completes this component.
import React, { useState, useEffect } from "react";
import styled from "styled-components";
const DragAndDrop = ({ handleDrop: externalDrop, children }) => {
const [drag, setDrag] = useState(false);
const [dragCounter, setDragCounter] = useState(0);
const handleDrag = (e) => {
e.preventDefault();
e.stopPropagation();
};
const handleDragIn = (e) => {
e.preventDefault();
e.stopPropagation();
setDragCounter(dragCounter + 1);
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
setDrag(true);
}
};
const handleDragOut = (e) => {
e.preventDefault();
e.stopPropagation();
setDragCounter(dragCounter - 1);
if (dragCounter - 1 === 0) {
setDrag(false);
}
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
setDrag(false);
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
externalDrop(e.dataTransfer.files);
e.dataTransfer.clearData();
setDragCounter(0);
}
};
useEffect(() => {
var div = document.getElementById("draganddrop-wrapper");
div.addEventListener("dragenter", handleDragIn);
div.addEventListener("dragleave", handleDragOut);
div.addEventListener("dragover", handleDrag);
div.addEventListener("drop", handleDrop);
return () => {
div.removeEventListener("dragenter", handleDragIn);
div.removeEventListener("dragleave", handleDragOut);
div.removeEventListener("dragover", handleDrag);
div.removeEventListener("drop", handleDrop);
};
}, []);
return (
<Wrapper id="draganddrop-wrapper">
{drag && (
<div className="drag-hover">
<div className="drag-overlay">
<div>drop here</div>
</div>
</div>
)}
{children}
</Wrapper>
);
};
const Wrapper = styled.div`
display: inline-block;
position: relative;
.drag-hover {
border: dashed grey 4px;
background: rgba(255, 255, 255, 0.8);
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 9999;
.drag-overlay {
position: absolute;
top: 50%;
right: 0;
left: 0;
text-align: center;
color: grey;
font-size: 36px;
}
}
`;
export default DragAndDrop;
Upvotes: 2
Views: 1296
Reputation: 4268
Its because you are mutating the files
variable instead of cloning it.
Change
let fileList = files;
to
const fileList = [...files];
Update
const handleDrop = useCallback((newFile) => {
if (newFile.type === "image/png") {
setFile(newFile.name);
}
}, []);
Upvotes: 3