ReactJS: 如何使用 Formik 处理图像/文件上传?

59

我正在使用ReactJS设计我的网站的个人资料页面。 现在我的问题是如何从本地上传图像并将其保存到数据库中,同时在个人资料页面中显示它。

import React, {Component} from 'react';
import { connect } from 'react-redux';
import { AccountAction } from '../../actions/user/AccountPg1Action';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';

class AccountInfo extends Component {
  constructor(props) {
    super(props) 
    this.state = {
      currentStep: 1,
      userAccountData: {
        userid: '',
        useravtar: '',
        attachement_id: '',
   }
  }
 }

handleFileUpload = (event) => {
  this.setState({useravtar: event.currentTarget.files[0]})
};

handleChange = event => {
    const {name, value} = event.target
    this.setState({
      [name]: value
    })    
  }

handleSubmit = event => {
    let that = this;
    const { AccountAction } = that.props;
    event.preventDefault();

    let accountInputs = {
      userid: 49,
      useravtar: that.state.image,
      attachement_id: 478,
}
    that.setState({
      userAccountData: accountInputs,
    })

    AccountAction(accountInputs)
  }
AccountInfoView = () => {
console.log(this.state.useravtar)
    return (
      <section id="account_sec" className="second_form">
      <div className="container">
      <React.Fragment>
        <Formik
          initialValues={‌{
            file: null,
            email: '',
            phone: ''
          }}
          validationSchema={accountInfoSchema}
          render={(values) => {
          return(
        <Form onSubmit={this.handleSubmit}>
        <Step1 
          currentStep={this.state.currentStep} 
          handleChange={this.handleChange}
          file= {this.state.useravtar}
          handleFileUpload={this.handleFileUpload}
          />
          </Form>
        );
      }}
      />
      </React.Fragment>
      )
  }

  render() {    

    return (
      <div>{this.authView()}</div>
    )
  }
}

function Step1(props) {
console.log(props.useravtar)
  if (props.currentStep !== 1) {
    return null
  } 

  return(
    <div className="upload">
        <label htmlFor="profile">
          <div className="imgbox">
            <img src="images/trans_116X116.png" alt="" />
            <img src={props.useravtar} className="absoImg" alt="" />
          </div>
        </label>
<input id="file" name="file" type="file" accept="image/*" onChange={props.handleFileUpload}/>
        <span className="guide_leb">Add your avatar</span>
      </div>
  )
}

当我在handleChange操作中使用console查看event.target.file[0]时,它会返回undefined。

此外,在handleSubmit操作中进行console.log(this.state.useravtar),它会显示像c:/fakepath/imgname.jpg这样的路径名。

P.S:我有多个表单,所以我正在逐步使用它。我正在使用Redux Reducer存储数据。

我参考了这个链接,但我的要求看起来不是这样的。


3
你尝试使用过表单数据对象吗?- https://developer.mozilla.org/zh-CN/docs/Web/API/FormData/Using_FormData_Objects - Amr
2
@SumanthMadishetty:我使用了Formik库,因此我使用了该库中的“Field”组件。https://jaredpalmer.com/formik/ - TMA
1
@Taalavya,Formik没有文件上传组件,您需要使用HTML输入并使用Formik的setFieldValue方法来设置数据。 - Sumanth Madishetty
7个回答

82

Formik默认不支持文件上传,但您可以尝试以下方法

<input id="file" name="file" type="file" onChange={(event) => {
  setFieldValue("file", event.currentTarget.files[0]);
}} />

这里的"file"表示您用于持有文件的关键字

提交时,您可以使用它获取文件的文件名、大小等信息

onSubmit={(values) => {
        console.log({ 
              fileName: values.file.name, 
              type: values.file.type,
              size: `${values.file.size} bytes`
            })

如果您想将文件设置为组件状态,则可以使用

onChange={(event) => {
  this.setState({"file": event.currentTarget.files[0]})};
}}
根据您的代码,您需要按以下方式处理文件上传:
在AccountInfo中添加一个函数来处理文件上传。
handleFileUpload = (event) => {
this.setState({WAHTEVETKEYYOUNEED: event.currentTarget.files[0]})};
}

并将同一个函数传递给以下的Step1组件

    <Step1 
      currentStep={this.state.currentStep} 
      handleChange={this.handleChange}
      file= {this.state.image}
      handleFileUpload={this.handleFileUpload}
      />
在Step1组件中上传文件时,将输入更改为:
<input id="file" name="file" type="file" accept="image/*" onChange={props.handleFileUpload}/>
如果您需要预览已上传的图像,则可以创建一个 blob 并将其作为图像源传递,如下所示。
<img src={URL.createObjectURL(FILE_OBJECT)} /> 

编辑-1

由于安全问题,URL.createObjectURL方法已被弃用,我们需要使用srcObject来处理媒体元素。您可以使用ref来分配srcObject,例如:

假设您正在使用类组件,

构造函数

您可以在构造函数中使用

constructor(props) {
  super(props)
  this.imageElRef = React.createRef(null)
}

处理变更功能

handleFileUpload = (event) => {
  let reader = new FileReader();
let file = event.target.files[0];
reader.onloadend = () => {
  this.setState({
    file: reader.result
  });
};
reader.readAsDataURL(file);
}

元素

<img src={this.state.file} /> 

