Formik values not updating in Input form but state updates - reactjs

I am creating an invoice generator with Formik. The issue is that the Fields Component prints out the new state of the form when I click save changes. But when I return to the form again, the old field persists. If I click on something or return, then the new field replaces the old field. I looked at other solutions like enableReinitialize and passing a handleChange but I have the same issue.
Edit Invoice Form Component
<Formik
initialValues={{
senderAddress: invoice.senderAddress,
clientName: invoice.clientName,
clientEmail: invoice.clientEmail,
clientAddress: invoice.clientAddress,
createdAt: new Date(invoice.createdAt),
paymentTerms: invoice.paymentTerms,
description: invoice.description,
items: invoice.items,
invoice: invoice.paymentTerms
}}
validationSchema={validationSchema}
onSubmit={onSubmit}
>
{formik => (
<Form setIsOpen={setIsOpen}
enableReinitialize>
<Heading>Edit <span>#</span>{invoice.id}</Heading>
<Fields client={client} getClientsChild={getClientsChild} setDraftId={setDraftId} />
<Buttons>
<Button type="button" secondary onClick={() => setIsOpen(false)}>Cancel</Button>
<Button type="submit" onClick={() => addDraft(formik.values)}>Save Changes</Button>
</Buttons>
</Form>
)}
Fields Component
useEffect(() => {
console.log(client)
//setFieldValue(businessID, client.draft.business_id)
//setFormikContext const { setFieldValue } = useFormikContext()
//setFieldValue(`items[${index}].total`, rounded || '0');
//onChange={value => setFieldValue(name, value)}
if (client) {
console.log("Client in child", client.draft)
setDraftId(client.draft.id)
formik.values.businessID = client.draft.businessID
formik.values.clientName = client.restaurant.first_name + client.restaurant.last_name;
formik.values.clientEmail = client.restaurant.email;
formik.values.clientAddress.street = client.restaurant.address;
formik.values.clientAddress.postCode = client.restaurant.postal_code;
formik.values.paymentTerms = client.draft.terms;
formik.values.businessID = client.draft.business_id;
console.log("Client in child", client.draft)
}
}, [client])
return (
<TouchScrollable>
<Wrapper>
<FieldSet>
<Legend>Bill From</Legend>
<BillFrom>
<SelectClient update={update} id="client" label="Select Client" name="clientOptions" options={clients ? clients : [{ name: '', value: '' }]} />
<Input label="Street Address" name="senderAddress.street" />
<Input label="City" name="senderAddress.city" />
<Input label="Post Code" name="senderAddress.postCode" />
<Input label="Country" name="senderAddress.country" />
</BillFrom>
</FieldSet>
<FieldSet>
<Legend>Bill To</Legend>
<BillTo>
<Input label="Business ID ( If financing Net 30 terms )" name="businessID" />
<Input label="Client's Name" name="clientName" />
<Input label="Client's Email" name="clientEmail" placeholder="e.g. email#example.com" />
<Input label="Street Address" name="clientAddress.street" />
<Input label="Postal Code" name="clientAddress.postCode" />
</BillTo>
</FieldSet>
<FieldSet>
<OtherFields>
<DatePicker label="Invoice Date" name="createdAt" />
<Select label="Payment Terms" name="paymentTerms" options={dropdownOptions} />
<Input label="Description" name="description" placeholder="e.g. Graphic Design Service" />
</OtherFields>
</FieldSet>
<Items name="items" />
{formik.submitCount > 0 && formik.errors &&
<Errors>
{reduceErrors(formik.errors).map((item, index) => (
<Error key={index}>{item}</Error>
))}
</Errors>
}
</Wrapper>
</TouchScrollable>
)
}

Related

React Formik FieldArray remove and push does not work

