Skip to content

React Tutorial State and Events

Handling Click Events in React

Lesson 48 of 156 2 min read By Deepak Paswan Updated Oct 10, 2026

Runnable lesson · press Try it on the examples to edit and run them here.

The click is the event you will use most. This lesson puts the earlier ideas together in practical examples.

A counter with three buttons

App.jsx
JSX
function App() {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>+1</button>{" "}
      <button onClick={() => setCount((c) => c - 1)}>-1</button>{" "}
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

A toggle

Toggle with the opposite of the current value. Using the updater form means rapid clicks never get out of step:

App.jsx
JSX
function App() {
  const [open, setOpen] = React.useState(false);

  return (
    <div>
      <button onClick={() => setOpen((o) => !o)} aria-expanded={open}>
        {open ? "Hide details" : "Show details"}
      </button>
      {open ? <p>Here are the details</p> : <p>Details are hidden</p>}
    </div>
  );
}

aria-expanded tells screen readers whether the content is open.

Clicks in a list

Each item needs to know which item was clicked. Close over the item (or its id) in an arrow function:

App.jsx
JSX
function App() {
  const products = [
    { id: 1, name: "Pen" },
    { id: 2, name: "Book" },
  ];
  const [bought, setBought] = React.useState(0);

  return (
    <div>
      <p>Bought: {bought}</p>
      {products.map((p) => (
        <button key={p.id} onClick={() => setBought(bought + 1)}>Buy {p.name}</button>
      ))}
    </div>
  );
}

A link normally navigates. Call preventDefault() on the event to stop that, and think about whether a <button> is the better element:

App.jsx
JSX
function App() {
  const [msg, setMsg] = React.useState("Not clicked");
  function handleClick(e) {
    e.preventDefault();
    setMsg("Link clicked, page stayed");
  }
  return (
    <div>
      <a href="https://example.com" onClick={handleClick}>Run action</a>
      <p>{msg}</p>
    </div>
  );
}

If the thing you click performs an action rather than going somewhere, use <button type="button"> instead. Use real links for real navigation.

Clicks that should not bubble

A click on a child element also triggers click handlers on its parents (it bubbles). Stop that with e.stopPropagation():

App.jsx
JSX
function App() {
  const [parent, setParent] = React.useState(0);
  return (
    <div>
      <div onClick={() => setParent(parent + 1)} role="button" tabIndex={0} aria-label="Parent area" style={{ padding: 16, background: "#eee" }}>
        Parent area
        <button onClick={(e) => e.stopPropagation()}>Inner button</button>
      </div>
      <p>Parent clicks: {parent}</p>
    </div>
  );
}

Use stopPropagation sparingly; it can hide events from code that expects them. Bubbling is described in Event Bubbling.

Double clicks, right clicks and more

onDoubleClick, onContextMenu (right-click), onMouseDown, onMouseUp and onPointerDown exist too. Prefer onClick for buttons, because it also works with the keyboard (Enter and Space).

Disabled buttons and double submission

A button that triggers a slow action should be disabled while it works, so a second click cannot start it twice:

App.jsx
JSX
function App() {
  const [status, setStatus] = React.useState("idle");

  function handleSave() {
    setStatus("saving");
    setTimeout(() => setStatus("saved"), 1000);
  }

  return (
    <div>
      <button onClick={handleSave} disabled={status === "saving"}>
        {status === "saving" ? "Saving..." : "Save"}
      </button>
      {status === "saved" && <p>Saved</p>}
    </div>
  );
}

Key points

  • Use onClick on <button> for actions; use real links for navigation.
  • Use the updater form for toggles and counters.
  • preventDefault stops the default action; stopPropagation stops bubbling.

Exercise: A like button

Make a button that shows “Like” and toggles to “Liked” when clicked. Show the number of likes, which should go up by one when liked and down by one when un-liked. —solution

App.jsx
JSX
function App() {
  const [liked, setLiked] = React.useState(false);
  const likes = liked ? 1 : 0; // derived from state, no second state needed

  return (
    <div>
      <button onClick={() => setLiked((l) => !l)}>{liked ? "Liked" : "Like"}</button>
      <p>Likes: {likes}</p>
    </div>
  );
}

The number of likes can be calculated from liked, so it is not a second piece of state.