react-perf/jsx-no-new-function-as-prop
Disallow functions created during render as the value of a JSX prop
Also called react_perf/jsx-no-new-function-as-prop
A function written inside a component is a new function 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 function does the same thing.
The rule reports arrow functions, function expressions, .bind() calls, Function() and new Function(). A function 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-function-as-prop" = "error" # or "warn"Every option, set to its default:
[lint.rules]
"react-perf/jsx-no-new-function-as-prop" = ["error", {
nativeAllowList = [],
}]Examples#
function Editor({ save }) {
return <Button onClick={() => save("draft")JSX attribute values should not contain functions created in the same scope.} />;
}class Editor extends React.Component {
render() {
return <Button onClick={this.save.bind(this)JSX attribute values should not contain functions created in the same scope.} />;
}
}The rule looks into ||, &&, ?? and ? ::
function Editor({ onSave }) {
return <Button onClick={onSave || (() => {}JSX attribute values should not contain functions created in the same scope.)} />;
}A function declared inside the component has the same problem as one written in the prop. The report is on the declaration:
function Editor({ save }) {
const saveDraftJSX attribute values should not contain functions created in the same scope. = () => save("draft");
function publishJSX attribute values should not contain functions created in the same scope.() {
save("public");
}
return <Buttons onSave={saveDraft} onPublish={publish} />;
}function reload() {
location.reload();
}
function Editor() {
return <Button onClick={reload} />;
}If the function uses props or state, cache it with useCallback:
import { useCallback } from "react";
function Editor({ save }) {
const saveDraft = useCallback(() => save("draft"), [save]);
return <Button onClick={saveDraft} />;
}In a class, make the handler a property and pass that:
class Editor extends React.Component {
save = () => {
this.props.save("draft");
};
render() {
return <Button onClick={this.save} />;
}
}Options#
| Option | Default | What it does |
|---|---|---|
nativeAllowList | [] | Props to skip on elements with a lowercase name, such as <button>. 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 a new function costs nothing there. With { "nativeAllowList": "all" }, this is allowed:
{ "nativeAllowList": "all" }No problemsfunction Editor({ save }) {
return <button onClick={() => save("draft")}>Save</button>;
}Components are still checked:
{ "nativeAllowList": "all" }1 problemfunction Editor({ save }) {
return <Button onClick={() => save("draft")JSX attribute values should not contain functions created in the same scope.} />;
}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.