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:
[lint.rules]
"react-perf/jsx-no-new-object-as-prop" = "error" # or "warn"Every option, set to its default:
[lint.rules]
"react-perf/jsx-no-new-object-as-prop" = ["error", {
nativeAllowList = [], # list of strings
}]Examples#
function Profile({ user }) {
return <Avatar user={user} style={{ width: 32 }JSX attribute values should not contain objects created in the same scope.} />;
}function Profile({ defaults, user }) {
return <Avatar user={Object.assign({}, defaults, user)JSX attribute values should not contain objects created in the same scope.} />;
}The rule looks into ||, &&, ?? and ? ::
function Profile({ user }) {
return <Avatar user={user ?? {}JSX attribute values should not contain objects created in the same scope.} />;
}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({ userJSX attribute values should not contain objects created in the same scope. = {} }) {
const styleJSX attribute values should not contain objects created in the same scope. = { width: 32 };
return <Avatar user={user} style={style} />;
}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#
| Option | Default | What 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:
{ "nativeAllowList": ["style"] }No problemsfunction Banner() {
return <div style={{ padding: 8 }} />;
}The element's other props are still checked, and so is style on a component:
{ "nativeAllowList": ["style"] }2 problemsfunction Banner() {
return (
<map-view center={{ lat: 52, lng: 13 }JSX attribute values should not contain objects created in the same scope.}>
<Badge style={{ padding: 8 }JSX attribute values should not contain objects created in the same scope.} />
</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.