Topic 6 of 15 · React Developer

Topic 6 : Events

Lesson TL;DRTopic 6: Events 📖 7 min read · 🎯 intermediate · 🧭 Prerequisites: lifecyclemethods, stateandprops ⚠️ Note: this lesson was regenerated because the source content did not match the title. Why this ma...
7 min read·intermediate·react · events · synthetic-events · event-handling

Topic 6: Events

📖 7 min read · 🎯 intermediate · 🧭 Prerequisites: lifecycle-methods, state-and-props

⚠️ Note: this lesson was regenerated because the source content did not match the title.

Why this matters

Up until now, your React components have just been displaying things — text, buttons, forms. But none of it does anything when a user clicks or types. That's what events fix. The moment you understand React's event system, your UI stops being a poster and starts being an app. We're talking about responding to clicks, capturing what someone types into a field, and handling form submissions — the exact things users expect to happen the second they interact with your page.

What You'll Learn

  • How React's synthetic event system wraps native browser events
  • How to attach event handlers to JSX elements using onClick, onChange, onSubmit, and more
  • How to prevent default browser behavior and stop event propagation
  • How to pass arguments to event handlers without immediately invoking them

The Analogy

Think of Vizag's public suggestion box. Every citizen who walks up and drops a note is like a browser event — a click, a keypress, a form submission. The class aide standing beside the box is React's synthetic event system: they intercept every note, translate it into a standardized format that all students can read regardless of which district (browser) it came from, and then route it to the right department. The aide can also decide to shred a note before it reaches the mayor (preventing default behavior) or stop it from being passed up the chain of command (stopping propagation). Your event handler is simply the department that finally acts on the note.

Chapter 1: Synthetic Events

React doesn't hand you raw browser events. Instead it wraps them in a SyntheticEvent — a cross-browser abstraction that normalizes inconsistencies between Chrome, Firefox, Safari, and Edge so your handlers work identically everywhere.

Key properties you'll use on every SyntheticEvent:

Property / MethodWhat it gives you
event.targetThe DOM element that triggered the event
event.target.valueThe current value of an input element
event.typeThe event type string, e.g. "click"
event.preventDefault()Stops the browser's built-in action
event.stopPropagation()Stops the event from bubbling to parent elements
function LogEvent() {
  function handleClick(event) {
    console.log(event.type);        // "click"
    console.log(event.target);      // <button>...</button>
  }

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

React reuses SyntheticEvent objects for performance. If you need to access event properties asynchronously (e.g. inside a setTimeout), call event.persist() first — or destructure the values you need before the async call.

Chapter 2: Attaching Event Handlers

In JSX, event handlers are camelCase attributes that accept a function reference — not a function call. Passing onClick={handleClick} hands React the function to call later. Passing onClick={handleClick()} calls it immediately during render, which is almost never what you want.

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

// Wrong — invokes immediately on every render
<button onClick={handleClick()}>Click me</button>

You can define handlers inline with an arrow function when the logic is short:

<button onClick={() => console.log('Clicked!')}>Click me</button>

Or as a named method on a class component:

class Counter extends React.Component {
  handleIncrement() {
    this.setState((prev) => ({ count: prev.count + 1 }));
  }

  render() {
    return (
      <button onClick={this.handleIncrement.bind(this)}>
        Count: {this.state.count}
      </button>
    );
  }
}

In class components, always bind handlers in the constructor or use class property arrow functions to ensure this is correct.

Chapter 3: Common Event Types

React exposes event handlers for the full spectrum of DOM events. Here are the ones you'll reach for most:

Mouse Events

<button onClick={handleClick}>Click</button>
<div onDoubleClick={handleDoubleClick}>Double-click me</div>
<div onMouseEnter={handleHover}>Hover zone</div>
<div onMouseLeave={handleLeave}>Leave zone</div>

Keyboard Events

<input
  onKeyDown={(e) => console.log('Key down:', e.key)}
  onKeyUp={(e) => console.log('Key up:', e.key)}
/>

Form / Input Events

<input onChange={(e) => setValue(e.target.value)} />
<form onSubmit={handleSubmit}>...</form>
<select onChange={(e) => setChoice(e.target.value)}>...</select>
<textarea onChange={(e) => setText(e.target.value)} />

Focus Events

<input onFocus={() => setActive(true)} onBlur={() => setActive(false)} />

Clipboard Events

<input onCopy={handleCopy} onPaste={handlePaste} />

Chapter 4: Handling Forms

Form handling is one of the most common event patterns in React. The standard approach is a controlled component: you store the input value in state and update it on every onChange.

import { useState } from 'react';

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

  function handleSubmit(event) {
    event.preventDefault();           // stop the page from reloading
    console.log({ email, password }); // use the values
  }

  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>
  );
}

event.preventDefault() on line 9 is critical — without it, the browser performs a full-page navigation on every form submit.

Chapter 5: Preventing Default Behavior and Stopping Propagation

Preventing default stops the browser from executing its built-in response to an event.

// Prevent navigation when clicking an anchor
<a href="https://example.com" onClick={(e) => e.preventDefault()}>
  Stay here
</a>

Stopping propagation prevents the event from bubbling up to parent elements. This matters when a child and a parent both have handlers for the same event.

function Card() {
  function handleCardClick() {
    console.log('Card clicked');
  }

  function handleButtonClick(e) {
    e.stopPropagation(); // the Card's onClick will NOT fire
    console.log('Button clicked');
  }

  return (
    <div onClick={handleCardClick} style={{ padding: '20px', border: '1px solid #ccc' }}>
      <p>Clicking here triggers handleCardClick.</p>
      <button onClick={handleButtonClick}>Click only the button</button>
    </div>
  );
}

