infinite_l88p
infinite_l88p

Reputation: 31

React Typescript: How to handle Formik File Upload?

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

Answers (2)

Ron Newcomb
Ron Newcomb

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

Muhammad Wasim Akram
Muhammad Wasim Akram

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

Related Questions