New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example for file Upload #71

Closed
sebas5384 opened this Issue Sep 15, 2015 · 52 comments

Comments

@sebas5384

sebas5384 commented Sep 15, 2015

Hey! awesome work with redux-form!

I'm using it at production, and I'm going to use it for file inputs, but I would appreciate an example or ideia of how it would be done with redux-form.

thanks!

@BBB

This comment has been minimized.

Contributor

BBB commented Sep 18, 2015

As far as I know, this is how:

  1. It's important to note that <input type="file" /> Doesn't support the setting of values, so if you want to bind the values back to something you'll need to write a custom "selected files" component.
  2. You need to do some parsing of the event before calling fields.yourField.handleChange(). Something like this:
<input
  type="file"
  onChange={
    ( e ) => {      
      e.preventDefault();
      const { fields } = this.props;
      // convert files to an array
      const files = [ ...e.target.files ];
      fields.yourField.handleChange(files);
    }
  }
/>
  1. Do your validation in the normal way

Hope that helps.

@erikras: What do you think about updating getValue to support e.target.files? https://github.com/erikras/redux-form/blob/master/src/reduxForm.js#L28

erikras added a commit that referenced this issue Sep 18, 2015

@erikras erikras closed this in ad8f1a1 Sep 18, 2015

@sebas5384

This comment has been minimized.

sebas5384 commented Sep 21, 2015

@BBB thanks for the help! I finally did it using the https://github.com/paramaggarwal/react-dropzone

@erikras great patch! I'll use it in later integrations with other components ;)

@lemonCMS

This comment has been minimized.

lemonCMS commented Nov 3, 2015

I have got this working in all current browsers, but IE 9 just return undefined, perhaps for older browsers you could return the field value?

@erikras

This comment has been minimized.

Owner

erikras commented Nov 5, 2015

@lemonCMS You mean like changing this:

if (type === 'file') {
  return files || dataTransfer && dataTransfer.files;
}

to this:

if (type === 'file') {
  return files || dataTransfer && dataTransfer.files || value;
}

??

@lemonCMS

This comment has been minimized.

lemonCMS commented Nov 5, 2015

Yes :)

@edorivai

This comment has been minimized.

edorivai commented Nov 10, 2015

I'm kinda struggling getting this to work. I'm under the impression I'm using the file input wrongly.

My initial attempt was to bind the same way I would bind any other input:

<input type='file' {...image} />

But as @BBB points out, React complains that value cannot be set programatically. Second try; handle the change manually (after @BBB's suggestion):

handleFile(fieldName, event) {
    event.preventDefault();
    const { fields } = this.props;
    // convert files to an array
    const files = [ ...event.target.files ];
    fields[fieldName].handleChange(files);
},
render() {
    (...)
    <input type='file' onChange={this.handleFile.bind(this, 'image')} />
    (...)
}

But handleChange is not defined on the field. Am I completely misunderstanding the suggestion, or has the API changed since the suggestion was made?

@lemonCMS

This comment has been minimized.

lemonCMS commented Nov 10, 2015

I am not sure, but i think i had it working as following.

const props = this.props.fields[field.name];

return (
<input
        type="file"
        onDrop={props.onDrop}
        onChange={props.onChange}
        onFocus={props.onFocus}
        onUpdate={props.onUpdate}
        />
);

And then the filelist will be send to your onSubmit function.

@erikras

This comment has been minimized.

Owner

erikras commented Nov 10, 2015

Yes, what @lemonCMS said should work. Although it could just be <input type="file" {...field}/>. 😄

I can't guarantee onDrop will work. Drag and drop works with text between fields. I have not tested with a file input.

@edorivai

This comment has been minimized.

edorivai commented Nov 10, 2015

Tried @lemonCMS' approach; changed

fields[fieldName].handleChange(files);

to

fields[fieldName].onChange(files);

which works.

The <input type="file" {...field} /> approach definitely throws an error!

@erikras

This comment has been minimized.

Owner

erikras commented Nov 10, 2015

Yes. handleChange went extinct in v3.0. 👍

@duro

This comment has been minimized.

duro commented Nov 11, 2015

@edorivai, @lemonCMS, @sebas5384: Do you guys have any examples of your approach to using file fields with redux-form? Especially if you were able to get it to work with a Dropzone component.

@edorivai

This comment has been minimized.

edorivai commented Nov 16, 2015

@duro Haven't tried myself, but I think the following would work:

<Dropzone onDrop={this.props.fields.myFileField.onChange}>
        <div>Try dropping some files here, or click to select files to upload.</div>
</Dropzone>
@austinmao

This comment has been minimized.

austinmao commented Dec 1, 2015

+1 does anyone have an example of redux-form + dropzone?

@kitze

This comment has been minimized.

kitze commented Jan 18, 2016

@austinmao have you found an example maybe? :)

