Как загрузить файл с помощью redux-формы?

Я не могу получить правильное значение в магазине при попытке загрузить файл. Вместо содержимого файла я получаю что-то вроде { 0: {} }. Вот код:

const renderInput = field => (
  <div>
    <input {...field.input} type={field.type}/>
    {
      field.meta.touched &&
      field.meta.error &&
      <span className={styles.error}>{field.meta.error}</span>
    }
  </div>
);

render() {

  ...

  <form className={styles.form} onSubmit={handleSubmit(submit)}>
    <div className={styles.interface}>
      <label>userpic</label>
      <Field
        name="userpic"
        component={renderInput}
        type="file"
      />
    </div>
    <div>
      <button type="submit" disabled={submitting}>Submit</button>
    <div>
  </form>

  ...

}

Все примеры в сети, которые я нашел, были сделаны с использованием редукционной формы v5.

Как мне ввести файл в redux-form v6?


person Mike Doudkin    schedule 26.09.2016    source источник


Ответы (8)


Создайте компонент поля, например:

import React, {Component} from 'react'

export default class FieldFileInput  extends 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
    const {input,label, required, meta, } = this.props  //whatever props you send to the component from redux-form Field
    return(
     <div><label>{label}</label>
     <div>
       <input
        type='file'
        accept='.jpg, .png, .jpeg'
        onChange={this.onChange}
       />
     </div>
     </div>
    )
}
}

Передайте этот компонент компоненту Field там, где вам нужно. Нет необходимости в дополнительных Dropzone или других библиотеках, если вам нужна простая функция загрузки файлов.

person bh4r4th    schedule 26.10.2017
comment
Ваш ответ должен быть принятым ответом, поскольку это более простой способ заставить его работать с redux-form без какой-либо другой зависимости. : +1: - person edgarjs; 20.03.2018
comment
@OwlyMoly Uncaught TypeError: Cannot read property 'onChange' of undefined - person shorif2000; 16.04.2018
comment
@ Shortif2000: Странно, у меня это работает. Вы пробовали передать метод изменения дескриптора этому полю как onChange опору для поля. Пример: `‹ Имя поля = fileUpload label = Загрузить новый снимок экрана type = file component = {FieldFileInput} onChange = {handleChange} / ›` - person bh4r4th; 17.04.2018
comment
@ Shortif2000 Триггер change с этим handleChange для обработки изменения значения поля при каждом изменении загруженного файла. Надеюсь, это решит вашу проблему. - person bh4r4th; 17.04.2018
comment
Это вызывает ошибку в консоли A non-serializable value was detected in an action, in the path: payload. Value:. Он работает, но распечатывает это, есть идеи? - person John; 07.02.2019
comment
@John Это ошибка или предупреждение? Я думаю, это должно быть предупреждение со стороны магазина redux. Путь не является сериализованными данными при сохранении в магазине в вашем случае. проверьте сериализованный тип значения, которое он может ожидать, затем десериализуйте ввод в строку / все, что ожидается. Простой способ - опробовать его в методе обработчика onChange. - person bh4r4th; 08.02.2019

Мой пример обертки ввода формы redux с Dropzone

import React, {Component, PropTypes} from 'react';
import Dropzone from 'react-dropzone';
import { Form } from 'elements';
import { Field } from 'redux-form';

class FileInput extends Component {
  static propTypes = {
    dropzone_options: PropTypes.object,
    meta: PropTypes.object,
    label: PropTypes.string,
    classNameLabel: PropTypes.string,
    input: PropTypes.object,
    className: PropTypes.string,
    children: PropTypes.node,
    cbFunction: PropTypes.func,
  };

  static defaultProps = {
    className: '',
    cbFunction: () => {},
  };

  render() {
    const { className, input: { onChange }, dropzone_options, meta: { error, touched }, label, classNameLabel, children, name, cbFunction } = this.props;

    return (
      <div className={`${className}` + (error && touched ? ' has-error ' : '')}>
        {label && <p className={classNameLabel || ''}>{label}</p>}
        <Dropzone
          {...dropzone_options}
          onDrop={(f) => {
            cbFunction(f);
            return onChange(f);
          }}
          className="dropzone-input"
          name={name}
        >
          {children}
        </Dropzone>
        {error && touched ? error : ''}
      </div>
    );
  }
}
export default props => <Field {...props} component={FileInput} />;

Горячо использовать это:

