facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
229.56k stars 47k forks source link

Bug: duplicate keys cause an extra element to render, while the console shows the correct number of items #31343

Open Phurba-Sherpa opened 1 month ago

Phurba-Sherpa commented 1 month ago

When rendering a list with duplicate keys, React incorrectly renders an extra item, which is interactable, despite the console logging the correct number of items. This behavior occurs even though the data source has the correct number of items.

React version: ^18.0.0

Steps To Reproduce

  1. $ npx create-react-app my-app
  2. $ cd my-app
  3. Replace the contents of App.js with the code listed below
  4. $ npm run start
  5. Open the page in Google Chrome or Edge or any browser
  6. Click one of the "Add record" button
import { useState } from "react";

const initialRecords = [
  { id: 1, name: "John Wick", email: "john@wick" },
  { id: 2, name: "Hermione Granger", email: "hermione@granger" },
  { id: 3, name: "Harry Potter", email: "harry@potter" },
  { id: 3, name: "ID Dup", email: "id@duplicate" },
];

export default function App() {
  const [records, setRecords] = useState(initialRecords);

  const handleAddClick = () => {
    const newRecord = { id: 4, name: "New Record", email: "new@record" };
    setRecords((prev) => [newRecord, ...prev]);
  };

  return (
    <div>
      <button onClick={handleAddClick}>Add record</button>
      {records.map(({ id, name, email }) => (
        <div key={id}>
          <p>{id}</p>
          <p>{email}</p>
          <p>{name}</p>
        </div>
      ))}
    </div>
  );
}

Link to code example: https://codesandbox.io/p/sandbox/ylr42w

The current behavior

  1. React renders an extra (phantom) item that duplicates an existing item (due to the duplicate key). And the count of extra item (id: 3) keeps increasing on every click.
  2. This extra item is fully interactable (e.g., it responds to click events).
  3. The console logs the correct number of items (5), but 6 items appear on the screen

The expected behavior

  1. The number of rendered items should match the actual number of items in the data array, regardless of key duplication.
  2. If keys are not unique, React should throw a warning or handle it predictably (e.g., not render an extra item).
anampartho commented 1 month ago

When you're rendering a list of items using map in react, react relies on unique keys to properly update the virtual dom. Using duplicate keys can cause unexpected behaviors which includes duplicate items being rendered. Different unexpected behaviors can happen throughout different versions when using non-unique keys.

See: https://github.com/facebook/react/issues/12124

RioChndr commented 4 weeks ago

I think this is expected because id should be unique already written in document how to render list. I suggest to you to use index of array instead of id in object to make each item unique.

+{records.map(({ id, name, email }, index) => (
        <div
          onClick={() => console.log("I am clicked", id)}
-          key={id}
+          key={index}
          className="grid grid-cols-5 border mb-2 p-2 hover:cursor-pointer"
        >
          <p>{id}</p>
          <p className="col-span-2">{email}</p>
          <p className="col-span-2"> {name}</p>
        </div>
      ))}
Ronny011-dy commented 3 weeks ago

I think this is expected because id should be unique already written in document how to render list. I suggest to you to use index of array instead of id in object to make each item unique.

+{records.map(({ id, name, email }, index) => (
        <div
          onClick={() => console.log("I am clicked", id)}
-          key={id}
+          key={index}
          className="grid grid-cols-5 border mb-2 p-2 hover:cursor-pointer"
        >
          <p>{id}</p>
          <p className="col-span-2">{email}</p>
          <p className="col-span-2"> {name}</p>
        </div>
      ))}

While this can fix the issue, I suggest checking why record ids are the same to begin with. Having several entries in the DB with the same key can and will cause a headache in the future (assuming these records are from an actual DB).

In any case, index isn't the safest value to send as key, because the index can change for the same item somewhere down the line, due to user interaction with a rendered list for example.