Upload a file with React
Tags: react, typescript, javascript, webdev
Published: 2021-04-30 04:35:46

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](https://github.com/georgexx009/photos-app)

### Controlled

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

```javascript
<input type="file" name="theFiles" onChange={handleChangeFile} />
```

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.

```typescript
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
	}
}
```

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()`](https://developer.mozilla.org/en-US/docs/Web/API/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](https://developer.mozilla.org/en-US/docs/Web/API/FormData)

```typescript
const formData = new FormData();

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

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.

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

### Server

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

[multer](https://www.npmjs.com/package/multer)

A simple configuration will be:

```typescript
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),
  }),
});
```

And we set as middleware the call from this.

```javascript
upload.array('theFiles')
```

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