我在 React Typescript 上使用 Formik 来处理我的学生资料表单。该表单用于处理简历和成绩单文件上传。我注意到一切都很顺利,直到我在 Chrome 中测试我的表单并在控制台日志中看到简历和成绩单文件的输入节点的值被设置为 C:akepath{原始文件名}。
import React from 'react';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
import Paper from '@material-ui/core/Paper';
import { Formik, Form, Field } from 'formik';
import * as yup from 'yup';
import useApi from 'hooks/useApi';
import useSnack from 'hooks/useSnack';
import Loader from 'Components/Loader';
import { TextFormField } from 'Components/TextFormField';
import { SelectFormField } from 'Components/SelectFormField';
import Button from 'Components/Button';
import {
classStandingTypes,
createStudentProfile,
} from 'Domains/Student/api/api';
import {
departments,
} from 'sharedData'
export interface IStudentProfileForm {
firstName: string;
middleName: string;
lastName: string;
departmentId: string;
sid: number;
classStanding: string;
email: string;
biography: string;
resume: string;
transcript: string;
};
const formInitialValues: IStudentProfileForm = {
firstName: '',
middleName: '',
lastName: '',
departmentId: '',
sid: 0,
classStanding: '',
email: '',
biography: '',
resume: '',
transcript: '',
};
const formSchema = yup.object({
firstName: yup.string().required('First name is required'),
lastName: yup.string().required('Last name is required'),
departmentId: yup.string().required('Department is required'),
sid: yup.string().required('Student ID is is required'),
classStanding: yup.string().required("Class standing is required"),
email: yup.string().required('Email is required').email('Please enter valid email'),
biography: yup.string().required("Biography is required")
});
function StudentProfileForm() {
const [studentProfile, setStudentProfile] = React.useState<IStudentProfileForm>(formInitialValues);
const request = React.useCallback(() => createStudentProfile(studentProfile), [studentProfile]);
const [snack] = useSnack();
const [sendRequest, isLoading] = useApi(request, {
onSuccess: () => {
snack('Student profile successfully created!', 'success');
},
});
return (
<Paper style={{ padding: 50 }}>
<Formik
validationSchema={formSchema}
initialValues={formInitialValues}
onSubmit={(formValues, actions) =>
{
console.log(formValues);
setStudentProfile(formValues);
sendRequest();
actions.resetForm({
values: { ...formInitialValues },
});
}}
>
{() => (
<Form>
<Grid container spacing={3} alignContent='center'>
<Grid item container justify='flex-start'>
<Typography variant='h4'>Create Student Profile</Typography>
</Grid>
<Grid item container spacing={5}>
<Grid item md={4} xs={12}>
<Field
name='firstName'
label='First Name'
component={TextFormField}
/>
</Grid>
<Grid item md={4} xs={12}>
<Field
name='middleName'
label='Middle Name'
component={TextFormField}
/>
</Grid>
<Grid item md={4} xs={12}>
<Field
name='lastName'
label='Last Name'
component={TextFormField}
/>
</Grid>
<Grid item md={6} xs={12}>
<Field
name='departmentId'
label='Department'
options={departments}
component={SelectFormField}
/>
</Grid>
<Grid item md={6} xs={12}>
<Field
name='classStanding'
label='Class Standing'
options={classStandingTypes}
component={SelectFormField}
/>
</Grid>
<Grid item md={6} xs={12}>
<Field
name='sid'
label='SID'
type = 'number'
component={TextFormField}
/>
</Grid>
<Grid item md={6} xs={12}>
<Field
name='email'
label='Email'
component={TextFormField}
/>
</Grid>
<Grid item md={6} xs={12}>
<Field
name='resume'
label='Resume'
type='file'
InputLabelProps={{
shrink: true,
}}
component={TextFormField}
/>
</Grid>
<Grid item md={6} xs={12}>
<Field
name='transcript'
label='Transcript'
type='file'
InputLabelProps={{
shrink: true,
}}
component={TextFormField}
/>
</Grid>
<Grid item md={12} xs={12}>
<Field
name='biography'
label='Biography'
multiline
component={TextFormField}
/>
</Grid>
</Grid>
<Grid container item xs={12}>
<Button type='submit' isLoading={isLoading}>
Submit
{isLoading && <Loader size={20} />}
</Button>
</Grid>
</Grid>
</Form>
)}
</Formik>
</Paper>
);
}
export default StudentProfileForm;
Formik 默认不支持文件上传,但你可以尝试以下方法
<input id="file" name="file" type="file" onChange={(event) =>{
formik.setFieldValue("file", event.currentTarget.files[0]);
}} />
这会像一个魅力:)
在我的代码库中,文件上传
<input type='file' ... />
被埋在可重复使用的样式组件中,所以我无权访问它。
<FancyFileUpload types={['PDF']} onSelect={f => handleFileSelect(f, setFieldValue, validateForm)} />
它给了我一个传递标准
onSelect
类型的 File
,该类型具有属性 .name
、.size
和 .type
。我将 formik 助手传递给我的处理程序(来自
<Formik<T> onSubmit=...etc>
{({ values, errors, isSubmitting, setFieldValue, validateForm }) => (
<Form>
...
</Form>
)}
</Formik>
const handleFileSelect = async (
f: File | undefined,
setFieldValue: FormikHelpers<MyModel>['setFieldValue'],
validateForm: FormikHelpers<MyModel>['validateForm']
) => {
setFieldValue('support.documentName', f?.name, true);
setFieldValue('support.documentMime', f?.type, true);
setFile(f);
await Promise.resolve('wait for the setXxx above to take effect');
return validateForm();
};
我使用
const [file, setFile] = useState<File>();
来保存文件本身,因为它是单独上传到服务器的。处理程序在我的模型中的嵌套对象属性内设置两个字符串,记住 useState 中的整个文件。
您需要手动调用Formik的
validateForm()
来检查/清除errors
字段。但是,它是同步运行的,setter 还没有生效。因此,我们等待 Promise,以便将 validateForm()
放入微任务队列并稍后发生。
顺便说一句,显示错误是这样的。将
Alert
替换为您想要的任何内容,但 ErrorMessage
也是从 Formik 导入的。
<ErrorMessage name={'support.documentName'}>
{msg => <Alert severity="error">{msg}</Alert>}
</ErrorMessage>