Mini-Tutorial: Deleting Dynamically Added Form Elements in React

Cailin Kless
3 min readMay 20, 2021
Photo by Gary Chan on Unsplash

This is a quick spinoff of Mike Cronin’s excellent ITNEXT tutorial on building dynamic controlled forms. In it, we’ll assume that you’ve just built a similar form of your own — in my case, a playbill form where a user can add as many fields for performer credits as they want — but want to be able to delete these fields as well. This can actually be implemented with just a few more lines of code!

First, we need to add a button onto our user-generated fields so that we’ll have something to trigger the deletion. At the end of the return in my CreditInputs component (CatInputs in the original example), just before the closing </div> tag, I added:

<button data-idx={idx} onClick={handleDeleteCredit}>Delete Credit</button>

This gives the button the same id number as the input field it is on so we can isolate which of the credits is being deleted, and sets up an event listener that will trigger our delete function when the button is clicked.

While we’re in the file for this component, let’s update our PropTypes below:

CreditInputs.propTypes = {   idx: PropTypes.number,   creditState: PropTypes.array,   handleCreditChange: PropTypes.func,   // NEW LINE BELOW: Assign handleDeleteCredit a PropType   handleDeleteCredit: PropTypes.func};

Let’s also adjust the top of our component so that it will be able to take in that handleDeleteCredit function we’re about to build and pass in:

const CreditInputs = ({ idx, creditState, handleCreditChange, handleDeleteCredit }) => ...

Next, let’s head to our main Form component and build that function by creating an array that is all the current credits except the one being deleted, and set the credit state to reflect new list:

const handleDeleteCredit = (e) => {   e.preventDefault();   const updatedCredits = [...creditState];   updatedCredits.splice(parseInt(e.target.dataset.idx), 1)   setCreditState(updatedCredits);}

We’re splicing out the element in the array whose index matches the specified idx number using parseInt since that data is currently in string form.

Don’t make my initial mistake and forget to put a preventDefault at the top! This took me by surprise since I’m used to only needing it for submit buttons, but if you leave it out in this case, a user will only be able to delete the last added field. For example, if the user adds 3 credit fields they could delete credit number 3 with no issue, but if they instead wanted to delete number 2, it would trigger a reindexing of the credits array and refresh the whole form, wiping out all previously entered data. Using preventDefault avoids this.

Now that we’ve got our function, we just need to pass it into our CreditInputs component. Our creditState.map in the Form component’s return will now look like this:

{   creditState.map((val, idx) => (      <CreditInputs      key={`credit-${idx}`}      idx={idx}      creditState={creditState}      handleCreditChange={handleCreditChange}      // NEW: Pass each credit the handleDeleteCredit function      handleDeleteCredit={handleDeleteCredit}      />   ))}

And you’re done! You can now delete any field you want, in any order you want, even your first field that was automatically generated.

You can view the gist here if you want to see how it all looks together. If the differences between Mike Cronin and my use cases is confusing, you can see my original implementation prior to adding delete functionality here. Hope this has been as helpful to you as the original code-along was to me!

--

--

Cailin Kless

Full stack developer and UX research & design student with a background in theater, events management, and yoga. Octopus enthusiast.