C
React/Hooks/Lesson 11

SyntheticEvent — onChange/onSubmit Types and When to Call preventDefault

25 min·theory
This chapter
3/5
TypeScript

SyntheticEvent — onChange/onSubmit Types and When to Call preventDefault

💡 Why Learn This? — You Use It Every Day Yet Guess at the Types

🎯 If you use the `e` in `onChange={(e) => ...}` without knowing what it is, `e.target.value` becomes `any`, and you lose all autocomplete and typo validation.
💼 React's event object is a wrapper around the browser's `Event` (`SyntheticEvent`). It adds some extra functionality, but the signature is nearly identical.
`onChange`, `onClick`, `onSubmit`, `onKeyDown` — each has a different type, and the target element (`HTMLInputElement` vs `HTMLButtonElement`) is also part of the type.
🔗 If you miss the right moment to call `e.preventDefault()`, the form will reload the page or the link will navigate away.
📈 Event delegation is handled automatically by React — all events are in fact processed once at the root.
🏢 실무에서는
You run into this every time you build even a single form line. Memorize these five types and you will never be confused again. They are used every day, just like `useState` and `useEffect`, so it is worth committing them to memory.

5 Core React Event Types

1. SyntheticEvent — A Wrapper Around the Browser Event

React events are a wrapper built on top of the standard Event, adding cross-browser compatibility, pooling, and delegation.

2. The 5 Signatures You'll Use Most

ts
// (a) onChange — input/textarea/select
(e: React.ChangeEvent<HTMLInputElement>) => void
(e: React.ChangeEvent<HTMLTextAreaElement>) => void
(e: React.ChangeEvent<HTMLSelectElement>) => void

// (b) onSubmit — form
(e: React.FormEvent<HTMLFormElement>) => void

// (c) onClick — button/div/...
(e: React.MouseEvent<HTMLButtonElement>) => void

// (d) onKeyDown — input/textarea
(e: React.KeyboardEvent<HTMLInputElement>) => void

// (e) onFocus / onBlur
(e: React.FocusEvent<HTMLInputElement>) => void

3. target vs currentTarget

tsx
<button onClick={(e) => {
  e.target;        // EventTarget — the element where the click actually occurred (may be a child)
  e.currentTarget; // HTMLButtonElement — the element the handler is attached to (always button)
}}>
  <span>Click</span>
</button>

Clicking the span gives e.target = span, e.currentTarget = button. Use currentTarget when you need the exact type of the element the handler is attached to.

4. preventDefault — When to Call It

tsx
<form onSubmit={(e) => {
  e.preventDefault();  // prevent page reload — call it first
  // ... handle form
}}>
tsx
<a href="/x" onClick={(e) => {
  if (modalOpen) e.preventDefault(); // conditional
}}>

5. For checkboxes, use checked, not value

tsx
<input type="checkbox" onChange={(e) => setAgree(e.target.checked)} />

6. Event Delegation — React Handles It for You

Even though you write <button onClick={...}>, React attaches only one listener to the root container and walks up the virtual tree to invoke handlers. You don't need to think about this detail.

💻 🅰️ Without Types — e is any
// ❌ Without type — e.target's identity is unknown
import { useState } from 'react';

export function LoginForm() {
  const [email, setEmail] = useState('');

  const onChange = (e) => {
    setEmail(e.target.value);     // OK if lucky
    setEmail(e.target.valeu);     // ❌ Typo passes
    setEmail(e.target.checked);   // Don't know if it's input or checkbox
  };

  const onSubmit = (e) => {
    // Easy to forget preventDefault
    console.log({ email });
    // → Page reload → Console cleared
  };

  return (
    <form onSubmit={onSubmit}>
      <input onChange={onChange} />
      <button type="submit">Login</button>
    </form>
  );
}
💻 🅱️ With Explicit Types — IDE Autocomplete Works
// ✅ Explicit event type
import { useState, type FormEvent, type ChangeEvent, type KeyboardEvent } from 'react';

export function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [remember, setRemember] = useState(false);

  // onChange for input
  const onChangeEmail = (e: ChangeEvent<HTMLInputElement>) => {
    setEmail(e.target.value);
    // setEmail(e.target.valeu); ← ❌ TS immediately shows red underline
  };

  // Checkbox is also ChangeEvent<HTMLInputElement> — but e.target.checked
  const onChangeRemember = (e: ChangeEvent<HTMLInputElement>) => {
    setRemember(e.target.checked);
  };

  // onSubmit for form
  const onSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    console.log({ email, password, remember });
  };

  // Handle only Enter key in input
  const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter') console.log('Enter');
  };

  // Shared handler — branch by name
  const onChangeShared = (e: ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    if (name === 'email') setEmail(value);
    if (name === 'password') setPassword(value);
  };

  return (
    <form onSubmit={onSubmit}>
      <input name="email" value={email} onChange={onChangeShared} onKeyDown={onKeyDown} />
      <input name="password" type="password" value={password} onChange={onChangeShared} />
      <label>
        <input type="checkbox" checked={remember} onChange={onChangeRemember} />
        Auto Login
      </label>
      <button type="submit">Login</button>
    </form>
  );
}

💡 💡 5 React Event Practical Tips

1. Memorize just these 5

  • onChange for input/textarea/select: ChangeEvent<HTMLInputElement>, etc.
  • onSubmit for form: FormEvent<HTMLFormElement>
  • onClick for button: MouseEvent<HTMLButtonElement>
  • onKeyDown for input: KeyboardEvent<HTMLInputElement>
  • focus/blur: FocusEvent<HTMLInputElement>

2. Always call e.preventDefault() as the first line in onSubmit
Forget it and the form triggers a full-page reload via GET/POST.

3. For checkboxes, use e.target.checked

tsx
onChange={(e) => setAgree(e.target.checked)}

4. e.currentTarget is the safe choice
e.target may be a child element (like a span) that received the actual click. The type of the element the handler is attached to comes from currentTarget.

5. e.stopPropagation() only works within the React tree
If an external library (e.g. jQuery) has attached a listener to document, React's stopPropagation won't stop it. In that case, use e.nativeEvent.stopImmediatePropagation().

⚡ Try It Live — Inspect the Event Object

Use console.log to see what information is inside the event object.
✏️ JS 코드
📟 Console output
▶ Press the Run button
⚠️ Runs in a browser sandbox — only console.log() is supported; alert/fetch are not.

Check Your Understanding

What is the most precise TypeScript type for an onChange handler on an input element?
💡 `React.ChangeEvent<HTMLInputElement>` is the correct type. `e.target` is inferred as `HTMLInputElement`, so autocomplete and type-checking apply to `value`, `checked`, `name`, and other properties. `Event` is the browser standard and is not a React SyntheticEvent, so it is inappropriate here. `SyntheticEvent` alone does not tell you the concrete type of `target`.
SyntheticEvent — Event Types - React