html table is duplicating defaultValue's for no apparent reason

Issue

I have some data that is displayed in a table. When I add a table data cell (td) with JS the defaultValue and innerText of the previous table data cell (td) is being copied in the new table data (td). I don’t understand why this happens? I just add a new html element which, in essence, has nothing to do with the already existing element I would say.

 const [dataSet, setDataSet] = useState([
{
  City: "Amsterdam",
  Provence: "North-Holland"
},
{
  City: "Rotterdam",
  Provence: "South-Holland"
}
]);
    
  const newCity = () => {
    const city = [
      {
        City: "Groningen",
        Provence: "Groningen"
      }
    ];

    setDataSet(city);
  };
<button onClick={newCity}>Add city</button>

<table>
 <tr>
  <th>City</th>
  <th>Provence</th>
 </tr>
 {dataSet.map(city => (
  <tr>
   <td>
     <input defaultValue={city.City}/>
   </td>
   <td>
    <input defaultValue={city.Provence}/>
   </td>
 </tr>
 )}
</table>

So when I click on the ‘newCity button’ a new table row (tr) is added, but instead of populating the table data cells (td) with the correct data (City: ‘Groningen’, Provence: ‘Groningen’) it populates the td with the data from the previous table row (tr) (City: ‘Amsterdam’, Provence: ‘North-Holland’). This seems really odd to me, since I only add a new html element that has no relation to the table row (tr) before it exempt for it being in the same table.

Here is a codebox link to see the behavior in action:
https://codesandbox.io/s/dry-water-rss2m8?file=/src/App.js

Why does it behave like this?

Solution

There are two important things to take into consideration here.

First, your program is outputting a warning:

Warning: Each child in a list should have a unique "key" prop.

Always deal with warnings. They are there for a reason!

Second, you are dealing with a default (as opposed to current) value.


When you replace the array with a new array, React generates new JSX and compares it to the existing DOM.

The first row has the same (lack of) key so it changes the default values of the existing row. (The second row no longer exists so it removes it).

Changing the default values doesn’t have any effect through: The inputs already have values, which they continue to display.


You could give the generated rows keys:

<tr key={something}>

… but you will need to figure out what the something is.

You might use the city name, but it seems like that is a piece of data which might change — so changing the city name would make it look like a new row and not an update to an existing row.

You might consider using the uuid module to generate a unique ID when the object is created.


You could also make the inputs controlled by passing them values instead of default values. This would require that you update the value you pass to them as they are edited.

Answered By – Quentin

Answer Checked By – Willingham (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.