如何使用redux-form上传文件?

时间:2016-09-26 08:45:00

标签: redux redux-form

尝试上传文件时,我无法在商店中获得正确的价值。我得到类似{ 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>

  ...

}

我发现网络上的所有示例都是使用vx of redux-form制作的。

如何在redux-form v6中输入文件?

9 个答案:

答案 0 :(得分:19)

创建一个字段组件,如:

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或其他库。

答案 1 :(得分:11)

我的redux表单用Dropzone

表单输入包装器
MediaPlayer

热门使用它:

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} />;

答案 2 :(得分:6)

另一种方法是渲染预览图像(下面的示例使用React 16+语法,只接受单个图像文件发送到API;但是,通过一些小的调整,它也可以缩放到多个图像和其他领域输入):

工作示例https://codesandbox.io/s/m58q8l054x

工作示例(过时)https://codesandbox.io/s/8kywn8q9xl

<强>之前

enter image description here

<强>后

enter image description here

<强>容器/ UploadForm.js

UIViewController

<强>组件/ dropzoneField.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);

<强>组件/ imagePreview.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;

<强>组件/ placeholder.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;

<强>组件/ showError.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;

<强> styles.css的

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;

答案 3 :(得分:5)

我设法使用包含TextField的material-ui上的redux-form来执行此操作:

B4编辑:

befor edit

修改后:

after edit

 <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}
    />
  )
}

答案 4 :(得分:2)

如果你需要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" />

答案 5 :(得分:1)

对于React> = 16和ReduxForm> = 8(React和8.2.5的测试版本是16.8.6) 按照以下组件工作。

(解决方案由DarkBitz发表在相关GitHub issue中)

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>
    )
}

答案 6 :(得分:1)

使用Redux表单

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>

答案 7 :(得分:0)

您也可以为此使用 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}
     />

答案 8 :(得分:-1)

采用常规格式

/*
//Express Server
const express = require("express");
const multer = require("multer");
const cors = require("cors");
const morgan = require("morgan");

const app = express();

app.use(cors());
app.use(morgan("tiny"));

const upload = multer();

app.post("/upload", upload.single("file"), (req, res, next) => {
  console.log(req.file);

  res.send("ok");
});

app.get("/", (req, res, next) => {
  res.send("ok");
});

app.listen(3001, () => {
  console.log("server is up");
});

*/

// with Normal Form
import React, { useState } from "react";
import ReactDOM from "react-dom";


const App = () => {
  const [name, setName] = useState(null);
  const [file, setFile] = useState(null);

  const uploadImage = (ev) => {
    ev.preventDefault();

    const data = new FormData();
    data.append("name", name);
    data.append("file", file);

    axios
      .post("http://localhost:3001/upload", data)
      .then((response) => {
        console.log(response);
      })
      .catch((err) => {
        console.log(err);
      });
  };

  const fileChanged = () => {
    var file = document.getElementById("fileItem").files[0];
    setFile(file);
  };

  return (
    <form onSubmit={(ev) => uploadImage(ev)}>
      <input
        type='text'
        id='name'
        onChange={(ev) => {
          const { value } = ev.target;
          setName(value);
        }}
      />
      <input id='fileItem' type='file' accept='.jpg' onChange={fileChanged} />
      <button type='submit'>Upload</button>
    </form>
  );
};



ReactDOM.render(
    <App />,
  document.getElementById("root")
);
<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>