<FileInput
 name="add_photo"
 label="Others:"
  classNameLabel="file-input-label"
  className="file-input"
  dropzone_options={{
    multiple: false,
    accept: 'image/*'
  }}
>
  <span>Add more</span>
</FileInput>
person Iurii Budnikov    schedule 06.10.2016
comment
Я считаю, что файл, полученный функцией onDrop, необходимо прочитать с помощью FileReader, прежде чем его можно будет отправить на сервер. Иначе это бесполезно. Если это поможет полностью ответить на исходный вопрос, я мог бы привести пример здесь. - person Pim Heijden; 27.09.2017

Другой способ сделать это, который будет отображать изображение предварительного просмотра (в приведенном ниже примере используется синтаксис React 16+ и принимает только один файл изображения для отправки в API; однако, с некоторыми незначительными настройками, он также может масштабироваться до нескольких изображений и других полей. входы):

Рабочий пример: https://codesandbox.io/s/m58q8l054x

Рабочий пример (устаревший): https://codesandbox.io/s/8kywn8q9xl


До:

введите описание изображения здесь

После:

введите описание изображения здесь


контейнеры / UploadForm.js

import React, { Component } from "react";
import { Form, Field, reduxForm } from "redux-form";
import DropZoneField from "../components/dropzoneField";

const imageIsRequired = value => (!value ? "Required" : undefined);

class UploadImageForm extends Component {
  state = { imageFile: [] };

  handleFormSubmit = formProps => {
    const fd = new FormData();
    fd.append("imageFile", formProps.imageToUpload.file);
    // append any additional Redux form fields
    // create an AJAX request here with the created formData

    alert(JSON.stringify(formProps, null, 4));
  };

  handleOnDrop = (newImageFile, onChange) => {
    const imageFile = {
      file: newImageFile[0],
      name: newImageFile[0].name,
      preview: URL.createObjectURL(newImageFile[0]),
      size: newImageFile[0].size
    };

    this.setState({ imageFile: [imageFile] }, () => onChange(imageFile));
  };

  resetForm = () => this.setState({ imageFile: [] }, () => this.props.reset());

  render = () => (
    <div className="app-container">
      <h1 className="title">Upload An Image</h1>
      <hr />
      <Form onSubmit={this.props.handleSubmit(this.handleFormSubmit)}>
        <Field
          name="imageToUpload"
          component={DropZoneField}
          type="file"
          imagefile={this.state.imageFile}
          handleOnDrop={this.handleOnDrop}
          validate={[imageIsRequired]}
        />
        <button
          type="submit"
          className="uk-button uk-button-primary uk-button-large"
          disabled={this.props.submitting}
        >
          Submit
        </button>
        <button
          type="button"
          className="uk-button uk-button-default uk-button-large"
          disabled={this.props.pristine || this.props.submitting}
          onClick={this.resetForm}
          style={{ float: "right" }}
        >
          Clear
        </button>
      </Form>
      <div className="clear" />
    </div>
  );
}

export default reduxForm({ form: "UploadImageForm" })(UploadImageForm);

components / dropzoneField.js

import React from "react";
import PropTypes from "prop-types";
import DropZone from "react-dropzone";
import ImagePreview from "./imagePreview";
import Placeholder from "./placeholder";
import ShowError from "./showError";

const DropZoneField = ({
  handleOnDrop,
  input: { onChange },
  imagefile,
  meta: { error, touched }
}) => (
  <div className="preview-container">
    <DropZone
      accept="image/jpeg, image/png, image/gif, image/bmp"
      className="upload-container"
      onDrop={file => handleOnDrop(file, onChange)}
    >
      {({ getRootProps, getInputProps }) =>
        imagefile && imagefile.length > 0 ? (
          <ImagePreview imagefile={imagefile} />
        ) : (
          <Placeholder
            error={error}
            touched={touched}
            getInputProps={getInputProps}
            getRootProps={getRootProps}
          />
        )
      }
    </DropZone>
    <ShowError error={error} touched={touched} />
  </div>
);

DropZoneField.propTypes = {
  error: PropTypes.string,
  handleOnDrop: PropTypes.func.isRequired,
  imagefile: PropTypes.arrayOf(
    PropTypes.shape({
      file: PropTypes.file,
      name: PropTypes.string,
      preview: PropTypes.string,
      size: PropTypes.number
    })
  ),
  label: PropTypes.string,
  onChange: PropTypes.func,
  touched: PropTypes.bool
};

export default DropZoneField;

components / imagePreview.js

import React from "react";
import PropTypes from "prop-types";

