Skip to main content

    Lesson 4 • Beginner

    Handling Events ⚛️

    By the end of this lesson you'll be able to make buttons, inputs, and forms do something — wiring up onClick, onChange, and onSubmit handlers, reading the event object, and stopping the page from reloading.

    What You'll Learn

    • Attach handlers with onClick, onChange, and onSubmit
    • Write event handler functions and pass them by name
    • Pass arguments to a handler using an arrow wrapper
    • Read the synthetic event object (e.target.value, e.type)
    • Use e.preventDefault() to stop a form reloading the page
    • Avoid the #1 trap: calling the handler instead of passing it

    1. Click Events & Handler Functions

    An event is something the user does — a click, a keypress, a form submit. An event handler is a function you write that runs in response. In React you attach one with a camelCase prop like onClick (not the lowercase onclick from HTML), and you pass the function by name. The golden rule: onClick={handleClick} — no parentheses. The parentheses would call the function immediately; you want to hand the function to React so it can call it on each click.

    function LikeButton() {
      // 1) Define the handler — a plain function that does the work.
      function handleClick() {
        alert("You liked this post!");
      }
    
      // 2) Pass the function BY NAME — no parentheses.
      //    React will call handleClick FOR you when the button is clicked.
      return <button onClick={handleClick}>Like</button>;
    }

    Read the worked example below. The handleLike function is exactly what you'd pass to onClick — here we call it three times to simulate three clicks and watch the count climb.

    Worked example: a click handler

    Run it and watch each 'click' call the handler.

    Try it Yourself »
    JavaScript
    // React calls your handler. Here we run the SAME handler logic in plain JS
    // so you can see exactly what happens each time a "click" fires.
    
    let likeCount = 0;
    
    // This is the kind of function you'd pass to onClick={...}
    function handleLike() {
      likeCount = likeCount + 1;
      console.log("Liked! Total likes: " + likeCount);
    }
    
    // Simulate three clicks. In React, the runtime calls handleLike for you.
    handleLike();   // Liked! Total likes: 1
    handleLike();   // Liked! Total likes: 2
    handleLike(); 
    ...

    The most important detail: pass it, don't call it

    This single distinction trips up almost every beginner. Burn it in now:

    // ❌ WRONG — handleClick() runs the MOMENT the component renders,
    //    not when you click. The return value (undefined) becomes the handler.
    <button onClick={handleClick()}>Like</button>
    
    // ✅ RIGHT — pass the function itself. React calls it on each click.
    <button onClick={handleClick}>Like</button>
    
    // ✅ ALSO RIGHT — wrap it in an arrow function when you need to
    //    pass an argument (see section 2).
    <button onClick={() => handleClick("post-42")}>Like</button>

    Now you try. Finish the handler and "click" the button by calling it.

    🎯 Your turn: write a click handler

    Fill in the ___ blanks, then check your output against the expected lines.

    Try it Yourself »
    JavaScript
    // 🎯 YOUR TURN — fill in the blanks marked ___ then press "Try it Yourself".
    
    let count = 0;
    
    // 1) Finish this handler so it adds 1 to count and logs the new total.
    function handleIncrement() {
      count = ___;                       // 👉 count + 1
      console.log("Count is now: " + count);
    }
    
    // In React you would write:  <button onClick={handleIncrement}>+1</button>
    // Notice: NO parentheses — you pass the function, you don't call it.
    
    // 2) "Click" the button three times by calling the handler 
    ...

    2. Passing Arguments to Handlers

    What if your handler needs data — like which item to delete? You can't write onClick={deletePost(id)} because, as you just learned, that calls deletePost immediately during render. The fix is an arrow wrapper: onClick={() => deletePost(id)}. The arrow function is the handler; it sits and waits, and only calls deletePost(id) when the click actually happens.

    {posts.map((id) => (
      <button key={id} onClick={() => deletePost(id)}>
        Delete #{id}
      </button>
    ))}

    Worked example: passing an argument

    See how the arrow wrapper delays the call until 'click'.

    Try it Yourself »
    JavaScript
    // Passing arguments: wrap the call in an arrow function () => fn(arg).
    // The arrow function is the handler; it calls deletePost WHEN clicked.
    
    function deletePost(id) {
      console.log("Deleting post #" + id);
    }
    
    const posts = [101, 102, 103];
    
    // In JSX you'd write:  onClick={() => deletePost(id)}
    // Here we build those arrow handlers and call them to prove they work.
    posts.forEach((id) => {
      const handler = () => deletePost(id);   // the wrapper React would store
      handler();                 
    ...

    3. The Event Object & onChange

    When React calls your handler, it passes one argument: the event object. React calls it a SyntheticEvent — a cross-browser wrapper around the native DOM event, so the same properties work in every browser. The one you'll use constantly is e.target.value: the current text of an input. The onChange event fires on every keystroke, which is how you keep React state in sync with what the user types (a controlled component).

    Worked example: reading the event object

    See what e.target.value, e.type, and e.target.name give you.

    Try it Yourself »
    JavaScript
    // The event object (React calls it a SyntheticEvent) is passed to your
    // handler automatically. It's a cross-browser wrapper around the real
    // DOM event, with the same properties everywhere.
    
    // Pretend React handed us this event from typing "Hi" in a text box:
    const fakeEvent = {
      type: "change",
      target: { value: "Hi", name: "username" },
      preventDefault() { console.log("(default behaviour prevented)"); },
    };
    
    // In an onChange handler you read the typed text from e.target.value:
    functio
    ...

    In real JSX, that handler wires an input to state like this:

    function SearchBar() {
      const [query, setQuery] = useState("");
    
      // e.target.value is whatever the user has typed so far.
      function handleChange(e) {
        setQuery(e.target.value);
      }
    
      return (
        <input
          type="text"
          value={query}              // controlled: React owns the value
          onChange={handleChange}    // runs on EVERY keystroke
          placeholder="Search..."
        />
      );
    }

    4. Forms & preventDefault()

    Forms have a built-in browser behaviour: submitting one reloads the page. In a React app that's a disaster — a reload throws away all your state and re-fetches everything. So in your onSubmit handler you call e.preventDefault() as the very first line. That cancels the default reload and lets you handle the submit in JavaScript instead. Put it on <form onSubmit={...}>, not the button — that way pressing Enter inside the form works too.

    function SearchForm() {
      const [query, setQuery] = useState("");
    
      function handleSubmit(e) {
        e.preventDefault();          // ⛔ stop the browser reloading the page
        console.log("Searching for:", query);
        // ...send the search request here...
      }
    
      return (
        <form onSubmit={handleSubmit}>
          <input value={query} onChange={(e) => setQuery(e.target.value)} />
          <button type="submit">Search</button>
        </form>
      );
    }

    Worked example: preventing the default reload

    See the order: preventDefault first, then handle the data.

    Try it Yourself »
    JavaScript
    // onSubmit fires when a form is submitted (button click OR Enter key).
    // By default the browser RELOADS the page — which wipes your React state.
    // e.preventDefault() stops that so YOU handle the submit in JavaScript.
    
    function handleSubmit(e) {
      e.preventDefault();                 // without this, the page reloads
      console.log("Form handled in JS — no reload!");
      console.log("Query:", e.target.query);
    }
    
    // Simulate the event React would pass to onSubmit:
    const submitEvent = {
      target: { 
    ...

    Your turn — complete the submit handler so the page is never reloaded and the typed email is logged.

    🎯 Your turn: handle a form submit

    Stop the reload and read e.target.value, then check the expected output.

    Try it Yourself »
    JavaScript
    // 🎯 YOUR TURN — complete the form submit handler.
    
    const event = {
      target: { value: "alice@example.com" },
      preventDefault() { console.log("(page reload stopped)"); },
    };
    
    function handleSubmit(e) {
      // 1) Stop the browser from reloading the page.
      ___;                          // 👉 e.preventDefault()
    
      // 2) Read what the user typed and log it.
      const email = ___;            // 👉 e.target.value
      console.log("Signing up: " + email);
    }
    
    handleSubmit(event);
    
    // ✅ Expected output:
    //  
    ...

    Common Errors (and the fix)

    • Calling the handler immediately: onClick={handleClick()} runs on render and fires once (or loops forever if it sets state). Pass it by name: onClick={handleClick}, or wrap it: onClick={() => handleClick(id)}.
    • "Maximum update depth exceeded": the classic symptom of the bug above — onClick={setCount(count + 1)} calls setCount during render, which re-renders, which calls it again. Use an arrow wrapper: onClick={() => setCount(count + 1)}.
    • The page reloads / state vanishes on submit: you forgot e.preventDefault(). Make it the first line of your onSubmit handler.
    • "Cannot read properties of undefined (reading 'value')": your handler reads e.target.value but you forgot to accept the event: write function handleChange(e) {… with the e parameter.
    • "this is undefined" in an old class component: a method passed as onClick={this.handleClick} loses its this. Bind it in the constructor (this.handleClick = this.handleClick.bind(this)) or use a class field arrow. Function components with hooks avoid this entirely — another reason they're now standard.

    Pro Tips

    • 💡 Name handlers handleX: handleSubmit, handleDelete, handleNameChange — the convention makes code instantly readable.
    • 💡 Only add an arrow wrapper when you need an argument. onClick={handleClick} is cleaner than onClick={() => handleClick()} when there's no argument.
    • 💡 preventDefault goes first. Call it at the top of the handler so an early return can never skip it.
    • 💡 Put onSubmit on the <form>, not the button — then Enter-to-submit works for free.

    📋 Quick Reference

    TaskCode
    Click handler<button onClick={handleClick}>
    Pass an argumentonClick={() => del(id)}
    Read input textonChange={(e) => set(e.target.value)}
    Form submit<form onSubmit={handleSubmit}>
    Stop page reloade.preventDefault()
    Key pressede.key === 'Enter'
    Stop bubblinge.stopPropagation()

    Frequently Asked Questions

    Q: Why is it onClick and not onclick?

    JSX uses camelCase for event props, so it's onClick, onChange, onSubmit. It's also a different mechanism from HTML — you pass a real JavaScript function, not a string of code.

    Q: When do I need the arrow function () => wrapper?

    Only when you need to pass an argument, e.g. onClick={() => deletePost(id)}. If the handler takes no arguments, pass it by name: onClick={handleClick}.

    Q: Where does the e in handleChange(e) come from?

    React passes it automatically. Whenever your handler is the one React calls directly, the first argument is the SyntheticEvent — just add an e parameter to receive it.

    Q: What happens if I forget e.preventDefault() in a form?

    The browser does its default thing and reloads the page, which destroys your React state and re-runs the whole app. Always call it first in an onSubmit handler.

    Mini-Challenge: A Toggle Handler

    No blanks this time — just a brief and an outline. Write the handler yourself, "click" it three times, and match the expected output. This is the exact pattern behind every show/hide, dark-mode, and accordion button you'll ever build.

    🎯 Mini-Challenge: build a toggle handler

    Write toggle() yourself and call it to simulate clicks.

    Try it Yourself »
    JavaScript
    // 🎯 MINI-CHALLENGE: A toggle handler
    //
    // Imagine a "Show / Hide" button. You'll write the handler logic.
    //
    // 1. Create a variable "isOpen" set to false.
    // 2. Write a function toggle() that flips isOpen (true -> false -> true)
    //    and logs:  "Panel is now: open"  or  "Panel is now: closed".
    //    Hint: isOpen = !isOpen;  flips a boolean.
    // 3. In React you'd attach it with:  <button onClick={toggle}>Toggle</button>
    //    Here, "click" the button by calling toggle() three times.
    //
    // ✅ E
    ...

    🎉 Lesson Complete

    • ✅ Attach handlers with camelCase props: onClick, onChange, onSubmit
    • ✅ Pass a function by nameonClick={fn}, never onClick={fn()}
    • ✅ Use an arrow wrapper () => fn(arg) to pass arguments
    • ✅ React passes a SyntheticEvent e; read input text from e.target.value
    • ✅ Call e.preventDefault() first in onSubmit to stop the page reloading
    • Next lesson: Hooks — deep-dive into useState and useEffect, the engines behind every handler that updates the screen

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service