Without stopPropagation(), clicking the button fires both handleButtonClick and handleCardClick.

Chapter 6: Passing Arguments to Event Handlers

When you need to pass extra data to a handler (like an item ID from a list), wrap the call in an arrow function:

function ItemList({ items }) {
  function handleDelete(id) {
    console.log('Deleting item', id);
  }

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

The arrow function () => handleDelete(item.id) creates a new function reference per render. For large lists where this becomes a performance concern, consider useCallback — but don't optimize prematurely.

You can also use data-* attributes and read from event.target.dataset to avoid closures altogether:

<button data-id={item.id} onClick={handleDelete}>Delete</button>

function handleDelete(e) {
  const id = e.target.dataset.id;
  console.log('Deleting item', id);
}

Chapter 7: Events in Function Components with Hooks

Modern React code uses function components and hooks. Here's a complete counter that demonstrates onClick, onChange, and onKeyDown together:

import { useState } from 'react';

function SmartCounter() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(1);

  function handleIncrement() {
    setCount((prev) => prev + step);
  }

  function handleDecrement() {
    setCount((prev) => prev - step);
  }

  function handleStepChange(e) {
    const value = parseInt(e.target.value, 10);
    if (!isNaN(value) && value > 0) setStep(value);
  }

  function handleKeyDown(e) {
    if (e.key === 'ArrowUp') handleIncrement();
    if (e.key === 'ArrowDown') handleDecrement();
  }

  return (
    <div onKeyDown={handleKeyDown} tabIndex={0}>
      <h2>Count: {count}</h2>
      <button onClick={handleDecrement}>{step}</button>
      <button onClick={handleIncrement}>+ {step}</button>
      <label>
        Step size:
        <input type="number" value={step} onChange={handleStepChange} min="1" />
      </label>
      <p>Focus the box and use ↑ / ↓ keys to step.</p>
    </div>
  );
}
flowchart TD
    U([User Action]) --> SE[SyntheticEvent Created]
    SE --> H{Handler attached?}
    H -- Yes --> FN[Your handler function runs]
    H -- No --> BUB[Event bubbles to parent]
    FN --> PD{event.preventDefault\ncalled?}
    PD -- Yes --> STOP[Browser default blocked]
    PD -- No --> DEF[Browser default runs]
    FN --> SP{event.stopPropagation\ncalled?}
    SP -- Yes --> NOBUB[Bubbling halted]
    SP -- No --> BUB

🧪 Try It Yourself

Task: Build a controlled search box that filters a hard-coded list of Vizag citizens in real time as you type.

Success criterion: Typing "bi" should show only citizens whose names contain "bi" (case-insensitive). The list updates on every keystroke with no submit button needed.

Starter snippet:

import { useState } from 'react';

const CITIZENS = ['the trainer', 'the trainer', 'the trainer', 'the trainer', 'the trainer'];

export default function CitizenSearch() {
  const [query, setQuery] = useState('');

  const filtered = CITIZENS.filter((name) =>
    name.toLowerCase().includes(query.toLowerCase())
  );

  return (
    <div>
      <input
        type="text"
        placeholder="Search citizens..."
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <ul>
        {filtered.map((name) => (
          <li key={name}>{name}</li>
        ))}
      </ul>
    </div>
  );
}

Add a Clear button that resets the query to an empty string when clicked.

🔍 Checkpoint Quiz

Q1. Why does React use SyntheticEvents instead of native browser events?

A) To make events faster by skipping the DOM entirely
B) To provide a normalized, cross-browser consistent event interface
C) Because native events don't support target.value
D) To enforce immutability on event data

Q2. Given this code, what gets logged when the button is clicked?

function Parent() {
  return (
    <div onClick={() => console.log('parent')}>
      <button onClick={(e) => { e.stopPropagation(); console.log('child'); }}>
        Click
      </button>
    </div>
  );
}

A) parent, then child
B) child, then parent
C) Only child
D) Only parent

Q3. What is wrong with this handler attachment?

<button onClick={handleSave()}>Save</button>

A) handleSave must be an arrow function
B) handleSave() is called immediately on every render, not on click
C) onClick is spelled incorrectly; it should be on-click
D) Nothing is wrong; this is valid React

Q4. You have a list of 50 product cards. Each card has a Delete button that should pass the product's id to a handleDelete(id) function. How do you pass the id without invoking the function immediately?

A) onClick={handleDelete(product.id)}
B) onClick={() => handleDelete(product.id)}
C) onClick={handleDelete.bind(product.id)}
D) onClick="handleDelete(product.id)"

A1. B — SyntheticEvents normalize browser differences so your code doesn't need conditionals for each browser's quirks.

A2. C — stopPropagation() prevents the click from reaching the parent div, so only "child" is logged.

A3. B — Adding () invokes handleSave during rendering and passes its return value (likely undefined) to onClick instead of the function itself.

A4. B — Wrapping in an arrow function () => handleDelete(product.id) creates a function reference that React calls on click, passing product.id at that moment.

🪞 Recap

  • React wraps native browser events in SyntheticEvents for cross-browser consistency.
  • Attach handlers in JSX with camelCase props like onClick, onChange, and onSubmit — always pass a function reference, not a call.
  • Call event.preventDefault() to block browser defaults (e.g. page reload on form submit) and event.stopPropagation() to halt event bubbling.
  • Controlled form inputs keep their value in state and update it via onChange, giving you full control over the form data.
  • Pass arguments to handlers by wrapping them in an arrow function: onClick={() => handleDelete(item.id)}.

📚 Further Reading

Like this topic? It’s one of 15 in React Developer.

Block your seat for ₹2,500 and join the next cohort.