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.
Why This Matters
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.
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.
The rule
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.
Bad example
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> );});