#Examples of file uploader using Dropzone and Progress components?

19 messages · Page 1 of 1 (latest)

tranquil iron
#

Hey everyone, I'm trying to build a file uploader component that uses the Dropzone to upload multiple files at once and display each submitted file as it's own component showing the upload progress using the Progress component. Does anyone have any examples of something with this type of complexity?

I'm using Next.js's App Router and React Query so anyone has examples with these, that'd be great! This is my first time building this type of component and I'm struggling to break down how to build this.

civic bough
#

I would also like to have the ability to show a progress bar when uploading!

shrewd crow
#

I think you could kind of mock one yourself, using a state initializing at 0 and then increase it, and when action is completed set it to 100%

jovial cradle
#

you can do an XHR request for example

#

it's a default JS web thing, a bit more old school, gives you progress, completion, error callbacks and you can subscribe to the progress and then set state of a slider or Progress component

#

there's also libraries like Axios and similar that wrap the progress listeners into a neater API, more of a modern solution

#

and in a similar vein you can then link it to a slider/Progress state

tulip hill
jovial cradle
#

and filepond as Chris pointed out

#

@upbeat lake has been through many different upload approaches at this point, if you have something specific RE: certain libraries he may know

civic bough
civic bough
#

Has anyone tried axios?! I was looking into that! What my issue is now, figuring out if I could call a service action function within axios?!

Such as:

// app/actions/s3File.ts

import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"
import { revalidatePath } from "next/cache"

type S3Payload = {
  uploadDestination: string
  bucket: string
  hostName: string
  uploadEndpoint: string
}

export async function uploadFileToS3( formData: FormData, payload: S3Payload ): Promise<any[]> {
  const s3 = new S3Client({
    ...
  })
  
  const { uploadDestination, bucket, hostName, uploadEndpoint } = payload

  try {
    const files = formData.getAll("file") as File[]
    
    const response = await Promise.all(
      files.map(async (file) => {
        const {name: fileName, type: fileType, lastModified: date} = file
        const fileExtension = fileType.split("/")\[1\]
        const fileID = `${uploadDestination}\_CUSTOMRANDOMID` as string
        const filePath = `${uploadDestination}/${fileID}.${fileExtension}`
        
        const arrayBuffer = await file.arrayBuffer()
        const buffer = Buffer.from(arrayBuffer)
        
        const uploadFileObject = {
          "Bucket": bucket,
          "Key": filePath,
          "Body": buffer,
          "ContentType": fileType,
        }
        
        const uploadFile = new PutObjectCommand(uploadFileObject)
        const upload = await s3.send(uploadFile)
      })
    )
    
    revalidatePath("/")
    return response
  } catch (error) {
    ...
  }
}

and call the await uploadFileToS3(file, S3Payload) function within my upload page 🤔

civic bough
#

I have figured a possible solution? However, I'm unsure if it makes sense to do so or just seems redundant?

But I have created an API route to effectively collect the data; then send it to a controller to separate the formData and create different variables for the files and the payload; then I pass that controller data down to the server action.

This does seem to be working.

The API route looks something like this:

// api/upload/route.ts

import { handleFileUpload } from "@/app/controllers/upload.controller";

export async function POST(req: any) {
    return handleFileUpload(req)
}

The controller looks something like this:

// controllers/upload.controller.ts

import { NextResponse } from "next/server"
import { uploadFileToS3 } from "@/app/actions/s3File.ts";

export const handleFileUpload = async (req: any) => {
  try {
    const form = await req.formData()
    const files = form.getAll("files") as any

    const payloadRAW = form.get("payload")
    const {
      uploadDestination,
      mediaID,
      bucket,
      uploadEndpoint,
      pathname,
      redirectPath
    } =  JSON.parse(payloadRAW)

    const payload = {
      uploadDestination,
      mediaID,
      bucket,
      uploadEndpoint,
      pathname,
      redirectPath
    } as any

    const uploadURL = await uploadFileToS3(files, payload);
    return NextResponse.json({ message: "success", uploadURL });
  } catch (error) {
    console.error("Error uploading file:", error);
    return NextResponse.json({ message: "failure", reason: error.message });
  }
}

I then reconfigured my server action to take in the Files: File[] instead of the formData: FormData, along with some other general changes.

#

Then on my upload page I have:

// uploadPage/component.tsx
try {
  files.forEach((file: string | Blob) => {
    formData.append("files", file)
    
    const url = "/api/upload"
    
    axios
    .post(url, formData,
      {
        headers: {
          'x-ms-blob-type': 'BlockBlob',
          'Content-Type': 'multipart/form-data'
        },
        maxContentLength: 2e10,
        maxBodyLength: 2e10,
        onUploadProgress: (event: any) => {
          setUploadProgress(Math.round((event.loaded / event.total) * 100))
        }
      }
    )
      .then((upload) => {
        setUploading(false)
        ...
      })
      .catch((error) => {
       ...
      })
    })
} catch (error) {
  console.error("File(s) couldn't be uploaded to S3", error)
}
return <>
...
<Dropzone
    onDrop={(files) => handleOnSubmit(files)}
    onReject={(files) => console.log('rejected files', files)}
    loading={isUploading}
    bg="none"
    radius="md"
    c="white"
    {...props}
  >
    ...
  </Dropzone>
  <Grid align="center" justify="space-evenly" gutter="1rem" m="1rem 0.5rem 0" w="100%">
    <GridCol span={10}>
      <Progress radius="sm" size="2rem" value={uploadProgress} color="primary" animated={isUploading} bg="none"  />
    </GridCol>
    <GridCol span={2}>
      <Text m="0" p="0" ta="right">{uploadProgress}% Uploaded!</Text>
    </GridCol>
  </Grid>
...
</>