Handling nested inputs in React
Tags: react, forms, frontend
Published: 2020-11-06 05:05:35

It's common for a form to have nested input fields. For example, think of a form that is intended to be filled by company officials with the following constraints, 
1. The companies may have multiple branches
2. Each branch has multiple contact numbers 
Obviously there are two levels of nesting involved. For simplicity, let the json-ified structure of the company look like this, 
```json
{
      name: "",
      branches: [
        {
          location: "",
          contacts: [""]
        }
      ]
    }
```
To begin with, the initial state of the component will essentially be the above json object. Next, the add input field functionalities need to be implemented, it's achieved merely by modifying the state object,

```js
  const addBranch = (e) => {
    let temp = {...state};
    temp.branches.push(
      {
        location: "",
        contacts: [""]
      }
    );
    setState(temp);
  };

  const addContact = (e, i) => {
    let temp = {...state};
    temp.branches[i].contacts.push('');
    setState(temp);
  };
```
The delete field functionality works much like that of adding, except instead of pushing into state, we delete the elements with the arrary.prototype.splice() function, here's how, 

```js
  const deleteBranch = (e, i) => {
    let temp = {...state}
    temp.branches.splice(i, 1)
    setState(temp)
  }

  const deleteContact= (e, i, j) => {
    let temp = {...state}
    temp.branches[i].contacts.splice(j, 1)
    setState(temp)
  }
```

Next, to close the JS chapter, let's define the functions to handle changes in the fields. Here are those with a submit function that for now console-logs the state object, 

```js
  const handleNameChange = e => {
    let temp = {...state}
    temp[e.target.name] = e.target.value
    setState(temp)
  } 

  const handleBranchChange = (e, i) => {
    let temp = {...state}
    temp.branches[i][e.target.name] = e.target.value
    setState(temp)
  } 

  const handleContactChange = (e, i, j) => {
    let temp = {...state}
    temp.branches[i].contacts[j] = e.target.value
    setState(temp)
  } 

  const submit = e => {
    console.log(state)
  }

```

Finally, let's talk about the JSX structure. There'll be three groups of input field (TextField, since I'm using Material-UI here). These are, name field, branch location field and the fields for individual contacts details. The basic construct will be much like,
```html
<div style={{ padding: '50px', width: '60%', margin: 'auto'}}>
      <h2 style={{textAlign: 'center', padding: '30px 0'}}>Company Details</h2>
      <TextField variant='outlined' name='name' style={styles1} placeholder='Name of the company'
        onChange={handleNameChange} value={state.name}
      />
      {
        state.branches.map((branch, i) => (
          <div style={{padding: '25px'}}>
            <span style={{fontSize: '18px'}}>Branch {i+1}: </span>
            <TextField variant='outlined' name='location' placeholder='Location of branch'
              style={styles2} onChange={e => handleBranchChange(e, i)} value={state.branches[i].location}
            />
            <Button variant='contained' color='secondary' style={{marginLeft: '10px'}}
              onClick={e => deleteBranch(e, i)}
            ><Delete style={{ fontSize: 20}}/></Button>
            {
              branch.contacts.map((contact, j) => (
                <div style={{padding: '10px'}}>
                  <span style={{fontSize: '18px'}}>Contact {j+1}: </span>
                  <TextField variant='outlined' name='contact' placeholder='Contact'
                    style={styles3} onChange={e => handleContactChange(e, i, j)} 
                    value={state.branches[i].contacts[j]}
                  /> 
                  <Button variant='contained' color='secondary' style={{marginLeft: '10px'}} 
                    onClick={e => deleteContact(e, i, j)}
                  ><Delete style={{ fontSize: 20}}/></Button>
                </div>
                
              ))
            }
            <Button variant='contained' color='primary' onClick={e => addContact(e, i)}>Add Contact</Button>
          </div>
        ))
      }
      <Button variant='contained' color='primary' onClick={addBranch}>Add Branch</Button> <br/><br/>
      <Button variant='contained' size='large' color='secondary' onClick={submit}>Submit</Button>
    </div>
```
Note that I've excluded CSS to the entirety except for the inline styles. [Here's a link to the editable playground.]( 
https://codesandbox.io/s/fervent-chatelet-xnivd?)