State & InteractionBeginner8 min05 / 9

Handling Events

Learn how to make your React components respond to clicks, keystrokes, and input changes by wiring up event handlers the right way.

A component that never responds to the user is really just a fancy brochure. The magic of interactive UIs — buttons that do things, inputs that react as you type, forms that submit — all comes down to event handling. In this lesson you'll learn how React lets you listen for user actions, respond to them with functions, and ultimately use them to drive state changes. By the end, clicking a button will feel like a superpower you actually understand.

#Events in HTML vs. React

If you've seen a little HTML before, you may have noticed attributes like onclick on buttons. React has a very similar idea, but with two key differences:

  1. The attribute names are camelCaseonClick instead of onclick, onChange instead of onchange.
  2. You pass a JavaScript function, not a string of code.

Here is the simplest possible example:

The onClick prop receives the function itself — not a call to the function.
function HelloButton() {
  function handleClick() {
    alert('Hello from React!');
  }

  return <button onClick={handleClick}>Say Hello</button>;
}

#The Golden Rule: Pass the Function, Don't Call It

This is the single most common mistake beginners make, and it trips up experienced developers too. When you wire up an event handler, you hand React the function so React can call it later when the event happens. If you call it yourself right now, it runs immediately — before any click has occurred.

Think of it like

Handing Over Your Phone Number vs. Calling Right Now

Imagine a friend asks, "Can someone call me when the pizza arrives?"

  • Correct: You hand them your phone number. They call you when the pizza arrives.
  • Wrong: You call them right now, before the pizza is even ordered.

onClick={handleClick} is handing over your number. onClick={handleClick()} is calling immediately — and React will run it during render, not on click.

Spot the difference: parentheses () after the name means "call now".
// CORRECT: pass the function reference
<button onClick={handleClick}>Click me</button>

// WRONG: calling the function immediately during render
<button onClick={handleClick()}>Click me</button>
Common mistake

The Invisible Bug

If you write onClick={handleClick()}, your component may seem to work at first — but you'll notice it fires on every render, or causes an infinite loop if it updates state. React won't throw an error; the code just does the wrong thing silently. Always double-check for stray parentheses.

#The Event Object

When React calls your handler, it passes along an event object — a bundle of information about what just happened. For a click, you can read where the mouse was. For a keystroke, you can read which key was pressed. For an input change, the most useful property is event.target.value — the current text in the field.

You can name this parameter anything, but e or event are the conventions:

React passes a synthetic event object — it behaves just like a native browser event.
function ClickLogger() {
  function handleClick(e) {
    console.log('Button clicked!');
    console.log('Mouse X position:', e.clientX);
  }

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

#Handling Input Changes with onChange

The onChange event fires every time the value inside an input changes — on every single keystroke. This is how React keeps your UI in sync with what the user is typing. Paired with useState, it forms the foundation of controlled inputs (more on those in the next lesson). Here's a sneak peek:

e.target.value holds the current text. Passing it to setName re-renders the component instantly.
import { useState } from 'react';

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

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

  return (
    <div>
      <input value={name} onChange={handleChange} placeholder="Type your name" />
      <p>Hello, {name || 'stranger'}!</p>
    </div>
  );
}

#Passing Extra Arguments to Handlers

Sometimes you need to pass extra data to your handler — for example, when you have a list of items and each delete button needs to know which item to delete. The trick is to wrap your handler in an arrow function inside the JSX:

The arrow function () => handleDelete(item) is a new function that, when called, will call handleDelete with the right item.
function TodoList() {
  const items = ['Buy milk', 'Walk the dog', 'Learn React'];

  function handleDelete(item) {
    console.log('Deleting:', item);
  }

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

Inline Arrow Functions Are Fine Here

You might wonder: doesn't creating a new arrow function on every render waste memory? For most cases — lists of a few dozen items — the answer is no, the cost is negligible. Optimize only when you have measured a real performance problem, not in advance.

Quick check

Which of the following correctly wires up a click handler in React?

You now have all the core tools for interactive React: onClick for buttons, onChange for inputs, the event object for context, and the rule that you always pass functions rather than calling them. These patterns appear in virtually every React component you'll ever write — you're already thinking like a React developer.

Key takeaways

  • Use camelCase event props like onClick and onChange — not the lowercase HTML versions.
  • Always pass the function reference (onClick={fn}), never call it (onClick={fn()}) — stray parentheses are a silent, common bug.
  • React passes an event object to your handler; use e.target.value to read what's inside an input.
  • To pass extra arguments, wrap your handler in an arrow function: onClick={() => handleDelete(item)}.
  • onChange fires on every keystroke — that's what makes live, responsive inputs possible.
Practice challenges
Test yourself · earn XP
0/4
Fix the bug#1

This code has a bug — what's wrong?

fix-bug
function HelloButton() {
  function handleClick() {
    alert('Hello from React!');
  }

  return <button onClick={handleClick()}>Say Hello</button>;
}
Predict the output#2

This handler logs a message. When does the console.log run, given how onClick is wired up here?

predict-output
function Logger() {
  function report() {
    console.log('clicked!');
  }

  return <button onClick={report}>Go</button>;
}
Fill in the blank#3

This controlled input keeps state in sync as the user types. Inside handleChange, fill in the property that holds the input's current text so it can be passed to setName.

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

  function handleChange(e) {
    setName(e.target.);
  }

  return <input value={name} onChange={handleChange} />;
}
Reorder the lines#4

Arrange these lines into a TodoList that maps over items and gives each Delete button an onClick that passes the correct item to handleDelete.

1
    <button key={item} onClick={() => handleDelete(item)}>Delete</button>
2
}
3
function TodoList({ items }) {
4
  ));
5
  function handleDelete(item) { console.log('Deleting:', item); }
6
  return items.map((item) => (
Your turn
Practice exercise

Build a small 'Like' button component. It should display a heart button and a count that starts at 0. Each click on the button should increase the count by 1. As a bonus, change the button label to show the current count: e.g. '♥ 3 Likes'.

Try it live — edit the code and hit Run to see it rendered:

solution.jsx · editable