简体   繁体   中英

useEffect with useState not rerendering React component

I have a component where the user drags some files and stores it in a state. Without getting into the bulk of the code, here is what I have

  const [files, setFiles] = useState([]);

  useEffect(() => {
    setFiles(getFileList(files, acceptedFiles));
  }, [files, acceptedFiles]);

I am using the React Dropzone package and when the user drops files to the container, the acceptedFiles property of dropzone gets populated with those files. In the useEffect hook, I want to set my state files based on these new files ie whenever the user adds new files, I want to update my state to include these new files and trigger a rerender. However, this isn't happening. When I drop a file, nothing gets rendered. But then when I drop another file, the first file gets rendered and when I add a third file, the second gets rendered and so on... I'm not sure what I'm doing wrong, any advice?

Here is my component:

export function Upload() {
  const { acceptedFiles, getRootProps, getInputProps, isDragActive, open } =
    useDropzone({ noClick: true });

  const [files, setFiles] = useState([]);

  useEffect(() => {
    setFiles(getFileList(files, acceptedFiles));
  }, [files, acceptedFiles]);

  const baseContent = (
    <div className={styles.baseContent}>
      <h4 className={styles.uploadText}>{"click to add or drop files here"}</h4>
      <AiOutlineUpload className={styles.icon} />
    </div>
  );

  return (
    <Dropzone getRootProps={getRootProps} isDragActive={isDragActive}>
      {files.length === 0 ? (
        <Clickzone getInputProps={getInputProps} open={open}>
          {baseContent}
        </Clickzone>
      ) : (
        <FileSystem files={files} getInputProps={getInputProps} open={open} />
      )}
    </Dropzone>
  );
}

function getFileList(files, newFiles) {
  var totalSize = getTotalSize(files);
  var count = files.length;

  for (var i = 0; i < newFiles.length; i++) {
    if (count >= constants.MAX_NUM_OF_FILES) break;

    var file = {
      name: newFiles[i].name,
      type: newFiles[i].type,
      size: newFiles[i].size,
    };

    file.name = sanitizeFileName(file.name);
    file.size = convertToMB(file.size);

    if (file.size > constants.MAX_UPLOAD_SIZE) continue;
    totalSize += file.size;
    if (totalSize > constants.MAX_UPLOAD_SIZE) break;
    count++;
    files.push(file);
  }

  return files;
}

At the moment your useEffect is waiting for files and accepted files to run. This would actually cause an infinite loop since you changing files would cause the useEffect to run which then sets files.

What you should be doing is using the onDrop event and call the function once files have been dropped.

So something like this

export function Upload() {
  const { acceptedFiles, getRootProps, getInputProps, isDragActive, open } =
    useDropzone({ noClick: true });

  const [files, setFiles] = useState([]);

  const baseContent = (
    <div className={styles.baseContent}>
      <h4 className={styles.uploadText}>{"click to add or drop files here"}</h4>
      <AiOutlineUpload className={styles.icon} />
    </div>
  );

  const onDropAccepted = (acceptedFiles) => {
    setFiles(acceptedFiles)
  }

  return (
    <Dropzone getRootProps={getRootProps} isDragActive={isDragActive} onDrop={handleInDropAccepted}>
      {files.length === 0 ? (
        <Clickzone getInputProps={getInputProps} open={open}>
          {baseContent}
        </Clickzone>
      ) : (
        <FileSystem files={files} getInputProps={getInputProps} open={open} />
      )}
    </Dropzone>
  );
}

Here is a link to the docs which shows you all of the other props you can add in, https://react-dropzone.js.org/#!/Dropzone

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