I wanted to have a dynamic form, so I followed this video (https://www.youtube.com/watch?v=me1kY_uFe5k) to utilize Formik remove and push method.
Now the browser is able to render all the data, but when I click on the "Delete" button or the "Add" button, nothing happened. There are also no error message so I did not know how to debug/fix it.
Note: When I review the tutorial video, I found the author used "values" (as default Props) to retrieve data. I did not go this way. Could it be the root cause? What is the use case of "values"? The video author used "values" but he did not explain why it was used.
function CreateTestSuite3() {
const initialValues = {
testSuite: {
tsId: 0,
tsName: "",
tsDescription: "",
testCases: [
{
testCaseNumber: 0,
testCaseName: "",
testCaseEnvironment: "",
testCaseSQL: ""
}
]
}
};
const onSubmit = (data) => {
console.log("this is form data: ", data);
axios.post("http://localhost:8080/TestSuite/CreateTestSuite", data)
.then((response) => {
console.log("Form Submitted");
})
};
return (
<div className="CreateTestSuite">
<Formik initialValues={initialValues} onSubmit={onSubmit}>
<Form className="formContainer">
<label>Test Suite Id: </label>
<ErrorMessage name="tsId" component="span" />
<Field autoComplete="off" name="testSuite.tsId" placeholder="tsId" />
<label>Test Suite Name: </label>
<ErrorMessage name="tsName" component="span" />
<Field autoComplete="off" name="testSuite.tsName" placeholder="tsName" />
<label>Test Suite Description: </label>
<ErrorMessage name="tsDescription" component="span" />
<Field autoComplete="off" name="testSuite.tsDescription" placeholder="tsDescription" />
<FieldArray name="testSuite.testCases">
{({push, remove, }) => (
<>
{initialValues.testSuite.testCases.map((TestCase, index) => (
<div key={index}>
<label>Test Case Number: </label>
<Field name={`TestCase.${index}.testCaseNumber`} placeholder="testCaseNumber" ></Field>
<label>Test Case Name: </label>
<Field name={`TestCase.${index}.testCaseName`} placeholder="testCaseName" ></Field>
<label>Test Case Environment: </label>
<Field name={`TestCase[${index}].testCaseEnvironment`} placeholder="testCaseEnvironment" ></Field>
<label>Test Case SQL: </label>
<Field name={`TestCase[${index}].testCaseSQL`} placeholder="testCaseSQL" ></Field>
<button type="button" onClick={() => remove(index)} >Delete</button>
</div>
))}
<button type="button" onClick={() => push({ testCaseNumber: 0, testCaseName: "", testCaseEnvironment: "", testCaseSQL: "" })}>Add Test Case</button>
</>
)}
</FieldArray>
<button type="submit">Create Test Suite</button>
</Form>
</Formik>
</div>
)
}
Add and remove is not working since your rendering using the initial values and not the updated values.
Please refer the below code.
<Formik initialValues={initialValues} onSubmit={onSubmit}>
{(formik) => {
const { values } = formik;
return (
<Form className="formContainer">
<label>Test Suite Id: </label>
<Field
autoComplete="off"
name="testSuite.tsId"
placeholder="tsId"
/>
<label>Test Suite Name: </label>
<Field
autoComplete="off"
name="testSuite.tsName"
placeholder="tsName"
/>
<label>Test Suite Description: </label>
<Field
autoComplete="off"
name="testSuite.tsDescription"
placeholder="tsDescription"
/>
<FieldArray
type="testSuite.testCases"
name="testSuite.testCases"
id="testSuite.testCases"
value={values.testSuite.testCases}
render={(arrayHelpers) => (
<div className="formContainer">
{values.testSuite.testCases.map((TestCase, index) => (
<div className="formContainer" key={index}>
<label>Test Case Number: </label>
<Field
name={`TestCase.${index}.testCaseNumber`}
placeholder="testCaseNumber"
></Field>
<label>Test Case Name: </label>
<Field
name={`TestCase.${index}.testCaseName`}
placeholder="testCaseName"
></Field>
<label>Test Case Environment: </label>
<Field
name={`TestCase[${index}].testCaseEnvironment`}
placeholder="testCaseEnvironment"
></Field>
<label>Test Case SQL: </label>
<Field
name={`TestCase[${index}].testCaseSQL`}
placeholder="testCaseSQL"
></Field>
<button
type="button"
onClick={() => arrayHelpers.remove(index)}
>
Delete
</button>
</div>
))}
<button
type="button"
onClick={() => {
console.log("hit");
arrayHelpers.insert({
testCaseNumber: 0,
testCaseName: "",
testCaseEnvironment: "",
testCaseSQL: ""
});
}}
>
Add Test Case
</button>
</div>
)}
/>
<button type="submit">Create Test Suite</button>
</Form>
);
}}
</Formik>
Code Sandbox: https://codesandbox.io/s/frosty-platform-ty83no?file=/src/App.js:527-3738

Populate react formik form with existing data

i am new to react, can anyone explain to me how can i manipulate or repopulate my existing form from backend data in react.
I am trying to edit and existing item in the inventory that i wanna change values for. i am using formik with react and formik grid. for data i am using AXIOS.
What i am trying to do is to get edit a specific entry from my database which has changed values. it's like trying to update values
<Formik
validationSchema={schema}
initialValues={{
name: "",
numberOfSets: "0",
sortValue: "0",
boxType: "",
price: "",
photo: "",
}}
onSubmit={({
name,
numberOfSets,
sortValue,
boxType,
price,
photo,
}) => {
boxService.addBox(
name,
numberOfSets,
sortValue,
boxType,
price,
photo
);
alert("Box added Successfully!"); // Box or Gift it's same thing
window.location.reload(false);
}}
>
{({
values,
errors,
touched,
handleBlur,
handleSubmit,
setFieldValue,
}) => {
return (
<Form
form={form}
name="edit-gift"
onFinish={handleSubmit}
{...layout}
labelAlign="left"
>
// These are the fields i am trying to manipulate
<Form.Item name="name" label="Name">
<Input
name="name"
title="Product Name"
dataIndex="name"
key="productName"
value={values.name}
onChange={(e) => setFieldValue("name", e.target.value)}
onBlur={handleBlur}
placeholder="Please enter Box Name"
/>
{errors?.name && touched?.name && (
<Text type="danger">{errors?.name}</Text>
)}
</Form.Item>
<Form.Item name="numberOfSets" label="Number of Sets">
<Input
name="numberOfSets"
type="number"
value={values.numberOfSets}
onChange={(e) =>
setFieldValue("numberOfSets", e.target.value)
}
onBlur={handleBlur}
placeholder="Please enter Number of Sets"
/>
{errors?.numberOfSets && touched?.numberOfSets && (
<Text type="danger">{errors?.numberOfSets}</Text>
)}
</Form.Item>
<Form.Item name="sortVlaue" label="Sort Value">
<Input
name="sortVlaue"
type="number"
value={values.sortValue}
onChange={(e) => setFieldValue("sortVlaue", e.target.value)}
onBlur={handleBlur}
placeholder="Please enter soring value"
/>
{errors?.numberOfBoxes && touched?.numberOfBoxes && (
<Text type="danger">{errors?.numberOfBoxes}</Text>
)}
</Form.Item>
<Form.Item name="boxType" label="Type">
<Select
value={values.boxType}
onChange={(value) => setFieldValue("boxType", value)}
onBlur={handleBlur}
placeholder="Please enter Box Type"
>
<Select.Option value="reward">Reward</Select.Option>
<Select.Option value="doublerandom">
Double Random
</Select.Option>
</Select>
{errors?.boxType && (
<Text type="danger">{errors?.boxType}</Text>
)}
</Form.Item>
<Form.Item name="price" label="Price">
<Input
name="price"
title="Product Price"
dataIndex="price"
key="price"
value={values.price}
onChange={(e) => setFieldValue("price", e.target.value)}
onBlur={handleBlur}
placeholder="Please enter Box Price"
/>
{errors?.price && touched?.price && (
<Text type="danger">{errors?.price}</Text>
)}
</Form.Item>
<Form.Item name="photo" label="Product Picture">
<div className="dropzone-container">
{values.photo && <img src={values.photo} alt=""></img>}
{!values.photo && (
<Dropzone
onDrop={(acceptedFiles) => {
acceptedFiles.map((file) => {
console.log(file);
});
let fr = new FileReader();
fr.onload = function () {
setFieldValue("photo", fr.result);
setFieldValue("imgName", acceptedFiles[0].name);
console.log(acceptedFiles[0]);
};
fr.readAsDataURL(acceptedFiles[0]);
}}
accept={{ "image/*": [] }}
maxSize={1000000}
>
{({ getRootProps, getInputProps }) => (
<section>
<div {...getRootProps({ style })}>
<input {...getInputProps()} />
<p>
Drag 'n' drop some files here, or click to
select files
</p>
<aside>
Tips: The size ratio of the photo is: 750:216,
the maximum recommended size is 1M, and the
format is: .jpg, .jpeg, .png
</aside>
</div>
</section>
)}
</Dropzone>
)}
</div>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Cofirm save
</Button>
</Form.Item>
</Form>
);
}}
</Formik>
You could either:
rerender the Formik component when your data is available and pass it to the initialValues prop by doing something like
results?.data && <Formik initialValues={{...}}>...</Formik>
or set the values manually with setValues after the data is available
const {setValues, values, ...} = useFormik({...});
useEffect(() => {
setValues(results?.data); // use any fields from your data
}, [results]} // this is the data from your api

Validating radio button with React Hook Form

I have a custom radio button component in React looking like this:
export default function SingleSelectionButton(props) {
const {
values, name, required, errors, defaultData,
xs, md, register, infoBox, infoBoxContent, validateField } = props;
return (
<>
<Form.Group>
<Form.Label>
<FormattedMessage
id={name}
/>
</Form.Label>
{values.map((value) => (
<span key={value} className="SingleSelectionButton">
<input
{...register(name, { required: required}
id={name + value}
type="radio"
name={name}
value={value}
defaultChecked={defaultData.primary[name] === value}
/>
<label htmlFor={name + value}>
<FormattedMessage id={value} />
</label>
</span>
))}
</Form.Group>
</>
);
};
I call it like this, using an array for the different values:
<SingleSelectionButton
name="us_person"
md={6}
values={["yes", "no"]}
register={register}
required={true}
errors={errors}
validateField="no"
/>
The validation with required is working fine.
The problem is that I don't manage to validate a value in particular.
I have tried the following:
<input
{...register(name, { required: required, validate: value => value === validateField })}
id={name + value}
type="radio"
name={name}
value={value}
defaultChecked={defaultData.primary[name] === value}
/>
And also:
<input
{...register(name, { required: required, pattern: validateField })}
id={name + value}
type="radio"
name={name}
value={value}
defaultChecked={defaultData.primary[name] === value}
/>
So far no joy. What am I missing?

onSubmit handler not working and I can't submit/validate my Formik form in Reactjs

I am a beginner to Formik forms and Yup Validation, However I am working on this form and I am not able to make the form submit successfully. I will appreciate your help what's going wrong in my code and why it's not submitting ?
my Yup validation (might not be the problem)
const validate = Yup.object({
name: Yup.string()
.max(20, "Must be 20 characters or less" )
.required('required'),
school: Yup.string("required name").required('Please select your school').oneOf(schools),
jobType: Yup.string().required("are you a student or a staff member ?!"),
parentName: Yup.string().required("enter parent name"),
parentPhone: Yup.number().required("Enter phone number"),
parentEmail: Yup.string().email().required("enter parent Email"),
staffPhone: Yup.string().required("enter staff Phone number"),
staffEmail: Yup.string().email().required("enter staff Email"),
condition: Yup.string().required("Please chose one").oneOf(condition)
})
My formik tag, the only thing in the return statement
<Formik
initialValues ={{
name: '',
school: '',
jobType: '',
// isStudent:'student',
// isStaffMember:'staffMember',
parentName: "",
parentPhone: '',
parentEmail: '',
staffPhone:'',
staffEmail: '',
condition:'',
...signupVals
}}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
console.log("Logging in", setSubmitting);
setSubmitting(false);
}, 500);
}}
validationSchema={validate}
// onSubmit={async (values) => {
// await new Promise((r) => setTimeout(r, 500));
// Window.alert(JSON.stringify(values, null, 2));
// }}
>
{({values, errors, touched, isValidating, isSubmitting}) => (
<>
{console.log(values)}
{console.log("submit ", isSubmitting)}
{console.log("validate ", isValidating)}
<div>
<h1 className=" my-4 font-weight-bold-display-4">
Sign Up
</h1>
<Form >
<div className='my-4'>
<label className="form-label" >Name: </label>
<Field className="ms-2" label ="name" name="name" type="text" id="exampleFormControlInput1" />
<ErrorMessage name="name" component='inline-block' style={{color:"red" , fontSize:"12px"}} />
{/* {errors} */}
</div>
<div className='my-4'>
<label>School: </label>
<Field className="ms-2" label ="school" as="select" name="school" >
<option />
<option>Aim Academy</option>
<option>Beyond Academy</option>
<option>Curiousity Academy</option>
<option>Discover Academy</option>
<option>Explore Academy</option>
</Field>
<ErrorMessage name="school" component='inline-block' style={{color:"red" , fontSize:"12px"}} />
</div>
<div id="my-radio-group" className='my-4'>Job type</div>
<ErrorMessage name="jobType" component='inline-block' style={{color:"red" , fontSize:"12px"}} />
<div role="group" aria-labelledby="my-radio-group" >
<div className='my-4'>
<label >
<Field className="px-2" type="radio" name="jobType" value="student" onClick={handleIsStudent}/>
Student
</label>
</div>
<div className='my-4'>
<label>
<Field type="radio" name="jobType" value="staff member" onClick={handleIsStaff} />
Staff member</label>
</div>
{isStudent ?<>
<label > Parent name
<Field className=" my-3 ms-2" type="input" name="parentName" />
<ErrorMessage name="parentName" component='inline-block' style={{color:"red" , fontSize:"12px"}}/>
</label>
<label> Parent Phone
<Field className=" my-3 ms-2" type="input" name="parentPhone" />
<ErrorMessage name="parentPhone" component='inline-block' style={{color:"red" , fontSize:"12px"}} />
</label>
<label> Parent Email
<Field className=" my-3 ms-2" type="input" name="parentEmail" />
<ErrorMessage name="parentEmail" component='inline-block' style={{color:"red" , fontSize:"12px"}} />
</label>
</>: null}
{isStaff ?<>
<label> staff phone
<Field className=" my-3 ms-2" type="input" name="staffPhone" />
<ErrorMessage name="staffPhone" component='inline-block' style={{color:"red" , fontSize:"12px"}} />
</label>
<label> staff Email
<Field className=" my-3 ms-2" type="input" name="staffEmail" />
<ErrorMessage name="staffEmail" component='inline-block' style={{color:"red" , fontSize:"12px"}}/>
</label>
</>: null}
<div className='my-4'>
<label>Condition: </label>
<Field className="ms-2" label ="Condition" as="select" name="condition" >
<option />
<option>Experiencing Symptoms</option>
<option>Tested positive for Covid</option>
</Field>
<ErrorMessage name="condition" component='inline-block' style={{color:"red" , fontSize:"12px"}} />
</div>
</div>
<button className="btn btn-dark mt-3" type="submit" > Register</button>
<button className="btn btn-danger mt-3 ml-3" type="submit" > Reset</button>
{/* {console.log(values)} */}
<TextField {...values} />
</Form>
</div>
</>
)}
</Formik>
To my understanding wrapping the inside will allow the form to fire automatically the onSubmit handler function I have! but I am not sure and would appreciate your help to figure this out?!
I don't think it's a good practice to submit your form every 500ms. With that said, I'll suggest moving the form into it's own component and using useFormikContext move the autosubmit into an useEffect:
Refactor to follow this structure:
<Formik ...>
<NewComponent/>
</Formik>
In the NewComponent paste all the field of your form
In the NewComponent get the context of values and handleSubmit:
const { values, handleSubmit } = useFormikContext();
Also in the NewComponent add a new useEffect:
useEffect(() => {
handleSubmit();
}, [values]);
That will give you an auto-submit, every time the users modify something in the fields. You can easily add the 500ms submit here too!
Here's the example:

