How can I pass the input id with the name and value in redux form? - reactjs

I'm following along with one of redux-form's tutorials and I'm unable to find a reference on how to properly pass an input's id when submitting the form. Currently, the key value pair submits just fine, but if I want to add the id per input as well, how can I achieve that?
demo
const renderField = (props) => (
<div>
<label>{props.label}</label>
<div>
<input {...props.input} placeholder={props.label} type={props.type} id={props.id}/>
{props.meta.touched && ((props.meta.error && <span>
{props.meta.error}</span>) || (props.meta.warning && <span>
{props.meta.warning}</span>))}
</div>
</div>
)
const FieldLevelValidationForm = (props) => {
const { handleSubmit, pristine, reset, submitting } = props
return (
<form onSubmit={() => handleSubmit(this, props.id)}>
<Field name="username" type="text"
component={renderField} label="Username"
id="user"
validate={[ required, maxLength15 ]}
/>
<Field name="email" type="email"
component={renderField} label="Email"
id="userEmail"
validate={email}
warn={aol}
/>
<Field name="age" type="number"
component={renderField} label="Age"
id="userAge"
validate={[ required, number ]}
warn={tooOld}
/>
<Field name="favoriteColor" component="select" id="userColor">
<option value="ff0000">Red</option>
<option value="00ff00">Green</option>
<option value="0000ff">Blue</option>
</Field>
<div>
<button type="submit" disabled={submitting}>Submit</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>Clear Values</button>
</div>
</form>
)
}

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

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:

Formik values not updating in Input form but state updates

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>
)
}

Declaring redux form in both the child and parent component causes error

