Dynamic Fields
Sometimes you do not know how many fields your form should have, so you need a way to add dynamic fields on user events, e.g. on a button click if a user should decide if a new field is needed, or on a change of a field, e.g. when a field is filled a new field should appear.
onClick
First off, import your components.
import React from 'react';import {Form,Field,Button,} from 'react-form-package';
The next step is to create a class
that will render our form.
The state is allways handled by react-form-package
, the only thing that you need todo is to handle the appearence of the form, e.g. add or remove the input.
We need create a addField
, removeField
, and optionally a calculateAvailableId
function. The calculateAvailableId
is only nessacary if you work on non unique index-based ids so that you ensure you do not overide an existing id.
The addField
function is here to add a new Field when you click on a <Button />
component.
To update the state of the <Form />
component, you need to add a rfpRole
property and a field
or a fieldId
property to a <Button />
component.
The rfpRole
takes a string which is either addField
or removeField
.
If you use the <Button />
component to add a new field to the state of the form component you need to provide a field
property which takes an object that represents your new <Field />
component. This object has to have at least a id
and a type
, but you can extend this object with rules like: min
, max
, required
, match
, and sameAs
.
If you use the <Button />
component to add remove an existing field from the state of the form component you need to provide a fieldId
property which takes a string: the id
of the field you want to remove.
In the state of the DynamicFields
component you have to create an array where you add refrences to the fields of your dynamic <Field />
components. To add new fields you need to add a new refrence, so that the part of the DOM rerenders with the new Field Component. To remove the Fields not only from the state of the <Form />
component but also from the DOM, you need to remove the refrences in your state that the component rerenders that part of the DOM.
class DynamicFields extends React.Component {constructor(props) {super(props);this.state = {// the field refrences that we use to render in our <Form /> componentcompanies: [{id: 'company-0',},],};this.addField = this.addField.bind(this);this.removeField = this.removeField.bind(this);this.calculateAvailableId = this.calculateAvailableId.bind(this);}calculateAvailableId() {const {companies,} = this.state;const arr = companies.map((item) => parseInt(item.id.split('-')[1], 10));let currentHighestId = Math.max(...arr);currentHighestId = currentHighestId !== -Infinity ? currentHighestId : 0;const highestAvailableId = currentHighestId + 1;return highestAvailableId;}addField() {const {companies,} = this.state;const highestAvailableId = this.calculateAvailableId();// add a new field refrence to the <Form /> componentthis.setState({companies: companies.concat({ id: `company-${highestAvailableId}` }),});}removeField(idx) {const {companies,} = this.state;// remove a field refrence to the <Form /> componentthis.setState({companies: companies.filter((_, index) => idx !== index),});}render() {const {companies,} = this.state;const highestAvailableId = this.calculateAvailableId();return (<Formvalidate>{/* render the <Field /> components based on our field refrences */}{companies.map((company, idx) => (<div><Fieldid={company.id}placeholder={`Company ${company.id.split('-')[1]}`}type="text"required/><Buttonid="removeField"// add the rfpRole propertyrfpRole="removeField"type="button"// add the fieldId property to remove the field from the state of the <Form /> componentfieldId={company.id}onClick={() => this.removeField(idx)}>Remove Company</Button></div>))}<div><Buttonid="addField"// add the rfpRole propertyrfpRole="addField"type="button"// add the field property to add to the state state of the <Form /> componentfield={{id: `company-${highestAvailableId}`,type: 'text',required: true,}}onClick={() => this.addField()}>Add Company</Button></div><div><Buttonid="submit"type="submit"onClick={(state) => {alert(JSON.stringify(state, null, 2));alert('open the console to see the whole state...');console.log(state);}}>submit</Button></div></Form>);}}export { DynamicFields };
Now render this Component. Everytime you click on the button Add Company
you get a new field, and everytime you click on the button Remove Company
you remove the corresponding field.
onChange
First off, import your components.
import React from 'react';import {Form,Field,Button,} from 'react-form-package';
The next step is to create a class
that will render our form.
Everything stays the same as in the example above, except:
That we now use a dynamic
property and the field
property on the <Field />
component. the field
property takes the same properties as in the <Button />
component. The dynamic
property indicates that this <Field />
component is dynamic and adds a new field in the state of the <Form />
component when this <Field />
is filled.
class DynamicFields extends React.Component {constructor(props) {super(props);// the field refrences that we use to render in our <Form /> componentthis.state = {companies: [{id: 'company-0',},],};this.addField = this.addField.bind(this);this.removeField = this.removeField.bind(this);this.calculateAvailableId = this.calculateAvailableId.bind(this);}calculateAvailableId() {const {companies,} = this.state;const arr = companies.map((item) => parseInt(item.id.split('-')[1], 10));let currentHighestId = Math.max(...arr);currentHighestId = currentHighestId !== -Infinity ? currentHighestId : 0;const highestAvailableId = currentHighestId + 1;return highestAvailableId;}addField(state, id) {const {companies,} = this.state;const highestAvailableId = this.calculateAvailableId();// add a new field refrence to the <Form /> componentif (state.data[id] && parseInt(id.split('-')[1], 10) + 1 === highestAvailableId) {this.setState({companies: companies.concat({ id: `company-${highestAvailableId}` }),});}}removeField(idx) {const {companies,} = this.state;// remove a field refrence to the <Form /> componentthis.setState({companies: companies.filter((_, index) => idx !== index),});}render() {const {companies,} = this.state;const highestAvailableId = this.calculateAvailableId();return (<Form>{/* render the <Field /> components based on our field refrences */}{companies.map((company, idx) => (<div><Fieldid={company.id}placeholder={`Company ${company.id.split('-')[1]}`}type="text"required// add the dynamic propertydynamic// add the field property to add to the state state of the <Form /> componentfield={{id: `company-${highestAvailableId}`,type: 'text',required: true,}}onChange={(state) => this.addField(state, company.id)}/>{companies.length && (<Buttonid="removeField"// add the rfpRole propertyrfpRole="removeField"type="button"// add the fieldId property to remove the field from the state of the <Form /> componentfieldId={company.id}onClick={() => this.removeField(idx)}>Remove Company</Button>)}</div>))}<div><Buttonid="submit"type="submit"onClick={(state) => {alert(JSON.stringify(state, null, 2));alert('open the console to see the whole state...');console.log(state);}}>submit</Button></div></Form>);}}export { DynamicFieldsOnChange };
Now render this Component. Everytime you fill out the <Field />
component you will add a new <Field />
, and everytime you click on the button Remove Company
you remove the corresponding field.