@austinmao

This comment has been minimized.

austinmao commented Jan 18, 2016

Nope :(

@BBB

This comment has been minimized.

Contributor

BBB commented Jan 19, 2016

@duro @austinmao @kitze I've put together a simple example of using dropzone with redux-form. http://ollie.relph.me/blog/redux-form-and-dropzone-react-example/ Any questions, just post an issue on the repo!

@shatran

This comment has been minimized.

shatran commented Feb 28, 2016

Did anyone had an experience with asyncValidate and Dropzone?
I'm trying to upload a file using react-dropzone and async validate a successful/unsuccessful file upload. The problem is i can't get asyncBlurFields to work with react-dropzone, and calling asyncValidate directly inside onChange doesn't work because 'values' object in asyncValidate is outdated and don't contain the file i want to upload. Any ideas?
Thanks!

@serranoarevalo

This comment has been minimized.

serranoarevalo commented Apr 28, 2016

I don't know if this helps anybody but this approach: <input type="file" {...field} /> didn't threw an error when I specified the value={null} inside of it like so:

<input type="file" {...profilePic} value={null} />

@erikras

This comment has been minimized.

Owner

erikras commented Apr 28, 2016

@serranoarevalo That is, in effect, converting it back to an uncontrolled input, which might be necessary for file inputs, I don't know. The React docs are light on that corner of the DOM.

@mikethejet

This comment has been minimized.

mikethejet commented Jun 3, 2016

where is the binary content of fole i can save for example to rethinkDB using r.binary ? (if i am using react-dropzone in redux) there is not any field that holds the data of file.. ?

@GuillaumeCisco

This comment has been minimized.

GuillaumeCisco commented Jul 20, 2016

Hello, is there a simple way to use Dropzone with the new api Field, I'm struggling with the onChange binding :/ As we cannot access this.props.fields anymore. Using a ref ?

Thank you,

@GuillaumeCisco

This comment has been minimized.

GuillaumeCisco commented Jul 21, 2016

Hello everybody.
For interested people, I managed to make it work with:

<Field name="picture" component={props =>
    <Dropzone
       {...props.input}
        multiple={false}
        style={dropzoneStyle}
        onDrop={(filesToUpload) => {
         this.files = filesToUpload;
         return props.input.onChange(filesToUpload);
         }}
    >
         <div>Try dropping a file here, or click to select file to upload.</div>
    </Dropzone>
    } type="file"/>
    {this.files &&
    <div>
        {this.files.map((file, i) => <span key={i}>{file.name}</span>)}
    </div>
     }

For information files is an array declared in my component as : files: [].
If you don't need to display current selected files, you can just use onDrop = props.input.onChange
Documentation should handle this behavior.

Hope it will helps ;)

@carpeliam

This comment has been minimized.

carpeliam commented Aug 11, 2016

@BBB's earliest comment here works for me (on redux-form@5.3.1 and react@15.1), save for one issue: the first time we select a file, the store is updated with the new form value and the component is rendered with the appropriate value; in subsequent changes to the file input, the store is updated properly and the component is rerendered but always receives the first file we chose.

I've uploaded a minimal example to http://carpeliam.github.io/redux-form-file-input-example, with code at https://github.com/carpeliam/redux-form-file-input-example.

@carpeliam

This comment has been minimized.

carpeliam commented Aug 15, 2016

@erikras not sure if you saw the above comment - should I file a separate issue? Not sure if 5.3.1 is supposed to work with React 15.1 or not.

