Reputation: 31
I am using Formik on React Typescript to handle my Student Profile form. This form is supposed to handle resume and transcript file upload. I noticed that everything worked great until I tested my form in Chrome and saw in the console log that the value of the input node for the resume and transcript files were being set to C:\fakepath{Original File Name}.
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;
Upvotes: 2
Views: 2250
Reputation: 3302
In my codebase the file upload <input type='file' ... />
is buried in re-usable styled components, so I don't have access to it.
<FancyFileUpload types={['PDF']} onSelect={f => handleFileSelect(f, setFieldValue, validateForm)} />
It gives me an onSelect
that passes a standard File
type, which has the properties .name
, .size
, and .type
. To my handler I pass in the formik helpers (from
<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();
};
I use a const [file, setFile] = useState<File>();
to hold the file itself, since it is uploaded to server separately. The handler sets two strings inside the nested object properties in my model, remembers the whole File in the useState.
You need to manually call Formik's validateForm()
to check/clear the errors
field. But, it runs synchronously, and the setters haven't taken effect yet. So we await a Promise so the validateForm()
is put into the microtask queue and happens later.
BTW showing the errors is thus. Replace Alert
with whatever you want, but ErrorMessage
is imported from Formik as well.
<ErrorMessage name={'support.documentName'}>
{msg => <Alert severity="error">{msg}</Alert>}
</ErrorMessage>
Upvotes: 0
Reputation: 1079
Formik doesnot support fileupload by default, But you can try the following
<input id="file" name="file" type="file" onChange={(event) =>{
formik.setFieldValue("file", event.currentTarget.files[0]);
}} />
And this will work like a charm :)
Upvotes: 1