Form Event Fires on Button Render : A Pesky Gotcha in React's Rendering Process
Tags: react, javascript
Published: 2021-05-25 22:11:02

This post is a note that explains the issue and its cause and solution(s).

I created the web app that fetches the users' information and shows them as a list. It also has the functions to edit or delete them.

The final code is [here](https://github.com/shiv-chan/react-group-assingment/tree/main/viki-kaho).

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w3xbk1mbjt6jflvxg0mm.png)

# What is the issue?
The issue was that the edit button seemed not working.

The code of the component with the issue is [here](https://github.com/shiv-chan/react-group-assingment/blob/b85143b9dd4a8d801ab3a61f5a021e1b9baf2b30/viki-kaho/src/Comment.js).
You can also interact with the code [here](https://codesandbox.io/s/react-form-and-button-issue-7gepy).


# What causes?
Actually, the edit button works fine.

The reason why it seemed not working is because the edit button's `onClick` event ends after the component gets re-rendered.

### Inspection

Added `console.log` and checked what happens when clicking the edit button.

```
loaded!  // the page loaded
editComment is now: false // initial state

// click the edit button

Edit button is clicked! 
editComment is now: true 
handleSave is called! 
editComment is now: false 
```

According to the logs, the following happens under the hood.
1. the edit button is clicked.
2. the edit button's `onClick` event runs and updates state `editComment` which is now `true`. (It was `false` as an initial state)
3. the component gets re-rendered.
4. `handleSave` function is executed for some reason and updates state `editComment` back to `false`.
5. the component gets re-rendered.

The edit button works but the save button, I mean, `handleSave` function gets executed at the same time. 

Since these things happen very quickly, we cannot see it and it looks the edit button is not working.

The following code is the simplified version of the render part of the Comment component.
```jsx
render(){
  return this.state.editComment ? (
    <tr>
     <td><form id="form1" onSubmit={this.handleSave}></form></td>
     <td><input form="form1" type="text" name="name"/></td>
     <td><input form="form1" type="email" name="email"/></td>
     <td><input form="form1" type="text" name="body" /></td>
     <td><button form="form1" type="submit">Save</button></td>
    </tr>
  ):(
  <tr>
   <td />
   <td>{this.state.name}</td>
   <td>{this.state.email}</td>
   <td>{this.state.body}</td>
   <td>
    <button onClick={() => this.setState({ editComment: true })}>Edit</button>
    <button onClick={() => handleDelete()}>Delete</button>
   </td>
  </tr>
  )
}
```
state `editComment` is `false` at first, so there shouldn't be `form` and the save button yet.

Weird!

Then why is `handleSave` function called?
Again, it is because **the edit button's `onClick` event ends after the component gets re-rendered.**

### Facts
After clicking the edit button, `form` gets created.

Since the edit button and the save button lie in the similar structure, so React regards these two as the DOM elements of the same type. In other word, React cannot differentiate these two buttons.

```jsx
// simplified version
// before re-render
  <tr>
   <td />
   <td>
    <button onClick={() => this.setState({ editComment: true })}>Edit</button>
    <button onClick={() => handleDelete()}>Delete</button>
   </td>
  </tr>

 

// after re-render
  <tr>
   <td>
    <form id="form1" onSubmit={this.handleSave}></form>
   </td>
   <td>
    <button form="form1" type="submit">Save</button>
   </td>
  </tr>
```

> When comparing two React DOM elements of the same type, React looks at the attributes of both, keeps the same underlying DOM node, and only updates the changed attributes.

https://reactjs.org/docs/reconciliation.html#dom-elements-of-the-same-type

So, the edit button is not destroyed. It remains there and just gets updated its attributes and properties.

It is still the edit button with extra attributes such as `from="form1"` or `type="submit"` saying "save", so to speak.

Then still *the button*'s `onClick` persists.

When *the button*'s `onClick` event ends, *the button* is associated with `form` and calls `handleSave` function.

# Solution(s)
1. **Add `e.preventDefault()` to the edit button's `onClick`.**
    It won't call `onSubmit`(= `handleSave` function) in `form`.

2. **Create new components for each DOM underlying the condition inside `render()`.** 
    When the component is re-rendered, the new button(= the save button) is created rather than updating the existed button(= the edit button).
    The edit button's `onClick` event is no longer listened.

3. **Add `key` to the edit button and save button respectively.**
    Inform React that these two buttons are different by adding `key`.
    https://reactjs.org/docs/reconciliation.html#keys

---

Apparently, this is a super niche edge case.

Using a table layout or placing the form's items outside `form` may cause the issue.

Considering accessibility or readability carefully when building the structure could prevent errors.

This is the lesson that I learned this time! 

### Acknowledgements
To understand this issue clearly, I popped into a bunch of web dev communities and asked around for this.

I'm really grateful to people in these communities for trying to help me with this. Again, thank you so much:pray:

Special thanks to [Kohei Asai]( https://www.kohei.dev/), [Daniel Corner](https://twitter.com/danielacorner), [Brandon Tsang]( https://brandontsang.net/), [Shogo Wada](https://twitter.com/wada_shogo).