How to use Formik with React-Select with isMulti? - reactjs

I'm building a system where we can create a project and assign it to several users by filling up a form using Formik, Yup and React-Select.
However, I'm really struggling with passing the array of users when submitting my form using Formik, Formik doesn't receive the data from React-Select.
Here are snippets of the code:
const validationSchema = () => {
return Yup.object().shape({
title: Yup.string().required('A title is required'),
description: Yup.string().required('A description is required'),
assigned: Yup.array().required('At least one assigned user is required'),
});
};
const formik = useFormik({
initialValues: {
title: '',
description: '',
assigned: [],
},
validationSchema,
validateOnBlur: false,
validateOnChange: false,
onSubmit: (data) => {
ProjectService.create(data).then(() => {
navigate('/projects');
window.location.reload();
});
},
});
The component containing the options to select:
Assign to:
<SelectList
name="users"
options={convertToReactSelectObject(users)}
onChange={(assignedUsers) =>
formik.setFieldValue('assigned', assignedUsers.value)
}
value={formik.values.assigned}
/>
{formik.errors.assigned ? formik.errors.assigned : null}
Does someone have an idea of what I should fix to make it work?
Thank you!

My mistake was that I was trying to only pass the "value" field of the array to Formik which doesn't work. So I've passed the whole array of assigned users
<SelectList
options={convertToReactSelectObject(users)}
value={formik.values.assigned}
onChange={(assignedUser) =>
formik.setFieldValue('assigned', assignedUser)
}
/>
Then on the backend, the new project will only take the value field of the assigned users.
// Create a Project
const project = new Project({
title: req.body.title,
description: req.body.description,
assigned: req.body.assigned.map((e) => e.value),
});

Related

Select option value returns '[object Object]'

