React Hooks: как правильно обрабатывать async setState внутри компонента? (uploadProgress axios) - PullRequest
1 голос
/ 23 октября 2019

Мы хотим разместить несколько файлов параллельно и отображать ход загрузки.

Наша первая попытка решить это - использовать setState и предыдущее состояние в обратном вызове uploadProgress:

const FileUpload = () => {
  const [pendingUploads, setPendingUploads] = useState({});

  // For clarity: files = Record<string, {id: string, progress: string, data: FormData}>
  const handleChange = files => {
    setPendingUploads(files);

    const handleUploadProgress = (progressEvent, id) => {
      const percentCompleted = (progressEvent.loaded * 100) / progressEvent.total;

      // Problem: pendingUploads is always empty, we do not get the updated value from last update 
      setPendingUploads({
        ...pendingUploads,
        [id]: {
          ...pendingUploads[id],
          progress: `${percentCompleted}%`
        }
      });
    };

    files.forEach(file => {
      axios.post("/files", file.data, {
        onUploadProgress: e => handleUploadProgress(e, file.id)
      });
    });
  };

  return (
    <>
      <div>
        {_.map(pendingUpload, (pendingUpload, idx) => (
          <div key={idx}>
            {pendingUpload.id}: {pendingUpload.progress}
          </div>
        ))}
      </div>

      <input multiple type="file" onChange={handleChange} />
    </>
  );
};

pendingUploadsвсегда пусто, поэтому значение прогресса отображается только для одного файла за раз.

Как правильно это сделать? На данный момент у нас есть 2 рабочих примера:

1) Решение 1

  • Сохранить карту ожидающей загрузки в состоянии (pendingUploads)
  • Инициируйте обновление прогресса (progressUpdate), используя setState внутри handleUploadProgress (обратный вызов axios)
  • Обновление pendingUploads в использованииEffect при изменении progressUpdate
const FileUpload = () => {
  const [pendingUploads, setPendingUploads] = useState({});
  const [progressUpdate, setProgressUpdate] = useState({ id: "", value: "" });

  useEffect(() => {
    setPendingUploads({
      ...pendingUploads,
      [progressUpdate.id]: {
        ...pendingUploads[progressUpdate.id],
        progress: progressUpdate.value
      }
    });
  }, [progressUpdate]);

  const handleUploadProgress = (progressEvent, id) => {
    const percentCompleted = (progressEvent.loaded * 100) / progressEvent.total;
    setProgressUpdate({ id, value: `${percentCompleted}%` });
  };

  // files = Record<string, {id: string, progress: string, data: FormData}>
  const handleChange = files => {
    setPendingUploads(files);
    files.forEach(file => {
      axios.post("/files", file.data, {
        onUploadProgress: e => handleUploadProgress(e, file.id)
      });
    });
  };

  return (
    <div>
      <div>
        {_.map(pendingUploads, (pendingUpload, idx) => (
          <div key={idx}>
            {pendingUpload.id}: {pendingUpload.progress}
          </div>
        ))}
      </div>

      <input multiple type="file" onChange={handleChange} />
    </div>
  );
};



2) Решение 2

  • Измените файлы в обратном вызове uploadProgress и используйте измененное значение для обновления состояния компонента
const FileUpload = () => {

  const [pendingUploads, setPendingUploads] = useState({});

  // files = Record<string, {id: string, progress: string, data: FormData}>
  const handleChange = files => {
    const handleUploadProgress = (progressEvent, id) => {
      const percentCompleted = (progressEvent.loaded * 100) / progressEvent.total;
      files[id].progress = `${percentCompleted}%`;
      setPendingUploads({ ...files });
    };

    files.forEach(file => {
      axios.post("/files", file.data, {
        onUploadProgress: e => handleUploadProgress(e, file.id)
      });
    });
  };

  return (
    <>
      <div>
        {_.map(pendingUpload, (pendingUpload, idx) => (
          <div key={idx}>
            {pendingUpload.id}: {pendingUpload.progress}
          </div>
        ))}
      </div>

      <input multiple type="file" onChange={handleChange} />
    </>
  );
};

Любой отзыв или рабочий пример, показывающий лучшие практики для данного конкретного случаяДобро пожаловать.

Спасибо

1 Ответ

2 голосов
/ 23 октября 2019

Вы не можете использовать pendingUploads const внутри ответных обратных вызовов, потому что это будет ссылаться на устаревшую версию. Вместо этого рассмотрите возможность использования предыдущей подписи setState состояния следующим образом: setPendingUploads((prevPendingUploads) => {return {...prevPendingUploads, /* updates here */}}

...