@shawnmclean

This comment has been minimized.

shawnmclean commented Sep 2, 2016

How does the new <Field> look with files type? Do I need my own file component? I can't seem to do type="file"

@BBB

This comment has been minimized.

Contributor

BBB commented Sep 6, 2016

https://github.com/BBB/dropzone-redux-form-example Has now been updated to work with v6. @shawnmclean @jckdrpr You might find it helpful.

@sherubthakur

This comment has been minimized.

sherubthakur commented Sep 7, 2016

Hey @BBB I tried out the example on the repo linked in your answer and I added redux-immutable-state-invariant to the project and I am getting the same error. Has no one else faced this issue?

Well it does work when there is no redux-immutable-state-invariant.

@asiniy

This comment has been minimized.

asiniy commented Oct 4, 2016

My take

import React from 'react'

class FileInput extends React.Component {
  constructor(props) {
    super(props)
    this.onChange = this.onChange.bind(this)
  }

  onChange(e) {
    const { input: { onChange } } = this.props
    onChange(e.target.files[0])
  }

  render() {
    const { input: { value } } = this.props

    return (<input
      type="file"
      value={value}
      onChange={this.onChange}
    />)
  }
}

export default FileInput

and then

import { Field } from 'redux-form'

<Field
  type="file"
  name="poster"
  component={FileInput}
/>

Works awesome!

@SepiaGroup

This comment has been minimized.

SepiaGroup commented Oct 20, 2016

thanks to @BBB i came up with the following

import React from 'react';
import Dropzone from 'react-dropzone';

const ReduxFormDropzone = (field) => {
    let {
        input,
        meta,
        dropzoneOnDrop,
        ...props
    } = field;

    return (
        <Dropzone
            onDrop={(acceptedFiles, rejectedFiles, e) => {
                field.input.onChange(acceptedFiles);
                field.dropzoneOnDrop && field.dropzoneOnDrop(acceptedFiles, rejectedFiles, e);
            }}
            {...props}
        />
    );
}

export default ReduxFormDropzone;

then you just need to do...

<Field
    name={"files"}
    component={ReduxFormDropzone}
    style={style.dropzone}
    multiple={false}
    dropzoneOnDrop={this.handleDrop}
/>
  • on the Field component, to use the Dropzone onDrop you have to use dropzoneOnDrop because Field has a OnDrop
@coder4affine

This comment has been minimized.

coder4affine commented Dec 8, 2016

@BBB I am getting the same error as @jckdrpr . Please suggest some solution for same.

browser.js?9520:40 Uncaught Error: A state mutation was detected between dispatches, in the path form.SkillForm.values.files.0.lastModifiedDate. This may cause incorrect behavior.

@jckdrpr Have you find any solution for above mention error?

@aindong

This comment has been minimized.

aindong commented Feb 16, 2017

@BBB your example returns empty formdata and the server cant find the payload. Any idea?

@hemedani

This comment has been minimized.

hemedani commented Mar 4, 2017

