Learn

/

Callback Naming

Callback Naming

7 patterns

Why the on prefix matters and how to name event handler props so they read like English.

Value change handler
easy
Avoid
interface SliderProps {
  value: number;
  setValue: (v: number) => void;
}

Prefer
interface SliderProps {
  value: number;
  onValueChange: (value: number) => void;
}

Child components don't "set" state — they signal that something changed. The on prefix followed by what happened (ValueChange) is the React convention. This mirrors native DOM events like onChange and onClick.

React Docs: Responding to Events
Delete action handler
easy
Avoid
interface TodoItemProps {
  todo: Todo;
  delete: () => void;
}

Prefer
interface TodoItemProps {
  todo: Todo;
  onDelete: () => void;
}

onDelete clearly communicates this is an event callback. The component requests deletion — the parent performs it.

React Docs: Naming event handler props
Form submission
easy
Avoid
interface SearchBarProps {
  query: string;
  search: (q: string) => void;
}

Prefer
interface SearchBarProps {
  query: string;
  onSearch: (query: string) => void;
}

onSearch is an event callback that says "the user triggered a search." The parent handles the actual search logic.

React Docs: Responding to Events
Selection handler specificity
medium
Avoid
interface DropdownProps {
  items: string[];
  onChange: (item: string) => void;
}

Prefer
interface DropdownProps {
  items: string[];
  onItemSelect: (item: string) => void;
}

onItemSelect tells you exactly what happened: the user selected an item. onChange is reserved for native <input> and <select> elements where it has established meaning.

For custom components, specific names like onItemSelect, onTabChange, or onColorPick describe the actual user action — invaluable when a component has multiple things that can change.

React Docs: Naming event handler props
Close callback with reason
hard
Avoid
type CloseReason =
  | 'backdropClick'
  | 'escapeKeyDown'
  | 'closeButton';

interface DialogProps {
  children: React.ReactNode;
  isOpen: boolean;
  onClose: () => void;
  /** Set after the dialog closes. */
  lastCloseReason?: CloseReason;
}

Prefer
type CloseReason =
  | 'backdropClick'
  | 'escapeKeyDown'
  | 'closeButton';

interface DialogProps {
  children: React.ReactNode;
  isOpen: boolean;
  onClose: (reason: CloseReason) => void;
}

Passing the reason as a callback parameter lets the parent decide how to respond to each close trigger in real time. For example, you might ignore backdrop clicks on a confirmation dialog but allow Escape.

MUI's Dialog uses exactly this pattern. A separate prop is reactive (updates after closing) instead of actionable (decides during closing).

MUI: Dialog API
Dual-level event callbacks
hard
Avoid
interface SliderProps {
  min: number;
  max: number;
  value: number;
  /** Fires on every value change. */
  onChange: (value: number) => void;
  /**
   * Debounce delay in ms before onChange fires.
   * Use 0 for real-time updates.
   * @default 0
   */
  changeDebounceMs?: number;
}

Prefer
interface SliderProps {
  min: number;
  max: number;
  value: number;
  /** Fires continuously as the thumb moves. */
  onChange: (value: number) => void;
  /** Fires once when the user releases the thumb. */
  onChangeCommitted: (value: number) => void;
}

Some interactions have two meaningful moments: the live update and the final commit. Two callbacks let the parent do different things at each moment — onChange for UI preview, onChangeCommitted for saving to the server.

MUI's Slider uses exactly this pattern. A changeDebounceMs config forces a trade-off between responsiveness and efficiency.

MUI: Slider API
Drag and drop callbacks
medium
Avoid
interface DraggableProps {
  children: React.ReactNode;
  dragStart: () => void;
  dragEnd: (pos: Position) => void;
  drop: (target: string) => void;
}

Prefer
interface DraggableProps {
  children: React.ReactNode;
  onDragStart: () => void;
  onDragEnd: (position: Position) => void;
  onDrop: (targetId: string) => void;
}

All three callbacks use the on prefix and descriptive parameter names (position vs pos, targetId vs target). Consistent naming across related events is key.

React Docs: Responding to Events