const ImagePreview = ({ imagefile }) =>
  imagefile.map(({ name, preview, size }) => (
    <div key={name} className="render-preview">
      <div className="image-container">
        <img src={preview} alt={name} />
      </div>
      <div className="details">
        {name} - {(size / 1024000).toFixed(2)}MB
      </div>
    </div>
  ));

ImagePreview.propTypes = {
  imagefile: PropTypes.arrayOf(
    PropTypes.shape({
      file: PropTypes.file,
      name: PropTypes.string,
      preview: PropTypes.string,
      size: PropTypes.number
    })
  )
};

export default ImagePreview;

components / placeholder.js

import React from "react";
import PropTypes from "prop-types";
import { MdCloudUpload } from "react-icons/md";

const Placeholder = ({ getInputProps, getRootProps, error, touched }) => (
  <div
    {...getRootProps()}
    className={`placeholder-preview ${error && touched ? "has-error" : ""}`}
  >
    <input {...getInputProps()} />
    <MdCloudUpload style={{ fontSize: 100, paddingTop: 85 }} />
    <p>Click or drag image file to this area to upload.</p>
  </div>
);

Placeholder.propTypes = {
  error: PropTypes.string,
  getInputProps: PropTypes.func.isRequired,
  getRootProps: PropTypes.func.isRequired,
  touched: PropTypes.bool
};

export default Placeholder;

components / showError.js

import React from "react";
import PropTypes from "prop-types";
import { MdInfoOutline } from "react-icons/md";

const ShowError = ({ error, touched }) =>
  touched && error ? (
    <div className="error">
      <MdInfoOutline
        style={{ position: "relative", top: -2, marginRight: 2 }}
      />
      {error}
    </div>
  ) : null;

ShowError.propTypes = {
  error: PropTypes.string,
  touched: PropTypes.bool
};

export default ShowError;

styles.css

img {
  max-height: 240px;
  margin: 0 auto;
}

.app-container {
  width: 500px;
  margin: 30px auto;
}

.clear {
  clear: both;
}

.details,
.title {
  text-align: center;
}

.error {
  margin-top: 4px;
  color: red;
}

.has-error {
  border: 1px dotted red;
}

.image-container {
  align-items: center;
  display: flex;
  width: 85%;
  height: 80%;
  float: left;
  margin: 15px 10px 10px 37px;
  text-align: center;
}

.preview-container {
  height: 335px;
  width: 100%;
  margin-bottom: 40px;
}

.placeholder-preview,
.render-preview {
  text-align: center;
  background-color: #efebeb;
  height: 100%;
  width: 100%;
  border-radius: 5px;
}

.upload-container {
  cursor: pointer;
  height: 300px;
}
person Matt Carlotta    schedule 25.10.2017
comment
Спасибо за код, но это не будет работать с последней версией react-dropzone. Обязательно используйте "react-dropzone": "5.0.1" в своих зависимостях. - person Fr4nz; 19.02.2019
comment
@ Fr4nz - Обновлены все зависимости до последней версии и добавлен новый код. - person Matt Carlotta; 19.02.2019

Мне удалось сделать это с помощью redux-form в material-ui, обертывающем TextField следующим образом:

Редактирование B4:

перед редактированием

После редактирования:

после редактирования

 <Field name="image" component={FileTextField} floatingLabelText={messages.chooseImage} fullWidth={true} />

с компонентом, определенным как:

const styles = {
  button: {
    margin: 12
  },
  exampleImageInput: {
    cursor: 'pointer',
    position: 'absolute',
    top: 0,
    bottom: 0,
    right: 0,
    left: 0,
    width: '100%',
    opacity: 0
  },
  FFS:{
    position: 'absolute',
    lineHeight: '1.5',
    top: '38',
    transition: 'none',
    zIndex: '1',
    transform: 'none',
    transformOrigin: 'none',
    pointerEvents: 'none',
    userSelect: 'none',
    fontSize: '16',
    color: 'rgba(0, 0, 0, 0.8)',
  }
};

export const FileTextField  = ({
                                  floatingLabelText,
                                  fullWidth,
                                  input,
                                  label,
                                  meta: { touched, error },
                                  ...custom })=>{
  if (input.value && input.value[0] && input.value[0].name) {
    floatingLabelText = input.value[0].name;
  }
  delete input.value;
  return (
    <TextField
      hintText={label}
      fullWidth={fullWidth}
      floatingLabelShrinkStyle={styles.FFS}
      floatingLabelText={floatingLabelText}
      inputStyle={styles.exampleImageInput}
      type="file"
      errorText={error}
      {...input}
      {...custom}
    />
  )
}
person Amihay    schedule 09.06.2018

