react-final-form icon indicating copy to clipboard operation
react-final-form copied to clipboard

Simple file input example

Open antoinerousseau opened this issue 6 years ago • 16 comments
trafficstars

There are no examples in the documentation about file inputs, and the issues advise to use react-dropzone, which is overkill if I just want a simple file input.

So I just wanted to share a TypeScript snippet that can be used for file inputs, especially if you want to have a File in your field value (instead of that useless fakepath given by the input value), if you want to create a FormData for example.

import type { InputHTMLAttributes } from "react"
import { Field } from "react-final-form"

interface Props extends InputHTMLAttributes<HTMLInputElement> {
  name: string
}

const FileField = ({ name, ...props }: Props) => (
  <Field<FileList> name={name}>
    {({ input: { value, onChange, ...input } }) => (
      <input
        {...input}
        type="file"
        onChange={({ target }) => onChange(target.files)} // instead of the default target.value
        {...props}
      />
    )}
  </Field>
)

The trick is to just replace the default change event behavior by giving it the files attribute instead of the value, and leave the input uncontrolled since we extract and ignore the value (this is normal for file inputs).

Then in your onSubmit handler, you will have a FileList as the field value, so you can iterate over it (or just read the first element if it's not a multiple file input) and add each File to your FormData, e.g. if you form has a <FileField name="files" />:

const handleSubmit = async (values: Values) => {
  const payload = new FormData()
  payload.append("file", values.files[0])
  // you can also add the rest of your form text data, e.g.:
  payload.append("firstname", "Antoine")
  payload.append("lastname", values.lastname)
  // and then just post it to your API using a regular POST:
  await fetch(YOUR_URI, {
    method: "POST",
    body: payload, // this sets the `Content-Type` header to `multipart/form-data`
  })
}

See it in action: https://codesandbox.io/s/react-final-form-file-field-ffugr

Related issues: #24 #92

If you like this example, I can make a PR to add it to the docs. Or it could be the default behavior of the lib for file inputs.

antoinerousseau avatar Oct 28 '19 11:10 antoinerousseau

Wow thank you! You saved my life.

Looks hacky enough to not be added to the official docs though. I think the lib should have it implemented as a feature instead.

mmoo avatar Dec 17 '19 15:12 mmoo

Thanks a lot @antoinerousseau. This is exactly what I was looking for. I agree with @mmoo, it would be great having it as a feature in the library.

dioris-moreno avatar Apr 04 '20 23:04 dioris-moreno

Can you add remove icon for deleting from file list array please

MadikMayCry avatar Jan 11 '21 20:01 MadikMayCry

@MadikMayCry checkout react-final-form-arrays for that matter.

antoinerousseau avatar Jan 11 '21 21:01 antoinerousseau

@MadikMayCry checkout react-final-form-arrays for that matter.

that was typo, i mean multiple file input, and delete icon for removing from files array

MadikMayCry avatar Jan 11 '21 21:01 MadikMayCry

Just add the multiple attribute.

antoinerousseau avatar Jan 11 '21 21:01 antoinerousseau

Just add the multiple attribute.

Here what i was talking about, but now problem is that component is not re-rendering on item deleting. https://codesandbox.io/s/infallible-wright-75cbm?file=/src/CustomForm.js

MadikMayCry avatar Jan 12 '21 17:01 MadikMayCry

Type 'HTMLInputElement' is missing the following properties from type 'FieldRenderProps<FileList, HTMLElement>': input, meta

Type 'FileList' is missing the following properties from type 'readonly string[]': concat, join, slice, indexOf, and 16

Capture

DevMammadov avatar Feb 09 '21 08:02 DevMammadov

Hi @DevMammadov, I guess the types changed since I first posted the snippet. I just updated this issue's description for you, with a sandbox link, let me know :)

antoinerousseau avatar Feb 09 '21 21:02 antoinerousseau

How could I send text form data alongside the file? EDIT: I managed to do it by appending a json string to the payload payload.append('form', JSON.stringify(data));

What would be a better way to do it? I need to post the form data and the file to an Express API

LetrixZ avatar Jul 06 '21 23:07 LetrixZ

@LetrixZ in my example, I'm creating a FormData and adding the file to it with .append(), but you can also add text the same way, e.g.:

const payload = new FormData()
payload.append("file", values.files[0])
payload.append("firstname", "Antoine")
payload.append("lastname", "Rousseau")

await fetch(YOUR_URI, {
  method: "POST",
  body: payload, // this sets the `Content-Type` header to `multipart/form-data`
})

I've updated the description to include this

antoinerousseau avatar Jul 07 '21 09:07 antoinerousseau

I was relying on touched to show error state, which wasn't updating. My fix was to also pull out onBlur from the input props, and also call it inside onChange:

onChange={({ target }) => {
    onChange(target.files);
    onBlur(target);
}}

karlshea avatar Dec 03 '21 22:12 karlshea

Hey I am trying to implement it in js and I get this error: Failed to set the 'value' property on 'HTMLInputElement': This input element accepts a filename, which may only be programmatically set to the empty string.

AnthonyGriffithG avatar Jan 01 '22 21:01 AnthonyGriffithG

@AnthonyGriffith please share your code with a reproducible issue using a codesandbox/codepen link.

antoinerousseau avatar Jan 03 '22 10:01 antoinerousseau

I have same error like @AnthonyGriffith, here is my Example:

import { Field, useField } from 'react-final-form';

...
const { input } = useField('file');

const onChange = ({ target }: { target: HTMLInputElement }) => {
    input.onChange(target.files[0]);
    input.onBlur(target);
  };

<Field
        name="file"
        type="file"
        component="input"
        onChange={onChange}
      />
...      

And my error is: Uncaught DOMException: Failed to set the 'value' property on 'HTMLInputElement': This input element accepts a filename, which may only be programmatically set to the empty string.

Update: Example from @antoinerousseau above works great

xelaz avatar Jan 04 '22 10:01 xelaz

lol why hasn't this been added yet

Vandivier avatar Jan 19 '24 19:01 Vandivier