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
Related
I'm trying to make a dynamic form where if the user wants to add an additional section to their pages. For instance, if they want to add a "Our Team" section, I'm trying to render an array of nested objects, but I'm kinda confuse since each team member can have image as well.
My backend works when I try on Postman. I'm using multer and Schema looks like this:
const sectionSchema = new Schema({
team: [
{
name: {
type: String,
required: false,
},
jobTitle: {
type: String,
required: false,
},
description: {
type: String,
required: false,
},
images: [
{
filename: {
type: String,
required: true,
},
contentType: {
type: String,
required: true,
},
imageBase64: {
type: String,
required: true,
},
},
],
},
]
});
Problem is when I submit says that:
Warning: An unhandled error was caught from submitForm() TypeError: item.images.forEach is not a function
at BaseFormAditional.js:80:1
at Array.forEach (<anonymous>)
at submitHandler (BaseFormAditional.js:75:1)
at onSubmit (BaseFormAditional.js:123:1)
at Formik.tsx:849:1
at Formik.tsx:1200:1
at Formik.tsx:756:1
Somehow, it does not matter how I handle images, it does not see as an array.
BaseFormAditional.js
const BaseFormAditional = () => {
const initialValues = {
// Team section
teamTitle: "",
team: [
{
name: "",
jobTitle: "",
description: "",
images: [],
},
]
};
const pageId = useParams().pageId;
const navigate = useNavigate();
const submitHandler = async (values) => {
const formData = new FormData();
values.team.forEach((item, index) => {
formData.append(`team[${index}][name]`, item.name);
formData.append(`team[${index}][jobTitle]`, item.jobTitle);
formData.append(`team[${index}][description]`, item.description);
item.images.forEach((image, imageIndex) => {
formData.append(`team[${index}][images][${imageIndex}][filename]`, image.filename);
formData.append(`team[${index}][images][${imageIndex}][contentType]`, image.contentType);
formData.append(`team[${index}][images][${imageIndex}][imageBase64]`, image.imageBase64);
});
});
formData.append("pageId", pageId);
// later axios as formData
};
return (
<div>
<Formik
initialValues={initialValues}
onSubmit={(values) => submitHandler(values)}
>
{({ values, setFieldValue }) => (
<Form>
{/* TEAM SECTION */}
<div>
{showTeamForm && (
<div>
<h1>Team</h1>
<label htmlFor="contact-title">Team Title:</label>
<br />
<Field id="team-title" name="teamTitle" />
<FieldArray name="team">
{({ insert, remove, push }) => (
<div>
{values.team.length > 0 &&
values.team.map((friend, index) => (
<div className="row" key={index}>
<div className="col">
<label htmlFor={`team.${index}.name`}>
Name
</label>
<Field
name={`team.${index}.name`}
placeholder="Jane Doe"
type="text"
/>
<ErrorMessage
name={`team.${index}.name`}
component="div"
className="field-error"
/>
</div>
<div className="col">
<label htmlFor={`team.${index}.jobTitle`}>
Phone number
</label>
<Field
name={`team.${index}.jobTitle`}
placeholder="Marketing"
type="text"
/>
<ErrorMessage
name={`team.${index}.jobTitle`}
component="div"
className="field-error"
/>
</div>
<div className="col">
<label htmlFor={`team.${index}.description`}>
Description
</label>
<Field
name={`team.${index}.description`}
placeholder="About team member"
type="text"
/>
<ErrorMessage
name={`team.${index}.description`}
component="div"
className="field-error"
/>
</div>
<div className="col">
<label htmlFor={`team.${index}.images`}>
Upload image
</label>
<FileInput
name={`team.${index}.images`}
type="file"
value={undefined}
/>
{/* <Field
name={`team.${index}.images`}
type="file"
/> */}
<ErrorMessage
name={`team.${index}.images`}
component="div"
className="field-error"
/>
</div>
<div className="col">
<button
type="button"
className="secondary"
onClick={() => remove(index)}
>
Delete Member
</button>
</div>
</div>
))}
<button
type="button"
className="secondary"
onClick={() =>
push({
name: "",
jobTitle: "",
description: "",
images: "",
})
}
>
Add Member
</button>
</div>
)}
</FieldArray>
</div>
)}
</div>
<div>
<button type="submit">Save</button>
</div>
</Form>
)}
</Formik>
</div>
);
};
export default BaseFormAditional;
The problem is on setting type images form and read this property.
As you can see. When you submit the form, you are setting images as a string or a single file, not as an array.
<div className="col">
<label htmlFor={`team.${index}.images`}>
Upload image
</label>
<FileInput
name={`team.${index}.images`}
type="file"
value={undefined}
/>
{/* <Field name={`team.${index}.images`} type="file"/> */}
<ErrorMessage
name={`team.${index}.images`}
component="div"
className="field-error"
/>
</div>
...
<button
type="button"
className="secondary"
onClick={() =>
push({
name: "",
jobTitle: "",
description: "",
// images: "", Specify an array
images: []
})
}
>
Add Member
</button>
If you check team[0].images on submit, you will check that team[0].images is a file, not an array.
If you want to save image in the array, you need to specify index position when you want to save it.
<div className="col">
<label htmlFor={`team.${index}.images[0]`}>
Upload image
</label>
<FileInput
name={`team.${index}.images[0]`}
type="file"
value={undefined}
/>
{/* <Field name={`team.${index}.images[0]`} type="file"/> */}
<ErrorMessage
name={`team.${index}.images[0]`}
component="div"
className="field-error"
/>
</div>
Furthermore, if your objective is creating dynamic input files, you must to add another fieldArray to manage this property.
// upgrade above code
<FieldArray name={`team.${index}.images`}>
// not destructure, otherwise conflict with push, remove from FieldArray parent
{(fileHelpers) => (
{ team[index].images.map( (image, indexFile) =>
<div className="col">
<label htmlFor={`team.${index}.images`}>
Upload image
</label>
<FileInput
name={`team.${index}.images[0]`}
type="file"
value={undefined}
/>
{/* <Field name={`team.${index}.images[0]`} type="file"/> */}
<ErrorMessage
name={`team.${index}.images[0]`}
component="div"
className="field-error"
/>
// Add button to push another element to array
<button
type="button"
className="secondary"
onClick={() =>
fileHelpers.push({})
}
>
Add File
</button>
</div>
}
)}
</FieldArray>
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:
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>
)
}
I am trying to call the render Input method from render Hobbies method but it is giving an error saying that the render Input property is undefined. I'm not getting the exact issue. It would be great if someone could tell me what is the problem in my code ? Thanks in advance.
Here's the code:
UserForm.js
import React, { Component } from 'react'
import { Field, reduxForm,FieldArray } from 'redux-form'
import Multiselect from 'react-widgets/lib/Multiselect'
import 'react-widgets/dist/css/react-widgets.css'
class UserForm extends Component {
renderInput(formProps) {
const className = `field ${formProps.meta.error && formProps.meta.touched ?
'error' : ''}`
return (
<div className={className}>
<label>{formProps.label}</label>
<input {...formProps.input} type={formProps.type} max={formProps.max} autoComplete='off'
label={formProps.label} id={formProps.id} placeholder={formProps.placeholder}
checked={formProps.input.value} value={formProps.input.value} />
{formProps.meta.touched &&
(formProps.meta.error && <span>{formProps.meta.error}</span>)}
</div>
)
}
renderMultiselect({ input, ...rest }) {
return (<Multiselect {...input}
onBlur={() => input.onBlur()}
value={input.value || []}
{...rest} />)
}
renderHobbies({ fields, meta: { error } }) {return(
<ul>
<li>
<button type="button" onClick={() => fields.push()}>Add Hobby</button>
</li>
{fields.map((hobby, index) =>
<li key={index}>
<button
type="button"
title="Remove Hobby"
onClick={() => fields.remove(index)}/>
<Field
name={hobby}
type="text"
component={this.renderInput}
label={`Hobby #${index + 1}`}/>
</li>
)}
{error && <li className="error">{error}</li>}
</ul>
)
}
onSubmit = (formValues) => {
console.log('formValues', formValues)
this.props.onSubmit(formValues)
}
render() {
const { handleSubmit } = this.props
const current = new Date().toISOString().split("T")[0]
let optionsList = [{ id: 1, name: 'Travelling' }, { id: 2, name: 'Reading' }, { id: 3, name: 'Gaming' }]
let items = [{ name: 'Travelling', value: 'Travelling' }, { name: 'Reading', value: 'Reading' }, { name: 'Gaming', value: 'Gaming' }]
const colleges = ['Pune University', 'S.P.College', 'F.C College']
return (
<div className='container'>
<form onSubmit={handleSubmit(this.onSubmit)}
className='ui form error'>
<div className='row mb-3'>
<label className='col-sm-2 col-form-label'>FullName</label>
<div className='col-sm-10'>
<Field name='fullname' component={this.renderInput}
type='text' className='form-control' placeholder='Full Name'
validate={[required, minLength3]} />
</div>
</div>
<div className='row mb-3'>
<label className='col-sm-2 col-form-label'>Address</label>
<div className='col-sm-10'>
<Field name='address' component={this.renderInput}
type='text' placeholder='Address'
validate={[required, maxLength25]} />
</div>
</div>
<div className='row mb-3'>
<label className='col-sm-2 col-form-label'>BirthDate</label>
<div className='col-sm-10'>
<Field
name='birthdate'
type='date'
max={current}
component={this.renderInput}
validate={required}
/>
</div>
</div>
<div className='row mb-3'>
<label className='col-sm-2 col-form-label'>Select Your Gender</label>
<div className='col-sm-1 ui radio'>
<div className='form-check'>
<label className='form-check-label'>Male</label>
<Field name='gender' component='input' type='radio' value='male'
className='ui input' />{' '}
</div>
<div className='form-check'>
<label className='form-check-label'>Female</label>
<Field name='gender' component='input' type='radio' value='female'
/>{' '}
</div>
<div className='form-check'>
<label className='form-check-label'>Other</label>
<Field name='gender' component='input' type='radio' value='other'
/>{' '}
</div>
</div>
</div>
<div className='row mb-3'>
<label className='col-sm-2 col-form-label'>Select Your Hobbies</label>
<div className='col-sm-10'>
<Field
name='hobbies'
component={this.renderMultiselect}
data={['Travelling', 'Gaming', 'Reading', 'Drawing']} />
</div>
<div>
<FieldArray name='hobbies' component={this.renderHobbies}/>
</div>
</div>
<div className='row mb-3'>
<label className='col-sm-2 col-form-label'>Select College</label>
<div className='col-sm-10'>
<Field name='college' component='select' placeholder='Select College' validate={required}>
<option value="">Select a college</option>
{colleges.map(collegeOption => (
<option value={collegeOption} key={collegeOption}>
{collegeOption}
</option>
))}
</Field>
</div>
</div>
<button type='submit' className='ui button'>Submit</button>
</form>
</div>
)
}
}
const required = value => (value || typeof value === 'number' ? undefined : 'Required')
const maxLength = max => value => value && value.length > max ? `Must be ${max} characters
or less`: undefined
const maxLength25 = maxLength(25)
const minLength = min => value => value && value.length < min ? `Must be ${min} characters or
more`: undefined
const minLength3 = minLength(3)
export default reduxForm({
form: 'userform'
})(UserForm)
That's the scope problem,
this is undefined in renderHobbies.
just binding the renderHobbies will work
<div>
<FieldArray name='hobbies' component={this.renderHobbies.bind(this)}/>
</div>
My code is working, but I got a warning :
Warning: As of React Final Form v3.3.0, props.mutators is deprecated
and will be removed in the next major version of React Final Form.
Use: props.form.mutators instead. Check your ReactFinalForm render
prop.
with this exemple :
https://codesandbox.io/s/kx8qv67nk5
return <Form
onSubmit={() => console.log('ok')}
mutators={{
...arrayMutators
}}
initialValues={ {customers: [{firstName: 'a', lastName: 'b'}]} }
render={({
handleSubmit,
mutators: { push, pop }, // injected from final-form-arrays above
pristine,
reset,
submitting,
values
}) => {
return (
<form onSubmit={handleSubmit}>
<div className="buttons">
<button
type="button"
onClick={() => push('customers', undefined)}>
Add Customer
</button>
</div>
<FieldArray name="customers">
{({ fields }) =>
fields.map((name, index) => (
<div key={name}>
<label>Cust. #{index + 1}</label>
<Field
name={`${name}.firstName`}
component="input"
placeholder="First Name"
/>
<Field
name={`${name}.lastName`}
component="input"
placeholder="Last Name"
/>
<GithubField name="user1" onSearch={(item) => {
this.api.getByFirstname(item).then(result => console.log(result));
}} />
<span
onClick={() => fields.remove(index)}
style={{ cursor: 'pointer' }}>❌</span>
</div>
))}
</FieldArray>
<div className="buttons">
<button type="submit" disabled={submitting || pristine}>
Submit
</button>
<button
type="button"
onClick={reset}
disabled={submitting || pristine}>
Reset
</button>
</div>
<pre>{JSON.stringify(values, 0, 2)}</pre>
</form>
)
}}
/>
How to do the right way ?
The warning already tells you what to do.
You must use form.mutators instead of just mutators
To do so, you can change your code from
mutators: { push, pop }
to
form: { mutators: { push, pop } }