Skip to content
NewBun 1.4.5 is out. Release notes
AphrodyBun

Linter · Rules

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

Disallow objects created during render as the value of a JSX 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

Reported:

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

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

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:

function Profile({ user = {} }) {
  const style = { width: 32 };
  return <Avatar user={user} style={style} />;
}

Not reported:

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:

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:

function Banner() {
  return <div style={{ padding: 8 }} />;
}

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

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.