How to implement a dynamic form with controlled components in ReactJS?

Martin Shishkov picture Martin Shishkov · Feb 18, 2017 · Viewed 48.3k times · Source

As I am looking at the examples in the reference for controlled form components in react.js official website, I am wondering how is one supposed to implement a form in which you would be able to remove and add input elements dynamically in such a way that they are controlled components? Is this even possible?

In the examples we can see:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

Due to the nature of my work, I often find myself having to implement such forms. Moreover, I don't add or remove input elements directly - I am managing custom components, but for the sake of simplicity here I am asking for basic form elements.

Answer

Mayank Shukla picture Mayank Shukla · Feb 18, 2017

How adding/removing input elements dynamically possible?

Yes, it is possible, you can add/remove input elements dynamically, But for that you need to take care of few things:

1- Proper binding of events.

2- Array to store the values of each input element separately.

3- When user fill value in any input element then updating only that specific value in state.

Logic:

Maintain an array inside state, that will store the values. Use #array.map to create ui (input element) for each array values. while creating the fields, use a remove button with each field, and pass the index of field in that function, it will help you to identify which field you wants to delete, do the same thing for onChange also.

Check this example:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { values: [] };
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  createUI(){
     return this.state.values.map((el, i) => 
         <div key={i}>
    	    <input type="text" value={el||''} onChange={this.handleChange.bind(this, i)} />
    	    <input type='button' value='remove' onClick={this.removeClick.bind(this, i)}/>
         </div>          
     )
  }

  handleChange(i, event) {
     let values = [...this.state.values];
     values[i] = event.target.value;
     this.setState({ values });
  }
  
  addClick(){
    this.setState(prevState => ({ values: [...prevState.values, '']}))
  }
  
  removeClick(i){
     let values = [...this.state.values];
     values.splice(i,1);
     this.setState({ values });
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.values.join(', '));
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
          {this.createUI()}        
          <input type='button' value='add more' onClick={this.addClick.bind(this)}/>
          <input type="submit" value="Submit" />
      </form>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id='container'/>

Check the working jsfiddle: https://jsfiddle.net/mayankshukla5031/ezdxg224/