I would like to ask for your help. I have mapped a list of data patientOptions to select field, each option has a value of
value: {
id: patient.id,
firstName: patient.firstName,
middleName: patient.middleName,
lastName: patient.lastName,
},
I need to store this information when I submit the form. If I console.log(patientOptions) this will appear
What I am trying to achieve is to get all values
value: {
id: 13412341234,
firstName: John,
middleName: Made,
lastName: Doe,
},
But if I will try to submit the form and log it to the console this will show up.
I have also tried just getting the value of 1 object it works but trying to get the value of multiple objects still returns the same error
Here is the rest of the code I hope anyone can help me.
const patientOptions = patient.map((patient) => ({
key: `${patient.firstName} ${patient.middleName} ${patient.lastName},
value: {
id: patient.id,
firstName: patient.firstName,
middleName: patient.middleName,
lastName: patient.lastName,
},
}));
onSubmit = (values) =>
console.log("form data", {
patient: values.patient,
});
<Formik
initialValues={{
patient: "",
}}
onSubmit={onSubmit}
>
<Form>
<FormikControl
control="select"
name="patient"
label="Patient"
options={patientOptions}
/>
</Form>
</Formik>;

Formik does not get date value when it returns from function

I created an array of objects that has form field definitions in order to quickly generate dynamic user forms, in react.
export const formFieldList = [
{
fieldType: 'input',
inputType: 'date',
name: 'LastLoginDate',
id: 'LastLoginDate',
label: 'Last Login Date',
value: moment(new Date()).format('YYYY-MM-DD'),
classWrapper: 'col-md-3',
}]
The problem occurred when I changed this constant array to a function. I need to get some data from async functions so I changed this array to a async func as well. All other form fields (text, select, checkbox) still work well but date fields, formik doest get date value after then.
export const formFieldList = async () => ([
{
fieldType: 'input',
inputType: 'date',
name: 'LastLoginDate',
id: 'LastLoginDate',
label: 'Last Login Date',
value: moment(new Date()).format('YYYY-MM-DD'),
classWrapper: 'col-md-3',
},
{
fieldType: 'select',
name: 'CurrencyId',
id: 'CurrencyId',
label: 'Currencies',
classWrapper: 'col-md-3',
options: await getCurrencies()
}]
If I write date value as hard-coded, it doesnt change anything, I mean function returns everything well, the problem is formik doesnt get that value as a prop when it returns from a function. My input component that get that values:
<input
{...formik.getFieldProps(Field.name)}
type={Field.inputType}
placeholder={Field.placeholder}
name={Field.name}
id={Field.id || Field.name}
/>
In the DOM, input element is shown as <input value name='xx' ... />.
My FormGenerator component that takes formFieldList and returns it to form:
const FormGenerator:FC<Props> = ({formFieldList, formButton, onSubmitValues}) => {
...
let initialValues = {}
for (const Field of formFieldList) {
if (Field.name !== 'newLine'){
initialValues = {...initialValues, [Field.name]: Field.value}
}
}
const schema = formFieldList.reduce(getValidationSchema, {})
const validationSchema = Yup.object().shape(schema)
const formik = useFormik({
initialValues,
enableReinitialize: true,
validationSchema,
onSubmit: async (values, {setStatus, setSubmitting}) => {
setLoading(true)
try {
onSubmitValues(values)
setLoading(false)
} catch (error) {
console.error(error)
setStatus('Some errors occurred')
setSubmitting(false)
setLoading(false)
}
},
})
return (
...
<form
onSubmit={formik.handleSubmit}
noValidate
>
{ formFieldList?.map((Field, index) => {
const touchedField = Field.name as keyof typeof formik.touched;
const errorsField = Field.name as keyof typeof formik.errors;
switch (Field.fieldType) {
case 'input':
return <InputField Field={Field} formik={formik} touchedField={touchedField} errorsField={errorsField} key={Field.name}/>
case 'select':
return <SelectField Field={Field} formik={formik} key={Field.name}/>
case 'newLine':
return <div className={Field.classWrapper} key={index}></div>
}
})}
...
</form>
</>
)
}
Updating:
After adding 'enableReinitialize' (with help of #Sheraff), form show dates and works well, but console give me an error as 'A component is changing an uncontrolled input to be controlled...'. Then I added value property to input element to make it controlled. But now date picker doesnt update ui when I change date manuelly (it returns correct date as its value). If I enter value 'undefined' in formFieldList then it works again, but this time I coundt initialize my date dynamically.
How can I fix it?

How to submit a formik form with two similar fields

I have a form and on this form there is an Add another form button which adds another form with the same field. I am using fieldArray by formik. Currently if I add another form then submit it's only submitting the values of the first form not the second. How can I ensure that all the form index fields are submitted.
Here's the code
const initialValues = {
certificationItems: [
{
name: '',
description: '',
additionalDetails: '',
isThirdParty: thirdParty,
issueDate: '',
expirationDate: '',
properties: [
{ name: 'prefillCertification', value: '' },
{ name: 'prefillInfo', value: '' }
]
}
]
};
<Formik
initialValues={initialValues}
onSubmit={(values) => {
console.log('values', index)
operations.updateBinding(['name'], values.certificationItems[0].name);
operations.updateBinding(['description'], values.certificationItems[0].description);
operations.updateBinding(['additionalDetails'], values.certificationItems[0].additionalDetails);
operations.updateBinding(['isThirdParty'], values.certificationItems[0].isThirdParty);
operations.updateBinding(['issueDate'], values.certificationItems[0].issueDate);
operations.updateBinding(['expirationDate'], values.certificationItems[0].expirationDate);
operations.submit();
}}

Errors state is not updating on first click

I am making a form to update addresses. When the user clicks on the submit button, the form isn't being submitted but after the click the errors are updated correctly and the form submits fine. Here is what im working with
const [errors, setErrors] = React.useState({
firstName: '',
lastName: '',
streetAddress: '',
streetAddress2: '',
city: '',
zipCode: ''
});
const validateRequiredFields = () => {
let inputError = {};
Object.keys(user).forEach(
(key) => {
inputError = { ...inputError, ...RequiredValidation(user, key) };
}
);
setErrors({ ...errors, ...inputError });
The Validation returns inputError which takes in the values and sets to null instead of empty strings if theres no error. When I console.log this it returns the correct format. Also if there is errors, the errors are updated correctly on first click. Having looked into this I saw that most recent state isn't always fetched which I believe is my problem & the other threads I looked into recommended something like this although I don't know if thats how I am supposed to set it up.
setErrors({ ...errors, ...inputError }, () => {
setErrors({ ...errors, ...inputError });
});
Use
setErrors((prevErrors) => ({ ...prevErrors, ...inputError }));

Conditional form validation with Yup

I am beginner in Yup and I need to do some form validation with Yup. Here is an example of my schema :
const MyFormSchema = Yup.object().shape({
contactId: Yup.string().required(),
phoneNumber : Yup.string().phoneNumber().required(),
email: Yup.string().email().required()
});
My form is valid in 2 cases : either the user enters the contactID or he enters the phoneNumber and the email.
I tried to implement this using test function but was unable to do it. Any help would be appreciated.
You can build the same logic for rest 2 fields:
Yup.object().shape({
contactId: Yup.string().when(["phoneNumber", "email"], {
is: (phoneNumber, email) => !phoneNumber || !email,
then: Yup.string().required()
}),
phoneNumber: Yup.string().when("contractId", {
is: contactId => !!contactId,
then: Yup.string().required()
}),
email: Yup.string().when("contractId", {
is: contactId => !!contactId,
then: Yup.string()
.email()
.required()
})
});
To check for email, you can use built in function Yup.email(). For phone you need to build custom validator:
const PHONE_VALIDATOR = /...your RegEx here.../
...
phoneNumber : Yup.string().matches(PHONE_VALIDATOR, 'Phone is not valid').required()

Resources