DEV Community

loading...
Cover image for Handling nested inputs in React

Handling nested inputs in React

Hussain Md. Safwan
・2 min read

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,
{
      name: "",
      branches: [
        {
          location: "",
          contacts: [""]
        }
      ]
    }
Enter fullscreen mode Exit fullscreen mode

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,

  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);
  };
Enter fullscreen mode Exit fullscreen mode

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,

  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)
  }
Enter fullscreen mode Exit fullscreen mode

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,

  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)
  }

Enter fullscreen mode Exit fullscreen mode

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,

<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>
Enter fullscreen mode Exit fullscreen mode

Note that I've excluded CSS to the entirety except for the inline styles. Here's a link to the editable playground.

Discussion (0)