react/jsx-key

Require a key prop on elements in arrays and iterators

error by default

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#

example.jsx3 problems
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 />);
example.jsxNo problems
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:

example.jsxNo problems
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.

example.jsxwith { "checkArraysOutsideJSX": true }2 problems
const icons = [<Home />, <User />];

With checkFragmentShorthand:

example.jsxwith { "checkFragmentShorthand": true }1 problem
const rows = users.map(user => (
  <>
    <dt>{user.name}</dt>
    <dd>{user.email}</dd>
  </>
));
example.jsxwith { "checkFragmentShorthand": true }No problems
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:

example.jsxwith { "checkKeyMustBeforeSpread": true }1 problem
const rows = users.map(user => <Row {...user} key={user.id} />);
example.jsxwith { "checkKeyMustBeforeSpread": true }No problems
const rows = users.map(user => <Row key={user.id} {...user} />);

With warnOnDuplicates:

example.jsxwith { "warnOnDuplicates": true }2 problems
const tabs = [<Tab key="home" />, <Tab key="home" />];