javascript-today

Event Handling

What is Event Handling?

Event handling is how you respond to user interactions like clicks, typing, and form submissions. React makes event handling straightforward with a consistent API across all browsers.

function Button() {
  function handleClick() {
    alert('Button clicked!');
  }

  return <button onClick={handleClick}>Click Me</button>;
}

Key differences from HTML:

  • React events use camelCase: onClick not onclick
  • Pass function reference: onClick={handleClick} not onClick="handleClick()"
  • Can’t return false to prevent default - must call preventDefault()

onClick - Handling Clicks

The most common event handler for buttons and clickable elements:

Basic Click Handler

function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

Inline Arrow Function

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

When to use which:

  • Named function - for complex logic, reusable handlers
  • Inline arrow function - for simple one-liners, passing arguments

Multiple Handlers

function ButtonGroup() {
  const [message, setMessage] = useState('');

  function handleSave() {
    setMessage('Saved!');
  }

  function handleCancel() {
    setMessage('Cancelled');
  }

  function handleDelete() {
    setMessage('Deleted!');
  }

  return (
    <div>
      <p>{message}</p>
      <button onClick={handleSave}>Save</button>
      <button onClick={handleCancel}>Cancel</button>
      <button onClick={handleDelete}>Delete</button>
    </div>
  );
}

Passing Arguments to Event Handlers

Using Arrow Functions

function ItemList() {
  const items = ['Apple', 'Banana', 'Orange'];

  function handleItemClick(item) {
    alert(`You clicked: ${item}`);
  }

  return (
    <ul>
      {items.map(item => (
        <li key={item}>
          <button onClick={() => handleItemClick(item)}>
            {item}
          </button>
        </li>
      ))}
    </ul>
  );
}

Using bind()

function ItemList() {
  const items = ['Apple', 'Banana', 'Orange'];

  function handleItemClick(item) {
    alert(`You clicked: ${item}`);
  }

  return (
    <ul>
      {items.map(item => (
        <li key={item}>
          <button onClick={handleItemClick.bind(null, item)}>
            {item}
          </button>
        </li>
      ))}
    </ul>
  );
}

Note: Arrow functions are more common and easier to read.

The Event Object

All event handlers receive a synthetic event object:

Accessing Event Properties

function ClickTracker() {
  function handleClick(event) {
    console.log('Button text:', event.target.textContent);
    console.log('Click coordinates:', event.clientX, event.clientY);
    console.log('Ctrl key held?', event.ctrlKey);
  }

  return <button onClick={handleClick}>Click Me</button>;
}

Preventing Default Behavior

function LinkButton() {
  function handleClick(event) {
    event.preventDefault();  // Prevent navigation
    console.log('Link clicked, but not navigating');
  }

  return (
    <a href="https://example.com" onClick={handleClick}>
      Click (won't navigate)
    </a>
  );
}

Stopping Propagation

function NestedButtons() {
  function handleOuterClick() {
    console.log('Outer clicked');
  }

  function handleInnerClick(event) {
    event.stopPropagation();  // Don't trigger outer click
    console.log('Inner clicked');
  }

  return (
    <div onClick={handleOuterClick} style={{ padding: '20px', background: '#eee' }}>
      Outer
      <button onClick={handleInnerClick}>Inner (stops propagation)</button>
    </div>
  );
}

onChange - Handling Input

Used for text inputs, textareas, and selects:

Text Input

function NameInput() {
  const [name, setName] = useState('');

  function handleChange(event) {
    setName(event.target.value);
  }

  return (
    <div>
      <input 
        type="text" 
        value={name} 
        onChange={handleChange}
        placeholder="Enter your name"
      />
      <p>Hello, {name}!</p>
    </div>
  );
}

Inline onChange

function NameInput() {
  const [name, setName] = useState('');

  return (
    <div>
      <input 
        type="text" 
        value={name} 
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter your name"
      />
      <p>Hello, {name}!</p>
    </div>
  );
}

Checkbox

function AgreementCheckbox() {
  const [agreed, setAgreed] = useState(false);

  return (
    <div>
      <label>
        <input 
          type="checkbox" 
          checked={agreed}
          onChange={(e) => setAgreed(e.target.checked)}
        />
        I agree to the terms
      </label>
      <button disabled={!agreed}>Submit</button>
    </div>
  );
}

Select Dropdown

function CountrySelector() {
  const [country, setCountry] = useState('usa');

  return (
    <div>
      <select value={country} onChange={(e) => setCountry(e.target.value)}>
        <option value="usa">United States</option>
        <option value="uk">United Kingdom</option>
        <option value="canada">Canada</option>
      </select>
      <p>Selected: {country}</p>
    </div>
  );
}

onSubmit - Handling Form Submission

Basic Form

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

  function handleSubmit(event) {
    event.preventDefault();  // Prevent page reload
    console.log('Submitting:', { email, password });
    // Send to API here
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
      />
      <button type="submit">Log In</button>
    </form>
  );
}

Form with Validation

function SignupForm() {
  const [email, setEmail] = useState('');
  const [error, setError] = useState('');

  function handleSubmit(event) {
    event.preventDefault();
    
    // Validate
    if (!email.includes('@')) {
      setError('Invalid email address');
      return;
    }
    
    setError('');
    console.log('Valid email:', email);
    // Submit to API
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      {error && <p style={{ color: 'red' }}>{error}</p>}
      <button type="submit">Sign Up</button>
    </form>
  );
}

