Skip to content

Events

You've built a button. Now it needs to actually do something when someone clicks it, save a form, open a menu, delete an item. React handles this the same way it handles everything else: through props. Instead of calling addEventListener like you did in the JavaScript events chapter, you pass a function directly to a prop on the element you want to listen on. The idea is the same, a function that runs when something happens, but the way you wire it up is a React pattern of its own.

Attaching a handler

jsx
function SaveButton() {
  function handleClick() {
    console.log('clicked')
  }
  return <button onClick={handleClick}>Save</button>
}

Event props are named in camelCase, matching the event they listen for: onClick for clicks, onChange for a field's value changing, onSubmit for a form being sent. You write the prop on the element, and give it a function to run.

handleClick is defined inside the component, then passed to onClick. React calls it whenever the button is clicked. You're passing the function by name, handleClick. Calling it, handleClick(), would run it immediately, during render. You are handing React the function to run later, not running it yourself right now.

When you need details about what happened, React passes your handler an event object with the same familiar properties, target, preventDefault(), and so on, that you'd expect from addEventListener. Name a parameter to catch it, conventionally event:

jsx
function SearchBox() {
  function handleChange(event) {
    console.log(event.target.value)
  }
  return <input onChange={handleChange} />
}

event.target is the element the event fired on, and event.target.value is the current text in the field. You only need to reach for the event object when a handler actually needs that information. A button's onClick often does not.

The most common mistake

Because the handler is a function you're handing to a prop, this is a mistake that catches nearly everyone at some point:

jsx
// Wrong: calls handleClick immediately, while the component renders
<button onClick={handleClick()}>Save</button>

// Right: passes the function itself, React calls it later, on click
<button onClick={handleClick}>Save</button>

The parentheses are the whole difference. handleClick() runs the function right then, during render, and whatever it returns (usually undefined) is what ends up on onClick. handleClick hands React the function itself, to call whenever the click actually happens. If a handler seems to fire on its own the moment the page loads, this is almost always why.

Passing handlers down as props

A handler doesn't have to live in the same component as the element it's attached to. You can define it in a parent and pass it down to a child as a prop, the same way you'd pass any other value:

jsx
function SaveButton({ onSave }) {
  return <button onClick={onSave}>Save</button>
}

function Form() {
  function handleSave() {
    console.log('saved')
  }
  return <SaveButton onSave={handleSave} />
}

Form owns handleSave and passes it into SaveButton under the prop name onSave. SaveButton doesn't know or care what the function does, it wires it up to onClick. This is how a click on a small, reusable component ends up triggering logic that lives higher up in your app, closer to the state or data that logic needs.

Preventing the default behavior

Some events come with browser behavior attached, and submitting a form is the one you'll run into first. By default, submitting a form reloads the page, which throws away anything your component was doing. Call event.preventDefault() inside the handler to stop that:

jsx
function SignupForm() {
  function handleSubmit(event) {
    event.preventDefault()
    console.log('form submitted')
  }
  return (
    <form onSubmit={handleSubmit}>
      <input type="email" />
      <button type="submit">Sign up</button>
    </form>
  )
}

Attach onSubmit to the <form> itself, not onClick to the button. That way pressing Enter inside the field submits the form too, not only clicking the button.

The event object React hands you isn't the native browser event. It's a wrapper React builds around it, normalizing the differences between browsers so the same code works everywhere without you checking which browser fired the event. It carries the properties you'd expect, target, preventDefault(), key, and so on, but it's React's own object.

React also doesn't attach a listener to every element that has an onClick or onChange prop. It attaches a single listener at the root of your app and uses event bubbling to figure out which component's handler should run for a given event. This is why event handling works the same way for elements that don't exist yet when the app first renders, an item added to a list later still fires its onClick correctly, because the listener at the root was never tied to that specific element in the first place.

JunoHandlers are functions on props Event props are named like onClick and onChange, and you give them a function to run, not the result of running one. Write onClick={handleClick}, never onClick={handleClick()}. When a handler needs details about what happened, catch them with a parameter named event.
JunoHandlers are functions on props Attach handlers with camelCase props like onClick, onChange, and onSubmit, and pass the function reference, not a call to it. Define a handler in a parent and pass it down to a child as a prop when the logic belongs higher up. And call event.preventDefault() in a submit handler, or the page reloads out from under your state.
JunoHandlers are functions on props React's event object is a synthetic wrapper over the native one, normalized across browsers, and React attaches a single listener at the root rather than one per element, relying on bubbling to route the event to the right handler. The everyday rule still holds underneath it: hand React the function, don't call it yourself, and reach for event.preventDefault() on a form's onSubmit to take over from the browser's default navigation.

Next up: Conditional rendering, where what a component returns starts to depend on the data it has.