How to add Radio Button in Formik Validations Reactjs?

I am using Formik for Validating my Registration form I want to add validate gender via radio button how can I do that. I am not able to add radio button.
This is what I have done:-
const SignupSchema = Yup.object().shape({
email: Yup.string()
.email('Invalid email')
.required('Required'),
password: Yup.string()
.min(4, 'Password Must be four characters long!')
.max(20, 'Too Long!')
.required('Required'),
});
class Register extends Component {
render() {
return (
<Formik
initialValues={{
email: '',
password:'',
gender:'',
}}
validationSchema={SignupSchema}
onSubmit={values => {
console.log(values);
}}
>
{({ errors, touched }) => (
<Form>
<Field style={customStyles.textfield} placeholder="Email" name="email" type="email" />
{errors.email && touched.email ? <div}>{errors.email}</div> : null}
<Field placeholder="Enter Password" name="password" type="password" />
{errors.password && touched.password ? <div >{errors.password}</div> : null}
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</div>
)
}
}
change gender initialValues as male
<Field
name="gender"
render={({ field }) => (
<>
<div className="radio-item">
<input
{...field}
id="male"
value="male"
checked={field.value === 'male'}
name="type"
type="radio"
/>
<label htmlFor="male">Male</label>
</div>
<div className="radio-item">
<input
{...field}
id="female"
value="female"
name="type"
checked={field.value === 'female'}
type="radio"
/>
<label htmlFor="female">Female</label>
</div>
</>
)}
/>
This solution gives you the chance to have more than 2 options.
Let say you have a file called Main.js and you want to put a radio button input in it. First, provide a list of options for your radio button in the Main.js
const radioOptions = [
{ key: 'Option 1', value: 'value 1' },
{ key: 'Option 2', value: 'value 2' },
{ key: 'Option 3', value: 'value 3' }
];
Next, create a RadioButton.js file with below code
import React from 'react';
import { Field } from 'formik';
const RadioButton = (props) => {
const { label, name, options, ...rest } = props;
return (
<div>
<label htmlFor={name}>{label}</label>
<Field name={name} {...rest} >
{
({ field }) => {
return options.map(option => {
return (
<React.Fragment key={option.key}>
<input
type='radio'
id={option.id}
{...field}
value={option.value}
checked={field.value === option.value}
/>
<label htmlFor={option.id}>{option.key}</label>
</React.Fragment>
);
})
}
}
</Field>
</div>
);
};
export default RadioButton;
Then put the reusable RadioButton Component in the Main.js wherever you want the radio button input to render.
The result UI will be 3 radio buttons with values "value 1", "value 2" and "value 3".
You can check out this awesome youtube series to know more.
This can be done simply but using this code below. it worked for me:
You will need to import Formik from "formik"
<Formik
initialValues={{
email: '',
password:'',
gender:'',
}}
// validations
validationSchema={SignupSchema}
onSubmit={values => { console.log(values); }}
>
{(formik) => (
<Form onSubmit={formik.handleSubmit}>
//Your Other inputs........
<div className="custom-control">
<input
id="male"
type="radio"
value="male"
name='gender'
onChange={formik.handleChange}
defaultChecked={formik.values.gender=== "male"}
/>
<label
className="custom-control-label"
htmlFor="male"
>
Male
</label>
</div>
<div className="custom-control">
<input
id="female"
type="radio"
value="female"
name='gender'
onChange={formik.handleChange}
defaultChecked={formik.values.gender=== "female"}
/>
<label
className="custom-control-label"
htmlFor="female"
>
Female
</label>
</div>
//Your Other inputs......
<button type="submit">Submit</button>
</Form>
)}
</Formik>

Resources