2
在哪里定义setFieldValue,它会抛出未定义的错误,如下所示: ./src/components/user/AccountInfo.jsx Line 266: 'setFieldValue' is not defined no-undef - TMA
5
setFieldValue是从<Formik />获取的,参考链接:https://jaredpalmer.com/formik/docs/api/formik#setfieldvalue-field-string-value-any-shouldvalidate-boolean-void。它可以用于设置表单字段的值,并可以选择是否执行验证。 - Dani Vijay
1
有趣。当我按照你的建议使用setFieldValue()时,出现了错误,@muthanth。这是错误信息: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. - codeinaire
"createObjectURL"现在在大多数浏览器中已被弃用。我进行了研究,并发现我们需要使用"srcObject"代替"src"。但是我不知道如何在<img/>标签中使用它。有人能帮忙吗?@SumanthMadishetty,请看一下。这对我非常有帮助。 - Utkarsh
3
面对这个错误 ==> 在 'HTMLInputElement' 上设置 'value' 属性失败:该输入元素接受文件名,只能以编程方式将其设置为空字符串。 - Hassan Ali Shahzad
显示剩余10条评论

13

以下是我如何使用 FormikMaterial UI 解决它的方法

在你的 JS 文件中,只需像下面这样声明一个名为 avatarPreview 的变量

  const [avatarPreview, setAvatarPreview] = useState('/avatars/default.png');



           <Box
            display='flex'
            textAlign='center'
            justifyContent='center'
            flexDirection='column'>
           
            <ImageAvatar size='md' src={avatarPreview || user?.avatar} />

            <Button
              variant='contained'
              component='label'
              startIcon={<CloudUploadIcon />}>
              Choose Avatar
              <input
                name='avatar'
                accept='image/*'
                id='contained-button-file'
                type='file'
                hidden
                onChange={(e) => {
                  const fileReader = new FileReader();
                  fileReader.onload = () => {
                    if (fileReader.readyState === 2) {
                      setFieldValue('avatar', fileReader.result);
                      setAvatarPreview(fileReader.result);
                    }
                  };
                  fileReader.readAsDataURL(e.target.files[0]);
                }}
              />
            </Button>
          </Box>

默认预览: 默认头像上传

选择头像后: 选择头像之后


5
将您的头像设置为“Avatar”。哈 - Logan Cundiff

4
您可以使用 Formik 来上传单个或多个文件并进行验证,具体操作如下:
import "./App.css";
import { useEffect, useState } from "react";
import * as Yup from "yup";
import { Formik, Field, Form, ErrorMessage, useField } from "formik";
import axios from "axios";


function App() {
  return (
    <Formik
      initialValues={{
        profile: [],
      }}
      validationSchema={Yup.object({
        profile:Yup.array().min(1,"select at least 1 file")
      })}
      onSubmit={(values, props) => {
        let data = new FormData();
        values.profile.forEach((photo, index) => {
          data.append(`photo${index}`, values.profile[index]);
        });
        axios
          .post("you_api_for_file_upload", data, {
            headers: {
              "Content-Type": "multipart/form-data",
            },
          })
          .then((response) => {
            console.log(response);
          })
          .catch((err) => {
            console.log(err);
          });
      }}
    >
      {(formik) => {
        return (
          <>
            <Form>
              <input
                id="file"
                name="profile"
                type="file"
                onChange={(event) => {
                  const files = event.target.files;
                  let myFiles =Array.from(files);
                  formik.setFieldValue("profile", myFiles);
                }}
                multiple
              />
              <ErrorMessage name="profile"/>
              <button type="submit" disabled={formik.isSubmitting}>
                Submit
              </button>
            </Form>
          </>
        );
      }}
    </Formik>
  );
}

export default App;

注意:您可以根据需要自定义min(your choice, "your message")。

   validationSchema={Yup.object({
        profile:Yup.array().min(1,"select at least 1 file")
      })}

1
您不能将输入名称和setFieldValue设置为相同的值,除非它是字符串类型。对于除字符串以外的类型,setFieldValue必须是不同的键。 - facebook-10203229637341337

4

FormIk不支持文件上传,我们需要以自定义的方式进行操作。只需触发onChange事件并设置文件即可。

如果在TypeScript中遇到任何setFieldValue错误,则只需执行以下操作:

onChange={(event) => {
   if (event.currentTarget.files) {
       formik.setFieldValue(
           "file",
            event.currentTarget.files[0]
        );
   }

2

要在formik中使用后端处理文件,您只需要添加以下输入(您可以将 avatar 更改为任何想要的内容):

<input
 type="file"
 name="avatar"
 onChange={(event) => {
 setFieldValue('avatar', event.currentTarget.files[0]);
 }}
 />

在 onSubmit 中,您可以使用 values 对象访问文件,例如 values.avatar

在服务器端(express)中,我们使用 req.file 访问文件。您还需要使用 multer 自动检测文件并在服务器端处理它。


首先,在 onSubmit 内部,我们需要使用 mutipart/form-data 发送请求来上传所有文件,然后进行第二个请求以创建一个新实体(例如,我们可以从第一个上传请求中接收 URL 或 FileEntity 并将其传递给创建 DTO)。 - monotype

1

我使用了简单的技巧

<Field name="image">
{ (form , meta , value ) =>
const {setFieldValue} = form 
return (  
<input name="image" type="file" onChange={(e) => setFieldValue(e.target.file[0])}
)
}
</Field>

for multiple image
<input name="image" type="file" onChange={(e) => setFieldValue(e.target.files)}
or use loop
for( i=0 ; i < e.target.file; i++){
setFieldValue([...image , e.target.file])
}

0
在使用Formik处理后端文件时,您只需要添加以下输入(您可以将 avatar 更改为任何您想要的内容):
<input
 type="file"
 name="avatar"
 onChange={(event) => {
 setFieldValue('avatar', event.currentTarget.files[0]);
 }}
 />

在onSubmit事件中,您可以使用values对象来访问文件,例如values.avatar

在服务器端(express)中,我们使用req.file来访问文件。您还需要使用multer和cloudinary来检测文件并在服务器端处理它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接