Run timers in a sequential manner: Reactjs

Viewed 161

I am having a file upload component, that takes in a list of files to be uploaded. I The wau I implemented, each of these files have thir timers run in parallel without affecting others. Progress bar are attached to each file and they are run to completion parallely. What I want is once I select files, I want each file to run its progress bar in sequential manner instead all progress bars at once. Like take a single file, run its progress and then onc eits done ,go to the next one

Can someone help

https://codesandbox.io/s/updated-file-upload-4vrnks?file=/src/Upload.tsxfile=/src/Upload.tsx:0-3474

import * as React from "react";
import { Stack, Button, ListItem } from "@material-ui/core";
import { useEffect, useRef } from "react";
import { Progress } from "./Progress";

export interface FileType {
  file: File;
  isDeleted: boolean;
  uploadStatus: number;
  uploadError: string;
  uploadAbort: boolean;
}

interface FileUploaderProps {
  selectedFiles: FileType[];
  setSelectedFiles: React.Dispatch<React.SetStateAction<FileType[]>>;
  file: FileType;
}

const UploadFile = ({ file, setSelectedFiles }: FileUploaderProps) => {
  const timerRef = useRef<ReturnType<typeof setInterval> | undefined>();
  const pollRef = useRef<number>();
  pollRef.current = file.uploadStatus;

  const onFileUpload = () => {
    timerRef.current = setInterval(() => {
      if (pollRef.current === 100) {
        clearInterval(timerRef?.current);
        return pollRef?.current;
      }
      const diff = Math.random() * 15;
      let currentProgess = file.uploadStatus;
      setSelectedFiles((prevState) => {
        return prevState.map((current: FileType) => {
          if (current.file.name === file.file.name) {
            currentProgess = Math.min(current.uploadStatus + diff, 100);
            return {
              ...current,
              uploadStatus: currentProgess
            };
          }
          return current;
        });
      });
      return currentProgess;
    }, 100 * Math.floor(Math.random() * 6) + 5 * 50);
  };

  useEffect(() => {
    onFileUpload();
    return () => {
      clearInterval(timerRef.current);
    };
  }, []);

  const cancelTimer = (e: React.MouseEvent<HTMLSpanElement>) => {
    e.stopPropagation();
    setSelectedFiles((prevState) => {
      return prevState.map((current: FileType) => {
        if (current.file.name === file.file.name) {
          return {
            ...current,
            uploadAbort: true,
            uploadStatus: 0,
            uploadError: "File Upload cancelled"
          };
        }
        return current;
      });
    });
    clearInterval(timerRef.current);
  };

  return (
    <ListItem key={file.file.name} className={"file-name"}>
      <span style={{ padding: "10px" }}>{file.file.name}</span>
      {file.uploadStatus !== 100 ? (
        file.uploadError || file.uploadAbort ? (
          <div className={"upload-error"}>
            <span>{file.file.name}</span>
            <span style={{ padding: "10px" }}>{file.uploadError}</span>
          </div>
        ) : (
          <>
            <span>Uploading...</span>
            <Stack sx={{ width: "80px", padding: "20px" }} spacing={2}>
              <Progress progress={file.uploadStatus} />
            </Stack>
            <Button variant="contained" color="error" onClick={cancelTimer}>
              Cancel
            </Button>
          </>
        )
      ) : (
        <>
          <span className="primary">{file.file.name}</span>
        </>
      )}
      {file.uploadStatus === 100 && (
        <Button
          variant="contained"
          color="error"
          className="mgmt-Delete-icon"
          style={{ padding: "10px" }}
          onClick={(e: React.MouseEvent<HTMLSpanElement>) => {
            e.stopPropagation();
            setSelectedFiles((prevState) => {
              return prevState.map((current: FileType) => {
                if (current.file.name === file.file.name) {
                  return {
                    ...current,
                    isDeleted: true
                  };
                }
                return current;
              });
            });
          }}
        >
          Delete
        </Button>
      )}
    </ListItem>
  );
};

export default UploadFile;

1 Answers

Let's add the active property to the FileType:

export interface FileType {
  ...
  active: boolean;
}

All dropped files are inactive by default.

We want to start uploading when the file becomes active:

useEffect(() => {
  if (file.active) {
    onFileUpload();
  }
  return () => {
    clearInterval(timerRef.current);
  };
}, [file.active]);

Whenever selectedFiles is changed we choosing the next active file (or keep current):

useEffect(() => {
  const newActiveFile = selectedFiles.find(
    (f) =>
      !f.isDeleted &&
      !f.uploadAbort &&
      !f.uploadError &&
      f.uploadStatus !== 100
  );
  if (newActiveFile && !newActiveFile.active) {
    setSelectedFiles(
      selectedFiles.map((f) =>
        f === newActiveFile ? { ...newActiveFile, active: true } : f
      )
    );
  }
}, [selectedFiles]);

Working example

Update

You can hide not started files by adding this filter to the List:

&& file.active

Updated example

Related