Skip to content
Laviyx Learn

Try

React course menu

Handling Events in React: onClick, onChange and More

Respond to user actions in React: event handler props like onClick and onChange, passing functions instead of calling them, passing arguments, the event object, preventDefault for forms, event propagation and stopPropagation, and naming handler props.

Beginner10 min readUpdated
On this page

Events let users interact with your app: clicking, typing, submitting, hovering. In React you attach event handlers with camelCase props such as onClick, onChange, onSubmit, onMouseEnter and onKeyDown.

Your first handler#

JSX
import { useState } from "react";

export default function App() {
  const [message, setMessage] = useState("Nothing clicked yet");

  function handleSave() {
    setMessage("Saved at 10:30");
  }

  return (
    <div>
      <button onClick={handleSave}>Save</button>
      <button onClick={() => setMessage("Cancelled")}>Cancel</button>
      <p>{message}</p>
    </div>
  );
}
Result

You can pass a named function (handleSave) or write an inline arrow function. The common naming convention is handleSomething for the function and onSomething for the prop.

Pass the function, do not call it#

JSX
<button onClick={handleSave}>     // ✅ React calls it when clicked
<button onClick={handleSave()}>   // ❌ calls it immediately, during rendering

With parentheses, the function runs while the component renders, not on click. If it also updates state, that triggers another render and an endless loop ("Too many re-renders").

Passing arguments#

Wrap the call in an arrow function:

JSX
import { useState } from "react";

const sizes = ["S", "M", "L", "XL"];

export default function App() {
  const [selected, setSelected] = useState("M");

  return (
    <div>
      {sizes.map((size) => (
        <button
          key={size}
          onClick={() => setSelected(size)}
          style={{ marginRight: 6, fontWeight: size === selected ? "bold" : "normal" }}
        >
          {size}
        </button>
      ))}
      <p>Selected size: {selected}</p>
    </div>
  );
}
Result

The event object#

Handlers receive an event object with details about what happened: e.target (the element), e.key for keyboard events, mouse position, and more:

JSX
import { useState } from "react";

export default function App() {
  const [log, setLog] = useState("Type in the box and press Enter");

  return (
    <div>
      <input
        placeholder="Search..."
        onKeyDown={(e) => {
          if (e.key === "Enter") setLog(`You searched for "${e.target.value}"`);
        }}
      />
      <p>{log}</p>
    </div>
  );
}
Result

React's event objects work the same way across browsers and have the same main properties and methods as native DOM events.

Preventing default behaviour#

Some events have built-in browser behaviour; a form submit reloads the page. Call e.preventDefault() to stop it and handle the submission in JavaScript:

JSX
import { useState } from "react";

export default function App() {
  const [result, setResult] = useState("");

  function handleSubmit(e) {
    e.preventDefault();
    const data = new FormData(e.target);
    setResult(`Subscribed: ${data.get("email")}`);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" required placeholder="you@example.com" />
      <button type="submit">Subscribe</button>
      <p>{result}</p>
    </form>
  );
}
Result

The Forms lesson shows the usual React approach of keeping input values in state.

Propagation: events bubble up#

An event on a child also triggers handlers on its parents ("bubbling"). e.stopPropagation() stops it going further:

JSX
import { useState } from "react";

export default function App() {
  const [log, setLog] = useState([]);
  const add = (text) => setLog((prev) => [...prev, text]);

  return (
    <div>
      <div onClick={() => add("card clicked")} style={{ border: "1px solid #ccc", padding: 12, cursor: "pointer" }}>
        Product card
        <button onClick={() => add("like clicked")} style={{ marginLeft: 8 }}>♥ Like (bubbles)</button>
        <button
          onClick={(e) => {
            e.stopPropagation();
            add("share clicked");
          }}
          style={{ marginLeft: 8 }}
        >
          Share (stops)
        </button>
      </div>
      <button onClick={() => setLog([])} style={{ marginTop: 8 }}>Clear log</button>
      <ol>{log.map((entry, i) => <li key={i}>{entry}</li>)}</ol>
    </div>
  );
}
Result

Clicking Like logs "like clicked" and then "card clicked"; clicking Share logs only "share clicked". (Index keys are fine here: the log only ever grows at the end.)

Custom event props on your components#

Your own components can accept handler props with any name, which lets parents decide what happens:

JSX
import { useState } from "react";

function LikeButton({ liked, onToggle }) {
  return <button onClick={onToggle}>{liked ? "♥ Liked" : "♡ Like"}</button>;
}

export default function App() {
  const [liked, setLiked] = useState(false);
  return <LikeButton liked={liked} onToggle={() => setLiked(!liked)} />;
}
Result

Common mistakes#

Practice#

Practice exercise

Build a colour picker: three buttons "Red", "Green" and "Blue". Clicking one changes the background colour of a box below them and shows the chosen name.

Show hint

Keep the chosen colour in useState, map over an array of colour names to create the buttons, and use style={{ background: colour }} on the box.

Show solution
JSX
import { useState } from "react";

const colours = ["red", "green", "blue"];

export default function App() {
  const [colour, setColour] = useState("red");
  return (
    <div>
      {colours.map((c) => (
        <button key={c} onClick={() => setColour(c)} style={{ marginRight: 6, textTransform: "capitalize" }}>
          {c}
        </button>
      ))}
      <div style={{ background: colour, height: 60, marginTop: 8 }} />
      <p>Chosen: {colour}</p>
    </div>
  );
}
Result

Key takeaway#

Next: State with useState.

Finished this lesson?