简体   繁体   中英

State returns an empty array even after setting in useEffect

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;


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);
    }
}, []);

Codesandbox

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM