react-perf/jsx-no-new-object-as-prop

Disallow objects created during render as the value of a JSX prop

Also called react_perf/jsx-no-new-object-as-prop

{} written inside a component makes a new object on every render. When you pass it as a prop, it's never equal to the value from the last render, so a child wrapped in React.memo renders again even though the contents are the same.

The rule reports object literals, Object(), new Object(), Object.assign() and Object.create(). An object at the top level of a module isn't reported, because it's created once.

Configure#

Off by default. To turn it on:

bunfig.toml
[lint.rules]
"react-perf/jsx-no-new-object-as-prop" = "error" # or "warn"

Every option, set to its default:

bunfig.toml
[lint.rules]
"react-perf/jsx-no-new-object-as-prop" = ["error", {
  nativeAllowList = [], # list of strings
}]

Examples#

example.jsx1 problem
function Profile({ user }) {
  return <Avatar user={user} style={{ width: 32 }} />;
}
example.jsx1 problem
function Profile({ defaults, user }) {
  return <Avatar user={Object.assign({}, defaults, user)} />;
}

The rule looks into ||, &&, ?? and ? ::

example.jsx1 problem
function Profile({ user }) {
  return <Avatar user={user ?? {}} />;
}

A variable declared inside the component has the same problem as an object written in the prop, and so does a destructured prop's default. The report is on the declaration:

example.jsx2 problems
function Profile({ user = {} }) {
  const style = { width: 32 };
  return <Avatar user={user} style={style} />;
}
example.jsxNo problems
const avatarStyle = { width: 32 };
const guest = {};

function Profile({ user }) {
  return <Avatar user={user ?? guest} style={avatarStyle} />;
}

If the object depends on props or state, cache it with useMemo:

example.jsxNo problems
import { useMemo } from "react";

function Profile({ user, size }) {
  const style = useMemo(() => ({ width: size }), [size]);
  return <Avatar user={user} style={style} />;
}

Options#

OptionDefaultWhat it does
nativeAllowList[]Props to skip on elements with a lowercase name, such as <div>. A list of prop names (case doesn't matter), or "all" for every prop.

A DOM element doesn't compare its props to skip a render, so style={{ .. }} costs nothing there. With { "nativeAllowList": ["style"] }, this is allowed:

example.jsxwith { "nativeAllowList": ["style"] }No problems
function Banner() {
  return <div style={{ padding: 8 }} />;
}

The element's other props are still checked, and so is style on a component:

example.jsxwith { "nativeAllowList": ["style"] }2 problems
function Banner() {
  return (
    <map-view center={{ lat: 52, lng: 13 }}>
      <Badge style={{ padding: 8 }} />
    </map-view>
  );
}

When not to use it#

A new prop value only matters to a child that compares its props, for example with React.memo. The React Compiler memoizes these values for you.