RE

Event Handling

React · 6 entries

Click Events

syntax
<button onClick={handler}>...</button>
// With argument
<button onClick={() => handler(arg)}>...</button>
example
function ColorPicker({ colors, onSelect }) {
  return (
    <div className="palette">
      {colors.map(color => (
        <button
          key={color}
          style={{ backgroundColor: color, width: 40, height: 40 }}
          onClick={() => onSelect(color)}
          aria-label={`Select ${color}`}
        />
      ))}
    </div>
  );
}

Note Pass a function reference, not a function call. Writing onClick={handler()} would call the function during render. Use an arrow function when you need to pass arguments: onClick={() => handler(id)}.

Change Events

syntax
<input onChange={(e) => handleChange(e.target.value)} />
<select onChange={(e) => handleSelect(e.target.value)}>
example
function TemperatureConverter() {
  const [celsius, setCelsius] = useState('');
  const fahrenheit = celsius !== '' ? (Number(celsius) * 9/5 + 32).toFixed(1) : '';

  return (
    <div>
      <label>
        Celsius:
        <input
          type="number"
          value={celsius}
          onChange={e => setCelsius(e.target.value)}
        />
      </label>
      <p>Fahrenheit: {fahrenheit}</p>
    </div>
  );
}

Note React's onChange fires on every keystroke, not just when the field loses focus (which is the native DOM behavior). This makes it ideal for real-time validation and derived values.

Submit Events

syntax
<form onSubmit={handleSubmit}>
  ...
  <button type="submit">Submit</button>
</form>
example
function SearchForm({ onSearch }) {
  const [query, setQuery] = useState('');

  function handleSubmit(e) {
    e.preventDefault();
    if (query.trim()) {
      onSearch(query.trim());
    }
  }

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

Note Always call e.preventDefault() in onSubmit to prevent the browser from performing a full page reload. The submit event fires when pressing Enter inside an input or clicking a submit button.

Synthetic Event Object

syntax
function handler(event) {
  event.target       // the DOM element
  event.currentTarget // element the handler is attached to
  event.preventDefault()
  event.stopPropagation()
  event.nativeEvent   // underlying browser event
}
example
function LinkInterceptor({ children }) {
  function handleClick(e) {
    // Check if it's an internal link
    const href = e.target.closest('a')?.getAttribute('href');
    if (href?.startsWith('/')) {
      e.preventDefault();
      navigate(href); // client-side navigation
    }
  }

  return <div onClick={handleClick}>{children}</div>;
}

Note React wraps native events in SyntheticEvent objects that work identically across browsers. As of React 17+, events are no longer pooled, so you can access event properties asynchronously without calling e.persist().

Preventing Default & Propagation

syntax
e.preventDefault();    // stop default browser action
e.stopPropagation();   // stop bubbling to parent handlers
example
function DropZone({ onFileDrop }) {
  function handleDragOver(e) {
    e.preventDefault(); // Required to allow drop
  }

  function handleDrop(e) {
    e.preventDefault();
    e.stopPropagation();
    const files = Array.from(e.dataTransfer.files);
    onFileDrop(files);
  }

  return (
    <div
      onDragOver={handleDragOver}
      onDrop={handleDrop}
      className="drop-zone"
    >
      Drop files here
    </div>
  );
}

Note Use preventDefault for links, form submissions, and drag-and-drop. Use stopPropagation sparingly -- it makes debugging harder. Prefer checking the event target to decide whether to act instead of stopping propagation.

Capture Phase & Delegation

syntax
// Capture phase (fires before children)
<div onClickCapture={handler}>...</div>

// Event delegation: one handler on parent
<ul onClick={handleItemClick}>
  <li data-id="1">A</li>
  <li data-id="2">B</li>
</ul>
example
function ItemGrid({ items, onItemAction }) {
  function handleClick(e) {
    const itemEl = e.target.closest('[data-item-id]');
    if (!itemEl) return;

    const id = itemEl.dataset.itemId;
    const action = e.target.dataset.action;

    if (action === 'delete') {
      onItemAction(id, 'delete');
    } else if (action === 'edit') {
      onItemAction(id, 'edit');
    }
  }

  return (
    <div onClick={handleClick}>
      {items.map(item => (
        <div key={item.id} data-item-id={item.id}>
          <span>{item.name}</span>
          <button data-action="edit">Edit</button>
          <button data-action="delete">Delete</button>
        </div>
      ))}
    </div>
  );
}

Note Event delegation (one handler on a parent) reduces the number of event listeners and works well for dynamic lists. Use data-* attributes or closest() to identify which child triggered the event. React internally uses delegation at the root already.