I am trying to create a react+vite project where I am trying to implement a reusable form components using Formik and Yup but not able to achieve this yet.
I have created a component called <AppInputField/>
which is just an <input/>
field with custom css to suit my design of the overall website. Similar to this I have <AppButton/>
which is again just <button/>
. And all the required attributes I am passing through props. Now when I am trying to create a similar for <AppForm/>
which will wrap around formik
library I am having a hard time solving it. From the searches I have came to know that it is related useFormik()
and useFormikContext()
but again not 100% sure.
Here's my codes :
SignUp.jsx
const SignUp = () => {
const signUpSchema = Yup.object({
name: Yup.string().min(2).max(25).required("Please enter your name"),
});
return (
<>
<AppForm
initialValues={{
name : ''
}}
validationSchema={signUpSchema}
onSubmit={(values) => {console.log("Submitted with these values\n" + values)}}
>
<AppInputField
name='name'
label='Enter your name'
placeholder='XYZ'
/>
<SubmitButton title='Register'/>
</AppForm>
</>
);
};
export default SignUp;
AppForm.js
function AppForm({initialValues, onSubmit, validationSchema, children}) {
return (
<Formik
initialValues={ initialValues }
onSubmit={ onSubmit }
validationSchema={ validationSchema }
>
{children}
{/* children passed as props here are the inner components of form
meaning AppInputField and SubmitButton
this actually renders the inside components */}
</Formik>
);
}
export default AppForm;
AppInputField.jsx
const AppInputField = ({name, label, placeholder, type='text') => {
const { handleChange, handleBlur, values } = useFormikContext();
return (
<>
<label htmlFor={name} className="input-label">{label}</label>
<input
autoComplete="off"
className="input-text"
id={name}
name={name}
onChange={handleChange}
onBlur={handleBlur}
placeholder={placeholder}
type={type}
value={values[name]}
/>
</>
)
}
export default AppInputField
SubmitButton.jsx
function SubmitButton({title}) {
const { handleSubmit } = useFormikContext();
return (
<AppButton
title={title}
type='submit'
onClick={handleSubmit}
/>
);
}
export default SubmitButton;
So the error when clicking on SubmitButton is
Uncaught TypeError: Cannot destructure property 'handleSubmit ' of 'useFormikContext(...)' as it is undefined.
If I change it to something submitForm
then same error. Also when using the useFormik()
the code of the components is changed but there also no success. When using useFormik
I cannot use the context in another component. I need to use all in the same form at the same jsx file. Hope i am able to explain my objective. This kind of abstraction I had came across when going through course of react-native of Mosh Hemdani. And I found it so beautiful that I want to achieve this in my react+vite
project. Any help would be appreciated.
Got it! would love to share with you all. useField
was way to go and thanks to adsy for guiding. Here is my code with Yup and Formik...
AppForm.jsx
const AppForm = ({initialValues, validationSchema, onSubmit, children}) => (
<>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
>
{(formikProps) => (
<Form>
{React.Children.map(children, child => {
if (React.isValidElement(child)) {
React.cloneElement(child);
}
return child;
})}
</Form>
)}
</Formik>
</>
);
export default AppForm
AppInputField.jsx
const AppFormInputField = ({label, type='text', ...props}) => {
const [field, meta, helpers] = useField(props);
return (
<>
<AppInputField label={label} type={type} {...field} {...props}/>
<ErrorMessage error={meta.error} visible={meta.touched && meta.error} />
</>
)
}
export default AppFormInputField
AppFormFileUpload.jsx
const AppFormFileUpload = ({label, allowedExtensions, setFieldValue, ...props}) => {
const [field, meta, helpers] = useField(props);
return (
<>
{/*FileUploadField is basically input field with attribute type=file with custom css*/}
<FileUploadField label={label} {...field} {...props}
value={undefined}
onChange={(event) => {
helpers.setValue(event.target.files)
}}
/>
<ErrorMessage error={meta.error} visible={meta.touched && meta.error}/>
</>
)
}
export default AppFormFileUpload
No change in SubmitButton.jsx
SignUp.jsx
const SignUp = () => {
const checkSchema = Yup.object({
firstName: Yup.string().min(3).required("First Name is Required"),
file : Yup.mixed().required('required')
.test('fileFormat', 'Only approved files are allowed', (value, context) => {
if (value) {//just pretest check
const supportedFormats = ['pdf','jpg', 'gif', 'png', 'jpeg', 'svg', 'webp'];
for (const [key, val] of Object.entries(value)) {
if(!supportedFormats.includes(val.name.split('.').pop())) return false
}
}
return true;
})
.test('fileSize', 'File size must be less than 3MB', (value, context) => {
if (value) {//just pretest check
for (const [key, val] of Object.entries(value)) {
if(val.size > 3145728) return false
}
}
return true;
}),
})
const initialValues={
firstName: '',
file : ''
}
const handleSubmit = (values, actions) => {console.log(values)}
return (
<>
<AppForm
initialValues={initialValues}
validationSchema={checkSchema}
onSubmit={handleSubmit}
>
<AppFormInputField name='firstName' label='Enter First Name' />
<AppFormFileUpload name='file' multiple/>
<SubmitButton title='Submit' />
</AppForm>
</>
);
};
export default SignUp;
Hope this solution might be helpful...In this way the forms can be reuse with their respective formik and yup validation