I'm using formik to create a reusable component. This is how I've created a container to render a formik form. I need to infer types from props; since, i've cloned a children, also passed on the props from the container, I need to infer types in the FormFields
import React from 'react';
import { Formik } from 'formik';
import { Container, Grid } from '@mui/material';
import MainCard from 'ui-component/cards/MainCard';
interface ContainerProps<T> {
title: string;
initialValues: T;
validationSchema: Object;
handleSubmit: (values: T, setSubmitting: (isSubmitting: boolean) => void) => void;
children: React.ReactElement;
others?: any;
}
const FormikContainer = <T,>({ title, initialValues, validationSchema, handleSubmit, children, ...others }: ContainerProps<T>) => (
<MainCard title={title}>
<Formik
enableReinitialize
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={(values, { setSubmitting }) => {
handleSubmit(values, setSubmitting);
}}
>
{(props) => (
<form onSubmit={props.handleSubmit}>
<Grid container gap={3} maxWidth="500px">
{React.cloneElement(children, { ...props })}
</Grid>
</form>
)}
</Formik>
</MainCard>
);
export default FormikContainer;
I'm not sure on how to infer types in the FormFields for all the props associated with it. How can I define types for otherProps and since otherProps are props through formik;I need to infer formikProps types dynamically as well.
FormFields.tsx
/* eslint-disable consistent-return */
/* eslint jsx-a11y/label-has-associated-control: 0 */
import React, { useState } from 'react';
import {
Grid,
TextField,
FormHelperText,
FormControl,
FormLabel,
RadioGroup,
FormControlLabel,
Radio,
MenuItem,
Button,
IconButton,
Typography,
InputLabel
} from '@mui/material';
import { FieldArray, getIn } from 'formik';
import { v4 as uuid } from 'uuid';
import AddIcon from '@mui/icons-material/Add';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import { ImgWrapper } from './formik.styles';
import { Label } from 'components/form/Form';
type PropTypes = {
formFields: any;
btnLabel: string;
handleAdd?: any;
handleRemove?: any;
otherProps?: any;
};
const ErrorMessage = ({ errors, touched, name }) => {
// console.log(errors);
return <FormHelperText error>{getIn(touched, name) && getIn(errors, name) && getIn(errors, name)}</FormHelperText>;
};
const FormFields = ({ formFields, btnLabel, ...otherProps }): any => {
const { values, errors, touched, handleChange, handleBlur, handleSubmit, isSubmitting, setFieldValue, handleAdd, handleRemove } =
otherProps;
const [img, setImg] = useState<string>('');
return (
<>
{formFields.map((field, index) => {
switch (field.type) {
case 'fieldArray':
return (
<Grid key={index} item xs={12}>
<Typography
variant={field.mainHeading.variant || 'h4'}
component="h1"
textAlign={field.mainHeading.align}
sx={{ p: 2 }}
>
{field.mainHeading.title}
</Typography>
<FieldArray name={field.name}>
{({ push, remove }) => {
return (
<Grid container gap={3} maxWidth="100%">
{values[field.name].map((eachField, fieldIndex) => {
const IconButtonList = !field.iconButtonDisable && (
<Grid key={`container-${fieldIndex}`} container>
<Grid item xs={10}>
<Typography variant="h4">{`${field.subHeading} ${
fieldIndex + 1
}`}</Typography>
</Grid>
<Grid
item
xs={2}
sx={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}
>
<IconButton onClick={() => handleAdd(push)}>
<AddIcon />
</IconButton>
<IconButton
onClick={() => {
handleRemove(eachField.id, values, remove);
}}
disabled={values[field.name].length === 1}
>
<DeleteOutlineIcon />
</IconButton>
</Grid>
</Grid>
);
const QuestionList = field.choice.map((eachChoice, choiceIndex) => {
return (
<Grid key={`question-${choiceIndex}`} item xs={12}>
<InputLabel>{eachChoice.label}</InputLabel>
<TextField
fullWidth
placeholder={eachChoice.placeholder}
name={`${field.name}[${fieldIndex}].${eachChoice.name}`}
label={eachChoice.innerLabel}
type={eachChoice.type}
size="medium"
onBlur={handleBlur}
onChange={handleChange}
/>
<ErrorMessage
{...{
errors,
touched,
name: `${field.name}[${fieldIndex}].${eachChoice.name}`
}}
/>
</Grid>
);
});
return [IconButtonList, QuestionList];
})}
</Grid>
);
}}
</FieldArray>
</Grid>
);
}
})}
<Button type="submit" variant="contained" onSubmit={handleSubmit} disabled={isSubmitting}>
{btnLabel || 'Test Button'}
</Button>
</>
);
};
export default FormFields;
It's how I thought of creating a reusable component using formik. Am I doing it right ?
Form.tsx
import FormikContainer from 'components/formik/FormikContainer';
import FormFields from 'components/formik/FormFields';
import * as Yup from 'yup';
import { v4 as uuid } from 'uuid';
import AddPhotoAlternateOutlinedIcon from '@mui/icons-material/AddPhotoAlternateOutlined';
import { formFields } from 'views/userManagement/appUsers/constants/variables';
const initialValues = {
content: [{ id: uuid(), question: '', answer: '' }]
};
const fields = [
{
name: 'content',
type: 'fieldArray',
mainHeading: { align: 'center', title: 'Main heading' },
subHeading: 'Section',
iconButtonDisable: false,
choice: [
{ name: 'question', type: 'text', label: 'Question', placeholder: 'Enter question' },
{ name: 'answer', type: 'text', label: 'Answer', placeholder: 'Enter answer' }
]
}
];
const Form = () => {
const handleSubmit = (values, setSubmitting: (isSubmitting: boolean) => void) => {
console.log(values);
setSubmitting(false);
};
const handleAdd = (push) => {
push({ id: uuid(), question: '', answer: '' });
};
const handleRemove = (id, values, remove) => {
// const target = values.content.findIndex((value) => value.id === id);
// console.log(target);
// remove(target);
console.log(values);
values.content = values.content.filter((value) => value.id !== id);
};
return (
<FormikContainer<typeof initialValues>
title="Formik Reusable components"
initialValues={initialValues}
validationSchema={Yup.object().shape({
content: Yup.array().of(
Yup.object().shape({
question: Yup.string().required('Question is a required field'),
answer: Yup.string().required('Answer is a required field')
})
)
})}
handleSubmit={handleSubmit}
>
<FormFields formFields={fields} btnLabel="Test Button" handleAdd={handleAdd} handleRemove={handleRemove} />
</FormikContainer>
);
};
export default Form;