Uploading multiple files with Fetch and FormData APIs
Asked Answered
J

3

18

I'm trying to use the native Fetch and FormData APIs to upload multiple files at once to the server but I can't for the life of me get it to work. Here's what I've got:

// acceptedFiles are File objects coming from `react-dropzone`.
function handleSubmit(acceptedFiles) {
  const data = new FormData();

  for (const file of acceptedFiles) {
    data.append('files', file, file.name);
  }

  return fetch('https://example.com/api/upload', {
    method: 'POST',
    body: data,      
  });
}

But what my Rails server receives is this:

Parameters: {"files"=>#
<ActionDispatch::Http::UploadedFile:0x00007feb347becc0 @tempfile=#
<Tempfile:/var/folders/kl/y1jrp7zs55sbx075jjjl3p280000gn/T/RackMultipart201
71112-6486-1ftkufy.mp4>, @original_filename="SampleVideo_1280x720_5mb.mp4",
 @content_type="video/mp4", @headers="Content-Disposition: form-data; 
name=\"files\"; filename=\"SampleVideo_1280x720_5mb.mp4\"\r\nContent-Type:
 video/mp4\r\n">}

In other words, it looks like files is actually just one file. But the docs for FormData say that append should append multiple files.

So what's going wrong?

Jenn answered 12/11, 2017 at 20:34 Comment(1)
This might be relevant: github.com/rack/rack/issues/566Jenn
J
37

The solution was to change files to files[]:

// acceptedFiles are File objects coming from `react-dropzone`.
function handleSubmit(acceptedFiles) {
  const data = new FormData();

  for (const file of acceptedFiles) {
    data.append('files[]', file, file.name);
  }

  return fetch('https://example.com/api/upload', {
    method: 'POST',
    body: data,      
  });
}
Jenn answered 13/11, 2017 at 3:2 Comment(1)
cant believe with so many examples, no had put []Misbehave
A
0

For sending multiple files i have done it a little bit different because my php api endpoint says there was only one file if i send it with formdata.append('files[]', file)

<input type="file" multiple data-sender="{{ user.hashIdentifier }}">
/** @type {HTMLInputElement} */
const input = document.querySelector('input[type=file]');
input.addEventListener('input', function listener(event){

  if(input.files.length > 0){
    const formData = new FormData();

    for(let i = 0; i < input.files.lenght; i++){
      formData.append(`file_${i}`, input.files[i]);
    }

    // my endpoint should know who sends the file

    formData.append('identify', input.dataSet.sender);

    sendData(formData);

  } else {
    console.error('no files selected');
  }   
});

/** 
 * sending the formData Object with all files to the server
 * @param {FormData} formData 
 */
function sendData(formData) {
  const url = '/api/saveFiles';
  const options = {
    method: 'POST',
    body: formData
  };

  fetch(url, options)
    .fetch((response) => {
      if(!response.ok) {
        throw Error(response.statusText);
      }

      return response.json();
    })
    .fetch((data) => {
      console.log('success', data);
    })
    .catch((error) => {
      console.error(error);
    })

}

my php is a symfony project and looks like that:


/**
 * @param Request $request (inject symfonys http request)
 * @param FileService $fileService (inject my own FileService)
 * @returns Response (symfony http Response)
 */
#[Route('/api/saveFiles', name: 'api_saveFiles', methods: ['POST'])]
public function api_saveFiles(Request $request, FileService $fileService): Response
{
  $statusCode = 409; // Conflict

  $result = array();
  
  /** @var FileBag */
  $fileBag = $request->files;

  if($fileBag->count() > 0) {
    $statusCode=200;
    $result['numberOfFiles'] = $fileBag->count();
    $result['keys'] = $fileBag->keys();

    $result['infos'] = array();
    foreach($fileBag->keys() as $key){
      try {
        $file = $fileBag->get($key);
        $fileInfo = $fileService->saveFile($file); // saves the file and gives back some infos
        $result['infos'][$fileInfo];
      } catch(Exception $ex) {
        // TODO: handle errors
      }    

    }
  }

  return new JsonResponse($result, $statusCode);
}

in the attemps i tried it with formData.append('files[]', file); php has only captured the last file.

Acrefoot answered 10/11, 2021 at 13:29 Comment(0)
C
0
// acceptedFiles are File objects coming from `react-dropzone`.
function handleSubmit(acceptedFiles) {
  const data = new FormData();

  for (const [index, file] of acceptedFiles) {
    data.append('files' + index, file, file.name);
  }

  return fetch('https://example.com/api/upload', {
    method: 'POST',
    body: data,      enter code here
  });
}
Criticism answered 17/6, 2022 at 7:21 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Evapotranspiration

© 2022 - 2024 — McMap. All rights reserved.