ReactHandling Events

Handling Events

React's event system lets you respond to user interactions — clicks, key presses, form submissions, mouse movement — using familiar JavaScript patterns. The API is intentionally similar to plain HTML events, but with a few important differences that make it more consistent, more powerful, and better suited to component-based architecture.

The React Event System (Synthetic Events)

React doesn't attach event listeners directly to DOM elements. Instead it uses event delegation: a single listener is attached at the root of your React tree. When a user clicks a button, the native click event bubbles up to the root, where React intercepts it and dispatches a SyntheticEvent to the appropriate handler.

This has two practical benefits: it's more memory-efficient (one listener instead of thousands), and it normalizes browser inconsistencies behind a cross-browser API. You don't need to worry about event.which vs event.keyCode or IE quirks — React handles it.

camelCase Event Names

In HTML you write onclick, onchange, onsubmit. In React JSX, event attributes use camelCase:

HTML attribute

React prop

Fires when

onclick

onClick

Element is clicked

onchange

onChange

Input value changes

onsubmit

onSubmit

Form is submitted

onkeydown

onKeyDown

Key is pressed down

onkeyup

onKeyUp

Key is released

onmouseover

onMouseOver

Mouse enters element

onmouseout

onMouseOut

Mouse leaves element

onfocus

onFocus

Element receives focus

onblur

onBlur

Element loses focus

ondblclick

onDoubleClick

Element is double-clicked

Passing Handler Functions, Not Calling Them

You pass a reference to the handler function — you don't call it inline. React calls your function when the event fires.

JSX
// ✓ Correct — pass the function reference
<button onClick={handleClick}>Click me</button>

// ✗ Wrong — this calls handleClick() immediately during render
<button onClick={handleClick()}>Click me</button>

The second form calls handleClick() while the JSX is evaluated — so it runs during render, not on click, which is almost never what you want.

A Basic Click Handler

JSX
import { useState } from 'react'

function LikeButton() {
  const [likes, setLikes] = useState(0)

  function handleClick() {
    setLikes(prev => prev + 1)
  }

  return (
    <button onClick={handleClick}>
      ♥ {likes} {likes === 1 ? 'like' : 'likes'}
    </button>
  )
}
Form Submission and e.preventDefault()

HTML forms reload the page by default when submitted. In React apps you almost always want to prevent this and handle submission in JavaScript. Call e.preventDefault() at the start of your submit handler:

JSX
import { useState } from 'react'

function SearchForm() {
  const [query, setQuery] = useState('')
  const [result, setResult] = useState(null)

  async function handleSubmit(e) {
    e.preventDefault() // stop the browser from navigating
    const data = await fetchSearch(query)
    setResult(data)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Search..."
      />
      <button type="submit">Search</button>
    </form>
  )
}
Keyboard Events

Use onKeyDown to respond to specific key presses. The event object provides e.key (the human-readable key name) and e.code (the physical key position). Prefer e.key for most use cases.

JSX
function ChatInput() {
  const [message, setMessage] = useState('')

  function handleKeyDown(e) {
    // Submit on Enter, but allow Shift+Enter for newlines
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault()
      sendMessage(message)
      setMessage('')
    }
  }

  return (
    <textarea
      value={message}
      onChange={e => setMessage(e.target.value)}
      onKeyDown={handleKeyDown}
      placeholder="Type a message... (Enter to send)"
    />
  )
}
Stopping Event Propagation

Events bubble up the DOM tree by default. A click on a child element also fires click handlers on every ancestor. Call e.stopPropagation() to stop the event from bubbling further up.

JSX
function Card({ onSelect, children }) {
  return (
    <div onClick={onSelect} className="card">
      {children}
      <button
        onClick={e => {
          e.stopPropagation() // prevent click from reaching the Card
          handleDelete()
        }}
      >
        Delete
      </button>
    </div>
  )
}

// Without stopPropagation, clicking Delete would also trigger onSelect
// because the click event would bubble from <button> up to <div onClick={onSelect}>
Warning
Avoid using `e.stopPropagation()` as a crutch. If you find yourself stopping propagation frequently, it often means your component structure needs rethinking. Overuse of `stopPropagation` makes event flow hard to reason about.
Inline Arrow Functions

When you need to pass arguments to a handler, an inline arrow function is the cleanest approach:

JSX
function TodoList({ todos, onDelete }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.text}
          <button onClick={() => onDelete(todo.id)}>Remove</button>
        </li>
      ))}
    </ul>
  )
}

// The arrow function () => onDelete(todo.id) closes over todo.id
// and passes it to onDelete when the button is clicked
Note
Inline arrow functions create a new function on every render, which means the child component gets a new prop reference each time. For most cases this is perfectly fine. Only optimize it with `useCallback` if profiling shows a real performance problem.
Mouse Events

JSX
function Tooltip({ label, children }) {
  const [visible, setVisible] = useState(false)

  return (
    <div
      onMouseOver={() => setVisible(true)}
      onMouseOut={() => setVisible(false)}
      style={{ position: 'relative', display: 'inline-block' }}
    >
      {children}
      {visible && (
        <span className="tooltip">{label}</span>
      )}
    </div>
  )
}
Common Event Props Reference
  • onClick — button clicks, any clickable element

  • onChange — input, textarea, and select value changes

  • onSubmit — form submission (attach to the <form> element)

  • onKeyDown / onKeyUp / onKeyPress — keyboard interactions

  • onFocus / onBlur — focus management, showing/hiding validation errors

  • onMouseDown / onMouseUp / onMouseMove — drag interactions

  • onMouseOver / onMouseOut — hover effects

  • onMouseEnter / onMouseLeave — like hover but don't bubble

  • onScroll — scroll position tracking

  • onInput — fires synchronously on every keystroke (less common than onChange)

  • onPaste / onCopy / onCut — clipboard operations

  • onDragStart / onDrop — drag-and-drop interfaces

  • onTouchStart / onTouchEnd — mobile touch events

  • onContextMenu — right-click context menu

  • onAnimationEnd / onTransitionEnd — CSS animation lifecycle

Tip
Prefer defining handler functions above the return statement rather than writing long inline handlers. `function handleClick(e) {...}` above the JSX is far easier to read and test than a complex arrow function inside an attribute.