Other Common Events

onMouseEnter / onMouseLeave

function HoverButton() {
  const [isHovered, setIsHovered] = useState(false);

  return (
    <button
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
      style={{ backgroundColor: isHovered ? 'blue' : 'gray' }}
    >
      {isHovered ? 'Hovering!' : 'Hover me'}
    </button>
  );
}

onFocus / onBlur

function FocusInput() {
  const [isFocused, setIsFocused] = useState(false);

  return (
    <input
      type="text"
      onFocus={() => setIsFocused(true)}
      onBlur={() => setIsFocused(false)}
      placeholder={isFocused ? 'Focused!' : 'Click to focus'}
      style={{ borderColor: isFocused ? 'blue' : 'gray' }}
    />
  );
}

onKeyDown / onKeyPress

function SearchBox() {
  const [search, setSearch] = useState('');

  function handleKeyDown(event) {
    if (event.key === 'Enter') {
      console.log('Search for:', search);
    }
    if (event.key === 'Escape') {
      setSearch('');
    }
  }

  return (
    <input
      type="text"
      value={search}
      onChange={(e) => setSearch(e.target.value)}
      onKeyDown={handleKeyDown}
      placeholder="Press Enter to search, Esc to clear"
    />
  );
}

Common Patterns

1. Toggle Boolean State

function ToggleButton() {
  const [isOn, setIsOn] = useState(false);

  return (
    <button onClick={() => setIsOn(!isOn)}>
      {isOn ? 'ON' : 'OFF'}
    </button>
  );
}

2. Increment/Decrement Counter

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count - 1)}>-</button>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

3. Add Item to List

function TodoList() {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState('');

  function handleAdd() {
    if (input.trim()) {
      setTodos([...todos, input]);
      setInput('');
    }
  }

  return (
    <div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyDown={(e) => e.key === 'Enter' && handleAdd()}
      />
      <button onClick={handleAdd}>Add</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>{todo}</li>
        ))}
      </ul>
    </div>
  );
}

4. Delete Item from List

function ItemList() {
  const [items, setItems] = useState(['Apple', 'Banana', 'Orange']);

  function handleDelete(index) {
    setItems(items.filter((_, i) => i !== index));
  }

  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>
          {item}
          <button onClick={() => handleDelete(index)}>Delete</button>
        </li>
      ))}
    </ul>
  );
}

5. Multiple Input Form

function UserForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    age: ''
  });

  function handleChange(event) {
    const { name, value } = event.target;
    setFormData({
      ...formData,
      [name]: value
    });
  }

  function handleSubmit(event) {
    event.preventDefault();
    console.log('Form data:', formData);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="name"
        value={formData.name}
        onChange={handleChange}
        placeholder="Name"
      />
      <input
        name="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <input
        name="age"
        value={formData.age}
        onChange={handleChange}
        placeholder="Age"
      />
      <button type="submit">Submit</button>
    </form>
  );
}

Best Practices

DO:

// Use named functions for complex handlers
function handleComplexOperation() {
  // ... lots of logic
}
<button onClick={handleComplexOperation}>Do Thing</button>

// Prevent default for forms
function handleSubmit(event) {
  event.preventDefault();
  // ...
}

// Use controlled components (value + onChange)
<input value={name} onChange={(e) => setName(e.target.value)} />

// Pass functions, not function calls
<button onClick={handleClick}>Click</button>  // ✅

// Use arrow functions to pass arguments
<button onClick={() => handleClick(id)}>Click</button>

DON’T:

// Don't call the function immediately
<button onClick={handleClick()}>Click</button>  // ❌ Calls on render!

// Don't forget preventDefault in forms
function handleSubmit(event) {
  // Missing event.preventDefault() - page will reload!
  console.log('submitting');
}

// Don't use uncontrolled components without a reason
<input onChange={(e) => setName(e.target.value)} />  // ❌ Missing value prop

// Don't create functions inside JSX in loops (performance)
{items.map(item => (
  <button onClick={() => { /* complex logic */ }}>  // ❌ Bad in loops
    {item}
  </button>
))}

Event Handler Performance

Avoid Creating Functions in Render (for large lists)

// ❌ Bad - creates new function on every render
function ItemList({ items }) {
  return items.map(item => (
    <button onClick={() => console.log(item.id)}>
      {item.name}
    </button>
  ));
}

// ✅ Better - use useCallback for expensive operations
function ItemList({ items }) {
  const handleClick = useCallback((id) => {
    console.log(id);
  }, []);

  return items.map(item => (
    <button onClick={() => handleClick(item.id)}>
      {item.name}
    </button>
  ));
}

Summary

Common Events:

  • onClick - Button/element clicks
  • onChange - Input value changes
  • onSubmit - Form submission
  • onMouseEnter / onMouseLeave - Hover
  • onFocus / onBlur - Focus state
  • onKeyDown - Keyboard input

Key Concepts:

  • Use camelCase: onClick, not onclick
  • Pass function reference: onClick={handleClick}
  • Event object: event.target.value, event.preventDefault()
  • Controlled components: value={state} + onChange={handler}

Form Handling:

function Form() {
  const [value, setValue] = useState('');

  function handleSubmit(event) {
    event.preventDefault();  // Prevent page reload
    console.log(value);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input 
        value={value} 
        onChange={(e) => setValue(e.target.value)} 
      />
      <button type="submit">Submit</button>
    </form>
  );
}