Extract event handlers defined inline in JSX to named functions or useCallback. Inline functions create new references every render, breaking React.memo and causing unnecessary child re-renders.
Every inline arrow function in JSX (e.g., `onClick={() => handleClick(id)}`) creates a new function object on every render. If passed as a prop to a child wrapped in React.memo, the new reference defeats memoization and the child re-renders unnecessarily. In lists with hundreds of items, this causes measurable performance degradation.
BeforeMerge scans your pull requests against this rule and 4+ others. Get actionable feedback before code ships.
When you write onClick={() => doSomething(id)} in JSX, JavaScript creates a brand new function object on every render. The function's behavior is identical, but its reference identity is different.
This matters because React uses reference equality to determine if props have changed. If a child component is wrapped in React.memo, a new function reference means the child re-renders even though nothing meaningful changed. In a list of 200 items, each with an inline handler, you get 200 unnecessary re-renders on every parent state change.
For simple components, this overhead is negligible. But in performance-sensitive paths — lists, tables, frequently-updating UIs — it adds up.
Extract inline functions from JSX props into named functions (for static handlers) or useCallback (for handlers that close over changing values). This preserves referential stability and allows child memoization to work.
function TodoList({ todos, onToggle, onDelete }: Props) {
return (
<ul>
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
// BAD: new function on every render, defeats React.memo on TodoItem
onToggle={() => onToggle(todo.id)}
onDelete={() => onDelete(todo.id)}
/>
))}
</ul>
);
}
const TodoItem = React.memo(function TodoItem({ todo, onToggle, onDelete }: ItemProps) {
return (
<li>
<input type="checkbox" checked={todo.done} onChange={onToggle} />
<span>{todo.title}</span>
<button onClick={onDelete}>Delete</button>
</li>
);
});function TodoList({ todos, onToggle, onDelete }: Props) {
// GOOD: stable callbacks that pass the id
const handleToggle = useCallback(
(id: string) => onToggle(id),
[onToggle]
);
const handleDelete = useCallback(
(id: string) => onDelete(id),
[onDelete]
);
return (
<ul>
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={handleToggle}
onDelete={handleDelete}
/>
))}
</ul>
);
}
const TodoItem = React.memo(function TodoItem({ todo, onToggle, onDelete }: ItemProps) {
return (
<li>
<input
type="checkbox"
checked={todo.done}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.title}</span>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</li>
);
});Search for arrow functions directly in JSX props:
grep -n "on[A-Z].*={() =>" --include="*.tsx" -r src/Focus on components that render lists or are wrapped in React.memo — inline functions in those contexts have the highest impact.
useCallback for handlers that depend on props or state