@asiniy did not work with v6 throw this error:

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.
    at Object.updateWrapper (webpack:///../~/react-dom/lib/ReactDOMInput.js?:156:20)
    at ReactDOMComponent.forceUpdateIfMounted (webpack:///../~/react-dom/lib/ReactDOMInput.js?:34:19)
    at CallbackQueue.notifyAll (webpack:///../~/react-dom/lib/CallbackQueue.js?:76:22)
    at Object.flushBatchedUpdates (webpack:///../~/react-dom/lib/ReactUpdates.js?:180:13)
    at ReactDefaultBatchingStrategyTransaction.closeAll (webpack:///../~/react-dom/lib/Transaction.js?:206:25)
    at ReactDefaultBatchingStrategyTransaction.perform (webpack:///../~/react-dom/lib/Transaction.js?:153:16)
    at Object.batchedUpdates (webpack:///../~/react-dom/lib/ReactDefaultBatchingStrategy.js?:62:26)
    at Object.batchedUpdates (webpack:///../~/react-dom/lib/ReactUpdates.js?:97:27)
    at dispatchEvent (webpack:///../~/react-dom/lib/ReactEventListener.js?:147:20)
@potty

This comment has been minimized.

potty commented Mar 23, 2017

My solution based on #2532 (comment). I don't know how hacky is it, but works for me:

import React from 'react'

export const FileInput = ({ input, resetKey }) => {
	const { value, ...inputProps } = input

	const handleChange = (e) => {
		input.onChange(e.target.files[0])
	}

	return (
		<input {...inputProps} key={resetKey} type="file" onChange={handleChange} onBlur={() => {}} />
	)
}
<Field name="image" component={FileInput} onChange={this.handleFileChange} />

I'm saving data URI from image cropper as value of the field. The problem was that onBlur was resetting field value to FileList object, so I had to disable it.

@jeserodz

This comment has been minimized.

jeserodz commented Apr 6, 2017

What worked in my case:

  1. Create a functional component that returns an <input type="file"/> component.
  2. Delete the value property from field.input in the functional component
  3. Use the functional component in <Field component={} />
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';

/** 
 * File input workarround:
 * More info: http://redux-form.com/5.2.5/#/examples/file?_k=57hmlw
 */
const customFileInput = (field) => {
  delete field.input.value; // <-- just delete the value property
  return <input type="file" id="file" {...field.input} />;
};

class SubmitVideoForm extends Component {
  render() {
    return (    
        <form className="submit-video-form">
          
          <Field 
            name="file"
            type="file"
            component={customFileInput}/>

          <input type="submit" value="Submit your video"/>
        </form>
    );
  }
}

SubmitVideoForm = reduxForm({
  form: 'submitVideoForm'
})(SubmitVideoForm);

export default SubmitVideoForm;

You can test this by querying the file input field's FileList length:

document.querySelector("#file").files.length;
@rmontes13

This comment has been minimized.

rmontes13 commented Apr 13, 2017

Just one thing to point in the answer from @asiniy, just remove the value attribute form the returned input to avoid 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."

    return (<input
      type="file"
      onChange={this.onChange}
    />)

More information http://redux-form.com/5.2.5/#/examples/file?_k=z12d2f

@vitalykorneev

This comment has been minimized.

vitalykorneev commented May 10, 2017

Hi. I have same problem like #71 (comment)
Is there a way to solve the problem?

@LulzAugusto

This comment has been minimized.

LulzAugusto commented Jun 15, 2017

I'm having the same problem as @sherubthakur mentioned with redux-immutable-state-invariant. Any solutions?

@rewop

This comment has been minimized.

rewop commented Jun 27, 2017

About the problem from #71 (comment) #71 (comment), I found out that deep-equal used in this condition, returns true (equal) in the second part of the condition, given two different instances of File object.

@rewop

This comment has been minimized.

rewop commented Jun 27, 2017

A work around for #71 (comment) is to not return the File object as value to the onChange of the field, but to return an object like:

{
    file: e.target.files[0],
    name: e.target.files[0].name, 
}

As long as the name is different, the value will be updated in the store as well as the validation will be performed. As a fix I suggest to reimplement the deep-equal function.

@pacozaa

This comment has been minimized.

pacozaa commented Jul 4, 2017

@SepiaGroup You are the man!

Do you manage to do the image preview as well?

@SepiaGroup

This comment has been minimized.

SepiaGroup commented Jul 4, 2017

@pacozaa glad you found it helpful.

preview should work, what seems to be the issue?

@pacozaa

This comment has been minimized.

pacozaa commented Jul 5, 2017

@SepiaGroup
Thank you again!
I forgot they are array value because it assume everything there are multiple files.
So I solve my problem just add [0] behind variable!

Anyone who face the same problem please don't forget to add [0] like this
file[0]

Again use @SepiaGroup trick works like a charm.

@lupitadavila

This comment has been minimized.

lupitadavila commented Aug 24, 2017

@LulzAugusto I have the same issues as you. Did you ever find a solution?

@LulzAugusto

This comment has been minimized.

LulzAugusto commented Sep 21, 2017

@lupitadavila Oh sorry for such delay in answering you (damn github notifications are such mess). About your question, I didn't find a solution, but I've updated redux-immutable-state-invariant and I'm now using their ignore parameter to pretend that the issue doesn't exist anymore. 😂

@JohnHour89

This comment has been minimized.

JohnHour89 commented Dec 20, 2017

@GuillaumeCisco hey, I have tried your method mentioned at here #71 (comment) it seems work but may I know how to get the url from it? Because the results returned it is always show as [object FileList] as shown in the screenshot below:

screen shot 2017-12-20 at 8 59 42 am

I just want to get the url from it, thank you

@GuillaumeCisco

This comment has been minimized.

GuillaumeCisco commented Dec 20, 2017

Hey @JohnHour89 , please understand how FileList works https://developer.mozilla.org/en-US/docs/Web/API/FileList
It is just an array of objects.
If you want to create an url from a File object, you can use data or blob
https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL

Hope it will help

@nguyennb9

This comment has been minimized.

nguyennb9 commented Mar 30, 2018

I have a form which using onSubmit of the Form:
<Form onSubmit={handleSubmit(this.onSubmit)}>

The function onSubmit can only capture the string, number... but not the file

  onSubmit(formData) {
   
  }

The examples above using onChange method to capture the file, but I want to use onSubmit, any suggestion?

@tini2n

This comment has been minimized.

tini2n commented Apr 11, 2018

Have problem with save in store, file from input[type="file"] 😓
I am using Fields Array (<FieldArray name="companyCertificates" component={InputFile} />) to generate new inputs after adding file. After input onChange I trying to save e.target.files[0] to store through input.onChange(e.target.files[0]), but Redux Inspector is showing me empty object.

Maybe I use not correct method to save or Redux Inspector don't show file value?

This my InputFile component:

import React, { Component } from 'react';
import { Field } from 'redux-form';

const InputField = ({ input, fields, index }) => {
    delete input.value;

    const fieldAdd = (e, fields) => {

        if (e.target.files[0]) {
            fields.push();
            input.onChange(e.target.files[0])
        }
    };

    return (
        <input type="file"
               {...input}
               onChange={(e) => {
                   e.preventDefault();

                   fieldAdd(e, fields, input);
               }}
               accept=".pdf" />
    )
};

export default class InputFile extends Component {
    constructor (props) {
        super(props);
    };

    componentWillMount () {
        let { fields } = this.props;

        if (fields.length === 0)
            fields.insert(0, null);
    };

    render () {
        let { fields } = this.props;

        return (
            <div className="input-group input-file">
                <div className="files">
                    {fields.map((name, index) => {
                        return (
                            <Field name={name}
                                   key={index}
                                   index={index}
                                   fields={fields}
                                   component={InputField} />
                        )
                    })}
                </div>
            </div>
        )
    }
};

Could smbdy help me with saving this file in store to send it later?
Thanx a lot!

@GuillaumeCisco

This comment has been minimized.

GuillaumeCisco commented Apr 11, 2018

@tini2n the problem is in this part

// should be a Class
const fieldAdd = (e, fields) => {

        if (e.target.files[0]) {
            // you are pushing nothing
            fields.push();

            // input is unknown
            input.onChange(e.target.files[0])
        }
    };

    return (
        <input type="file"
               {...input}
               onChange={(e) => {
                   e.preventDefault();

                   // passing input parameter but not using it
                   fieldAdd(e, fields, input);
               }}
               accept=".pdf" />
    )
@tini2n

This comment has been minimized.

tini2n commented Apr 11, 2018

@GuillaumeCisco thanx for reply!)

fields.push() is FieldsArray's method to add new input elements. Seems that it working well.
input is known as input element. Below console.log of it
image

I checked Diff of values and saw that value in store is replaced by empty object:
image

@GuillaumeCisco

This comment has been minimized.

GuillaumeCisco commented Apr 11, 2018

@tini2n Your code is too messy. Please clean it, use a Class for InputField and declare correctly your functions and parameters.
There is no declaration of FieldArray in your sample code.
Furthermore, you are pushing nothing, see the doc about FieldArray's push method:

fields.push(value:Any) : Function
Adds a value to the end of the array. Returns nothing.

This is not a mutator; it dispatches an action which updates the state in Redux, which will cause your component to rerender.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment