DEV Community

loading...
Cover image for Upload a file with React

Upload a file with React

Emmanuel Galindo
I’m a 24 year old mechatronic engineer by title and Full Stack Developer by working career.
・2 min read

We could make it controlled or uncontrolled as any input with react. Will vary only on how we store the file in the React component.

I don’t have an exclusive repo for this but an app that I’m working on apply this in photo form component and in an endpoint from the API.

georgexx009/photos-app

Controlled

We need an input file type. No value need it.

<input type="file" name="theFiles" onChange={handleChangeFile} />
Enter fullscreen mode Exit fullscreen mode

For the “on change handler” y use one custom hook that helps me with my forms.

This hook has the state from the form, and returns two types of handlers (one for text inputs, and another for files), but you can add more.

import { ChangeEvent, useState } from 'react'

interface UseFormProps<T> {
    initialState: T
}

export const useForm = <T>({ initialState }: UseFormProps<T>) => {
    const [formState, setFormState] = useState<T>(initialState)

    const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
        setFormState(prevState => ({
            ...prevState,
            [event.target.name]: event.target.value
        }))
    }

    const handleChangeFile = (event: ChangeEvent<HTMLInputElement>) => {
        if (!event.target.files?.length) {
      return;
    }

        setFormState(prevState => ({
            ...prevState,
            [event.target.name]: event.target.files
        }))
    }

    return {
        formState,
        handleChange,
        handleChangeFile
    }
}
Enter fullscreen mode Exit fullscreen mode

What we should see here, is that we are going to save event.target.files. If the input supports multiple files then the array will have all the files, but for now is only an array with one element. One thing to point here, is that the name from the input has to be the same as the name from the property in the form state to could save it with the bracket notation which accepts a variable.

Then, to being able to send it, we have to append the file to a FormData object.

The FormData interface provides a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using the XMLHttpRequest.send() method. It uses the same format a form would use if the encoding type were set to "multipart/form-data”.
MDN - https://developer.mozilla.org/en-US/docs/Web/API/FormData

const formData = new FormData();

Array.from(formState.theFiles).forEach((file: any) => {
    formData.append('theFiles', file);
});
Enter fullscreen mode Exit fullscreen mode

One core point to mention here is that the key we use to save our file has to be the same we use in the back when we call Multer method. Is up to you if you change the way of handle this.

And finally, we call our fetch method with axios.

const config = {
    headers: { 'content-type': 'multipart/form-data' }
};
const response = await axios.post('/api/photos', formData, config);
Enter fullscreen mode Exit fullscreen mode

Server

For the endpoint we will need Multer to manage our file.

multer

A simple configuration will be:

const oneMegabyteInBytes = 1000000;
const outputFolderName = './public/uploads';

const upload = multer({
  limits: { fileSize: oneMegabyteInBytes * 2 },
  storage: multer.diskStorage({
    destination: './public/uploads',
    filename: (req, file, cb) => cb(null, file.originalname),
  }),
});
Enter fullscreen mode Exit fullscreen mode

And we set as middleware the call from this.

upload.array('theFiles')
Enter fullscreen mode Exit fullscreen mode

This will set in our request object a files property with our files/file.

Discussion (0)