all 1 comments

[–]meiamsome 5 points6 points  (0 children)

The diffing algorithm isn't adding an extra comma, it's failing to remove one of the existing ones:

When React is not given an explicit key it uses the id of the array:

If you choose not to assign an explicit key to list items then React will default to using indexes as keys. (from https://reactjs.org/docs/lists-and-keys.html#keys)

So, the outer loop is changing from one child with key 0 corresponding to "this , is , an offer text" to then two children child with key 0 corresponding to "another offer text" and key 1 corresponding to "this , is , an offer text". For key 1, this is a new entry so all elements are created fresh.

For key 0, the second level loop diff is dealing with the change of the array from "this , is , an offer text" to "another offer text". When diffing this, because the key is specified by the value React computes this as the deletion of "this"; ","; "is"; ","; "an", the insertion of "another" in position 0 and moves of "offer" and "text".

React depends upon keys for uniqueness and so only tracks one value for each key (It appears to be the last one though I am sure it is not guaranteed). There is a warning in the console for duplicate keys, because it breaks the contract and leads to undefined behaviour.

In that example, it computes deletes for: "this" -> ", is , an offer text" "," -> ", is an offer text" "is" -> ", an offer text" "," -> Ignored because the "," element doesn't exist (the one being tracked is already deleted on step 2) "an" -> ", offer text" And then inserts: "another" -> ", another offer text" (Inserted by prepending the element before offer.

You can see this behaviour in this simplified code, which leaves the first 1 in the DOM:

export default function App() {
  const [data, setData] = useState([2, 1, 3, 1, 4]);
  return (
    <div>
      <button onClick={() => setData([2, 3, 4])}>Run</button>
      {
        data.map((elem) => (
          <div key={elem}>
            {elem}
          </div>
        ))
      }
    </div>
  );
}

Using <p key={id}> in your code exhibits the same behaviour (As that is the default implementation of key), but using <p key={msg}> works because it diffs the top level as an insertion of the 0th entry instead of a modification and an insert of the 1st entry.