Если вам нужна кодировка base64 для отправки на ваш сервер, вот модифицированная версия, которая сработала для меня:

export class FileInput extends React.Component {

  getBase64 = (file) => {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = () => resolve(reader.result);
      reader.onerror = error => reject(error);
    });
  }

  onFileChange = async (e) => {
    const { input } = this.props
    const targetFile = e.target.files[0]
    if (targetFile) {
      const val = await this.getBase64(targetFile)
      input.onChange(val)
    } else {
      input.onChange(null)
    }
  }

  render() {

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

Тогда ваш компонент поля будет выглядеть так:

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

person Will    schedule 31.05.2018
comment
Как удалить пример ссылки на файл большого двоичного объекта Data: application / pdf; base64, docBlob: data: application / pdf; base64, JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PC9Qcm9kdWNlcihEeW5hbWljUERGIGZvciAuTLjCUIH - person Bharath Mb; 22.04.2020

Для React> = 16 и ReduxForm> = 8 (протестированная версия - 16.8.6 для React и 8.2.5) работает следующий компонент.

(Решение опубликовано DarkBitz в связанной проблеме GitHub)

const adaptFileEventToValue = delegate => e => delegate(e.target.files[0]);

const FileInput = ({ 
  input: { value: omitValue, onChange, onBlur, ...inputProps }, 
  meta: omitMeta, 
  ...props 
}) => {
  return (
    <input
      onChange={adaptFileEventToValue(onChange)}
      onBlur={adaptFileEventToValue(onBlur)}
      type="file"
      {...props.input}
      {...props}
    />
  );
};

export const FileUpload = (props) => {
    const { handleSubmit } = props;
    const onFormSubmit = (data) => {
        console.log(data);
    }
    return (
          <form onSubmit={handleSubmit(onFormSubmit)}>
            <div>
              <label>Attachment</label>
              <Field name="attachment" component={FileInput} type="file"/>
            </div>
            <button type="submit">Submit</button>
          </form>
    )
}
person fandasson    schedule 28.08.2019
comment
Я получаю это, когда использую ваше решение: `` Ошибка: ошибка инварианта: между отправками в форме пути обнаружена мутация состояния.UploadImageForm.values.attachment.lastModifiedDate ''. - person Lucjan Grzesik; 24.06.2020

С Redux Form

const { handleSubmit } = props;
  //make a const file to hold the file prop.
  const file = useRef();
  
  // create a function to replace the redux-form input-file value to custom value.
  const fileUpload = () => {
  // jsx to take file input
  // on change store the files /file[0] to file variable
    return (
      <div className='file-upload'>
        <input
          type='file'
          id='file-input'
          accept='.png'
          onChange={(ev) => {
            file.current = ev.target.files;
          }}
          required
        />
      </div>
    );
  };

  //catch the redux-form values!
  //loop through the files and add into formdata
  //form data takes key and value
  //enter the key name as multer-config fieldname
  //then add remaining data into the formdata
  //make a request and send data.
  const onSubmitFormValues = (formValues) => {
    const data = new FormData();
    for (let i = 0; i < file.current.length; i++) {
      data.append("categoryImage", file.current[i]);
    }

    data.append("categoryName", formValues.categoryName);
  Axios.post("http://localhost:8080/api/v1/dev/addNewCategory", data)
      .then((response) => console.log(response))
      .catch((err) => console.log(err));
  };
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

person Praveen Nagaraj    schedule 25.08.2020

Вы также можете использовать для этой цели react-dropzone. Приведенный ниже код отлично работал у меня

filecomponent.js

import React from 'react'
import { useDropzone } from 'react-dropzone'
function MyDropzone(props) {

    const onDrop = (filesToUpload) => {
        return props.input.onChange(filesToUpload[0]);
    }

    const onChange = (filesToUpload) => {
        return props.input.onChange(filesToUpload[0]);
    }

    const { getRootProps, getInputProps } = useDropzone({ onDrop });
    return (
         <div {...getRootProps()}>
          <input {...getInputProps()} onChange={e => onChange(e.target.files)} />
          <p> Drop or select yout file</p>
         </div>
        )
}

export default MyDropzone;

Используйте это в форме

     <Field
       name="myfile"
       component={renderFile}
     />
person Mohamed Ismail    schedule 27.04.2021