Uncaught Error: asyncValidate function passed to reduxForm must return a promise
Declaring multiple redux forms in both child and parent component causes the error above. I feel like it's a bug actually.
I have a parent component called WorkExperience, where I render multiple child components called DashboardListItem, which is the work experience a user has.
In the parent component I have a redux form to create a work experience. Also, I have other redux forms inside the child component where I can toggle edit forms for each list item.
The structure is same as this.
WorkExperience (has postWorkExperienceForm)
DashboardListItem (has edit form with populated initial values)
DashboardListItem
DashboardListItem
So, this structure is causing the error when I type into toggleable edit form. If I remove redux form declaration either from the parent or the child component, everything becomes normal.
Also all the forms are in the store too.
Thank you
Parent Component
renderWorkExperience(){
const workExperience = this.props.candidate.workExperience;
return Object.keys(workExperience).map((key, index) => {
let date = `${workExperience[key].startYear}-${workExperience[key].endYear}`
return <DashboardListItem key={index} {...this.props}
title={workExperience[key].companyName}
subTitle={workExperience[key].title}
meta={date}
summary={workExperience[key].summary}
initialValues={workExperience[key]}
form={workExperience[key]._id} />
});
}
renderForm(){
const activeClass = this.state.displayForm ? 'btn btn-success btn-block mt8' : 'btn btn-primary btn-block mt8'
const { handleSubmit } = this.props;
return(
<form onSubmit={ handleSubmit(this.onSubmit) } className="form-horizontal">
<div className={this.state.displayForm ? 'd-block mb8' : 'd-none'}>
<Field name="companyName"
type="text"
label="Company Name"
placeholder="Apple inc."
id="input-company-name"
component={renderHorizontalTextField} />
<Field name="title"
type="text"
label="Title"
placeholder="Marketing Specialist"
id="input-title"
component={renderHorizontalTextField} />
<Field name="startYear"
type="text"
label="Start Year"
placeholder=""
id="input-start-year"
component={renderHorizontalTextField} />
<Field name="endYear"
type="text"
label="End Year"
placeholder="Blank if current"
id="input-end-year"
component={renderHorizontalTextField} />
<Field name="summary"
rows="4"
label="Summary"
placeholder="Summary..."
id="input-summary"
component={renderTextAreaFieldWithLabelAndPopover} />
</div>
<button type={this.state.displayForm ? "button" : "submit"}
className={activeClass}
onClick={this.handleClick}>{ !this.state.displayForm ?
'Add Work Experience' : 'Save' }
</button>
</form>
)
}
export default reduxForm({
form: 'postWorkExperienceForm'
})(WorkExperience);
Child Component
renderForm(){
const activeClass = this.state.displayForm ? 'btn btn-success btn-block mt8' : 'btn btn-primary btn-block mt8';
const { handleSubmit } = this.props;
return(
<form onSubmit={ handleSubmit(this.onSubmit) } className="form-horizontal">
<div className={this.state.displayForm ? 'd-block mt8' : 'd-none'}>
<Field name="companyName"
type="text"
label="Company Name"
placeholder="Apple inc."
id="input-company-name"
component={renderHorizontalTextField} />
<Field name="title"
type="text"
label="Title"
placeholder="Marketing Specialist"
id="input-title"
component={renderHorizontalTextField} />
<Field name="startYear"
type="text"
label="Start Year"
placeholder=""
id="input-start-year"
component={renderHorizontalTextField} />
<Field name="endYear"
type="text"
label="End Year"
placeholder="Blank if current"
id="input-end-year"
component={renderHorizontalTextField} />
<Field name="summary"
rows="4"
label="Summary"
placeholder="Summary..."
id="input-summary"
component={renderTextAreaFieldWithLabelAndPopover} />
<button className="btn btn-success" type="submit">Save</button>
</div>
</form>
)
}
export default reduxForm({
enableReinitialize: true
})(
connect(null, { updateWorkExperience })(DashboardListItem)
);
Found out that a similar question is asked here. Although he hasn't fixed the problem, found a way around.
Redux Form - "form={ }" and "initialValues={ }" properties not recognized with multiple forms (redux-form v7.0.4)
I ended up putting work experience form into a separate component called WorkExperienceForm and imported it into WorkExperience and DashboardListItem components. This solved the issue.
Form names are passed to WorkExperienceForm from the parent components.
I was planning to put the forms into a separate component anyway, but wanted to solve the problem. So the bug is still there if it is one.
WorkExperienceForm
import React from 'react';
import { Field, reduxForm } from 'redux-form';
import { renderHorizontalTextField } from '../Fields/TextFields';
import { renderTextAreaFieldWithLabelAndPopover } from '../Fields/TextAreaFields';
const WorkExperienceForm = ({ handleSubmit, onSubmit }) => {
return(
<form onSubmit={ handleSubmit(onSubmit) } className="form-horizontal">
<Field name="companyName"
type="text"
label="Company Name"
placeholder="Apple inc."
id="input-company-name"
component={renderHorizontalTextField} />
<Field name="title"
type="text"
label="Title"
placeholder="Marketing Specialist"
id="input-title"
component={renderHorizontalTextField} />
<Field name="startYear"
type="text"
label="Start Year"
placeholder=""
id="input-start-year"
component={renderHorizontalTextField} />
<Field name="endYear"
type="text"
label="End Year"
placeholder="Blank if current"
id="input-end-year"
component={renderHorizontalTextField} />
<Field name="summary"
rows="4"
label="Summary"
placeholder="Summary..."
id="input-summary"
component={renderTextAreaFieldWithLabelAndPopover} />
</form>
)
}
export default reduxForm({
enableReinitialize: true
})(WorkExperienceForm);

Focus kickout on custom redux form input

I got this redux form input :
<Field
component={MyCustomComponent}
name={name}
type={type}
/>
Using this custom element (bootstrap) :
<FormGroup controlId='login'>
<FormControl name={name} placeholder={name} value={props.value} onChange={input.onChange} {...props} />
</FormGroup>
And I use it like so :
<Input type='text' name='email' placeholder='Email' {...email} />
<Input type='password' name='password' placeholder='Password' {...password} />
Something strange happens. When I select the input and I try to write something inside, I am automatically kicked out of the input (focus).
When I try to write it again, everything works.
It's only when I select it for the first time after that the page is loaded.
Full form :
<Form onSubmit={handleSubmit(this.onLogin)}>
<div>
<Input type='text' name='email' placeholder='Email' {...email} />
<Input type='password' name='password' placeholder='Password' {...password} />
</div>
<Button type='submit' bsStyle='primary' bsSize='large' active>
<FormattedMessage
id='login.select-label'
defaultMessage='Login'
/>
</Button>
</Form>
Full input :
export const Input = (props) => {
const { name, type } = props
const MyCustomComponent = ({input}: props) => (
<FormGroup controlId='login'>
<FormControl name={name} placeholder={name} value={props.value} onChange={input.onChange} {...props} />
</FormGroup>
)
return (
<Field
component={MyCustomComponent}
name={name}
type={type}
/>
)
}

Resources