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

Solution 1:

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/

Solution 2:

How adding/removing input elements dynamically with functional components?

The same component from the selected answer reviewed and rewrited as a functional component.

import React from 'react';
import { useState } from 'react';

function DynamicInput() {

    const [values, setValues] = useState({ val: []});

      function createInputs() {
        return values.val.map((el, i) =>
          <div key={i}>
            <input type="text" value={el||''} onChange={handleChange.bind(i)} />
            <input type='button' value='remove' name={i} onClick={removeClick.bind(i)} />
          </div>
        );
      }

    function handleChange(event) {
      let vals = [...values.val];
      vals[this] = event.target.value;
      setValues({ val: vals });
    }

    const addClick = () => {
      setValues({ val: [...values.val, '']})
    }

    const removeClick = (event) => {
      let vals = [...values.val];
      let index = Number(event.target.name);
      vals.splice(index, 1);
      setValues({ val: vals });
    }

    const handleSubmit = event => {
      alert('A name was submitted: ' + values.val.join(', '));
      event.preventDefault();
    }

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

}

export default DynamicInput;

Solution 3:

If you want to use an object because you have many input field you can do this :

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

class App extends React.Component {
    constructor(props) {
      super(props);
      this.state = { values: [{question: '',response: ''}] };
      this.handleSubmit = this.handleSubmit.bind(this);
    }
    
     createUI(){
       return this.state.values.map((el, i) => 
           <div key={i}>
              <input type="text" value={el.question}  onChange={this.handleChange.bind(this, i)} />
              <textarea type="text"  value={el.response} 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.id] =   event.target.value;
       this.setState({ values }); 
    }
    
    addClick(){
      this.setState(prevState => ({ values: [...prevState.values, {question: '',response: '',}]}))
    }
    
    removeClick(i){
       let values = [...this.state.values];
       values.splice(i,1);
       this.setState({ values });
    }
    
    handleSubmit(event) {
      event.preventDefault();
      alert(this.state.values.map(objet=> "{"+ "  "  + "'question' :" + "  " + objet.question + "  " + "'response' :" + "  " + objet.response +"  " + "}") );
    }
     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>

<!-- begin snippet: js hide: false console: true babel: false -->
              <input type="text"  value={el.response} onChange={this.handleChange.bind(this, i)}  id="response" a />
          
         <input type='button' value='remove' onClick={this.removeClick.bind(this, i)}/>
        
         
       </div>
      
       )
    }
  
    handleChange (i,event) {
       let values = [...this.state.values];
       values[i][event.target.id] =   event.target.value;
       this.setState({ values }); 
    }
    
    addClick(){
      this.setState(prevState => ({ values: [...prevState.values, {question: '',response: '',}]}))
    }
    
    removeClick(i){
       let values = [...this.state.values];
       values.splice(i,1);
       this.setState({ values });
    }
  
    handleSubmit(event) {
      event.preventDefault();
      alert(this.state.values.map(objet=> "{"+ "  "  + "'question' :" + "  " + objet.question + "  " + "'response' :" + "  " + objet.response +"  " + "}") );
    }
    render() {
      return (
        <form onSubmit={this.handleSubmit}>
            {this.createUI()}   
            <div  className="d-flex justify-content-end">
              <KamaminiButton onClick={this.addClick.bind(this)}  text='Ajouter une autre question' />
            </div>     
            <input type="submit" value="Submit" />
        </form>
      );
    }
  }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='container'/>