react/jsx-key

Require a `key` prop on elements in arrays and iterators

React uses key to tell list items apart from one render to the next. Without it, React warns at run time and matches items by position. After an insert or a reorder, an item can then end up with its neighbor's state, such as the text typed into an input.

The rule looks at elements returned from a .map() or Array.from() callback, and at elements in an array literal that React renders as a list: one that's a child of an element, or that a function returns. It skips lists passed to React.Children.toArray(), because that adds keys itself.

This rule is on by default if your package.json depends on react.

Configure#

On by default as an error. To turn it off:

bunfig.toml
[lint.rules]
"react/jsx-key" = "off" # or "warn"

Every option, set to its default:

bunfig.toml
[lint.rules]
"react/jsx-key" = ["error", {
  checkFragmentShorthand = false,
  checkKeyMustBeforeSpread = false,
  warnOnDuplicates = false,
  checkArraysOutsideJSX = false,
}]

Examples#

Reported:

const list = (
  <ul>
    {users.map(user => <li>{user.name}</li>)}
  </ul>
);

const tabs = <nav>{[<Tab key="home" />, <Tab />]}</nav>;

const stars = Array.from({ length: 5 }, () => <Star />);

Not reported:

const list = (
  <ul>
    {users.map(user => <li key={user.id}>{user.name}</li>)}
  </ul>
);

const tabs = <nav>{[<Tab key="home" />, <Tab key="settings" />]}</nav>;

const panels = React.Children.toArray([<Panel />, <Panel />]);

An array that's only a place to keep elements doesn't need keys:

function Wizard({ step }) {
  const steps = [<Address />, <Payment />, <Review />];
  return steps[step];
}

const card = <Card actions={[<a href="#edit">Edit</a>, <a href="#more">More</a>]} />;

Options#

OptionDefaultWhat it does
checkFragmentShorthandfalseAlso report <>...</> in a list. It can't take a key, so use <React.Fragment key={...}>.
checkKeyMustBeforeSpreadfalseAlso report a key that comes after a {...spread}.
warnOnDuplicatesfalseAlso report siblings that have the same key.
checkArraysOutsideJSXfalseAlso report elements in every other array literal.

Only bun lint has checkArraysOutsideJSX. With an ESLint config file every array literal is checked, as in eslint-plugin-react.

const icons = [<Home />, <User />];

With checkFragmentShorthand:

const rows = users.map(user => (
  <>
    <dt>{user.name}</dt>
    <dd>{user.email}</dd>
  </>
));
const rows = users.map(user => (
  <React.Fragment key={user.id}>
    <dt>{user.name}</dt>
    <dd>{user.email}</dd>
  </React.Fragment>
));

When a key comes after a spread, the automatic JSX runtime falls back to createElement for that element. checkKeyMustBeforeSpread reports it:

const rows = users.map(user => <Row {...user} key={user.id} />);
const rows = users.map(user => <Row key={user.id} {...user} />);

With warnOnDuplicates:

const tabs = [<Tab key="home" />, <Tab key="home" />];