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:

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

Every option, set to its default:

bunfig.toml
[lint.rules]
"react-perf/jsx-no-new-function-as-prop" = ["error", {
  nativeAllowList = [],
}]

Examples#

example.jsx1 problem
function Editor({ save }) {
  return <Button onClick={() => save("draft")} />;
}
example.jsx1 problem
class Editor extends React.Component {
  render() {
    return <Button onClick={this.save.bind(this)} />;
  }
}

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

example.jsx1 problem
function Editor({ onSave }) {
  return <Button onClick={onSave || (() => {})} />;
}

A function declared inside the component has the same problem as one written in the prop. The report is on the declaration:

example.jsx2 problems
function Editor({ save }) {
  const saveDraft = () => save("draft");
  function publish() {
    save("public");
  }
  return <Buttons onSave={saveDraft} onPublish={publish} />;
}
example.jsxNo problems
function reload() {
  location.reload();
}

function Editor() {
  return <Button onClick={reload} />;
}

If the function uses props or state, cache it with useCallback:

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

example.jsxNo problems
class Editor extends React.Component {
  save = () => {
    this.props.save("draft");
  };

  render() {
    return <Button onClick={this.save} />;
  }
}

Options#

OptionDefaultWhat 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:

example.jsxwith { "nativeAllowList": "all" }No problems
function Editor({ save }) {
  return <button onClick={() => save("draft")}>Save</button>;
}

Components are still checked:

example.jsxwith { "nativeAllowList": "all" }1 problem
function Editor({ save }) {
  return <Button onClick={() => save("draft")} />;
}

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.