Skip to Content
Lesson 5

Handling User Events

Workplace Context

Web applications are rarely static displays of information. Users need to interact with them: clicking buttons, typing into forms, selecting options, hovering over elements. As a React developer, you will constantly write code that listens for these user actions (events) and responds accordingly, perhaps by updating the component’s state, triggering navigation, or fetching data. This lesson covers how to connect user interactions in the browser to the logic within your React components, making your applications truly interactive.

Learning Objectives

By the end of this lesson, learners will be able to:

  • Attach event handler functions to JSX elements using the on<EventName> syntax (e.g., onClick, onChange).
  • Define event handler functions both inline and as separate methods within a component.
  • Understand the basics of React’s SyntheticEvent object, which wraps the browser’s native event.
  • Prevent the default browser behavior for specific events (e.g., form submission) using event.preventDefault().
  • Implement child-to-parent communication by passing event handler functions down as props (callback pattern).

Handling Events in JSX

React makes handling events on DOM elements very similar to how you handle them in plain HTML, but with a few key differences:

  1. camelCase Naming: React event names are written in camelCase (e.g., onClick, onChange, onSubmit, onMouseEnter) instead of lowercase (e.g., onclick, onchange).
  2. Function References: You pass a function (or a reference to a function) as the event handler, rather than a string of code like in HTML attributes.

Basic Syntax:

<button onClick={handleClick}>Click Me</button> <input type="text" onChange={handleInputChange} /> <form onSubmit={handleFormSubmit}> {/* Form elements */} </form>

Here, handleClick, handleInputChange, and handleFormSubmit are expected to be JavaScript functions defined within your component’s scope.


Event Handler Functions

There are two common ways to define the functions that handle events:

  1. Separate Named Functions (Recommended for non-trivial logic): Define functions directly inside your component. This keeps your JSX cleaner and makes the logic reusable if needed.

    import React, { useState } from 'react'; function Clicker() { const [message, setMessage] = useState("Waiting..."); // Define the handler function inside the component const handleClick = () => { setMessage("Button was clicked!"); }; return ( <div> <p>{message}</p> {/* Pass a reference to the function */} <button onClick={handleClick}>Click Me</button> </div> ); } export default Clicker;
  2. Inline Arrow Functions (Suitable for very simple logic or passing arguments): Define the function directly within the JSX attribute.

    import React, { useState } from 'react'; function InlineHandlerExample() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> {/* Inline arrow function calls setCount directly */} <button onClick={() => setCount(count + 1)}>Increment</button> {/* Inline function calling another function (less common unless passing args) */} <button onClick={() => alert('Resetting!')}>Show Alert</button> </div> ); } export default InlineHandlerExample;
⚠️
Warning

While concise, defining arrow functions directly inline (onClick={() => doSomething()}) can potentially cause performance issues in complex scenarios because a new function instance is created on every render. For simple cases, it is often fine, but for handlers passed down to child components or within lists, defining them as separate functions is generally preferred.


The SyntheticEvent Object

When an event occurs, React passes an instance of SyntheticEvent to your handler function. This is a cross-browser wrapper around the browser’s native event object. It provides a consistent API, ensuring your code works similarly across different browsers.

The SyntheticEvent object has many of the same properties and methods as the native event object, including:

  • event.target: The DOM element that triggered the event (e.g., the button that was clicked, the input that was typed into).
  • event.target.value: Often used with inputs (onChange) to get the current value.
  • event.preventDefault(): Prevents the browser’s default action for the event.
  • event.stopPropagation(): Stops the event from bubbling up the DOM tree.
import { useState } from 'react'; function InputLogger() { const [inputValue, setInputValue] = useState(''); // The handler automatically receives the event object const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { // event.target points to the input element // event.target.value holds the current value of the input console.log("Input value:", event.target.value); setInputValue(event.target.value); }; // Type the event based on the element (e.g., HTMLInputElement for input changes) // React provides types like React.MouseEvent, React.ChangeEvent, React.FormEvent etc. return ( <div> <label htmlFor="myInput">Type something: </label> <input type="text" id="myInput" value={inputValue} // Controlled component (value linked to state) onChange={handleChange} /> <p>You typed: {inputValue}</p> </div> ); } export default InputLogger;

Typing Events in TypeScript

Notice the type annotation (event: React.ChangeEvent<HTMLInputElement>). React provides specific types for different synthetic events (React.MouseEvent for clicks, React.ChangeEvent for inputs/selects/textareas, React.FormEvent for form submissions, etc.). Generic types like <HTMLInputElement> specify the type of event.target. Using these types provides better autocompletion and type safety.


Preventing Default Behavior

Certain browser events have default actions associated with them. For example:

  • Clicking a submit button inside a <form> typically tries to submit the form and reload the page.
  • Clicking a link (<a> tag) navigates to the URL specified in its href attribute.

In SPAs built with React, you often want to handle these actions yourself using JavaScript without causing a full page reload or navigating away unexpectedly. You can use event.preventDefault() to stop the browser’s default action.

Example: Handling Form Submission

import { useState } from 'react'; function SimpleForm() { const [username, setUsername] = useState(''); const handleUsernameChange = (event: React.ChangeEvent<HTMLInputElement>) => { setUsername(event.target.value); }; // Handler for the form's onSubmit event const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { // Prevent the default form submission (which causes a page reload) event.preventDefault(); // Now you can handle the submission logic in JavaScript alert(`Submitting username: ${username}`); // In a real app, you might send 'username' to an API here setUsername(''); // Optionally clear the input after submission }; return ( // Attach the handler to the form's onSubmit event <form onSubmit={handleSubmit}> <h2>Simple Form</h2> <div> <label htmlFor="username">Username: </label> <input type="text" id="username" value={username} onChange={handleUsernameChange} /> </div> <button type="submit">Submit</button> </form> ); } export default SimpleForm;

In this example, event.preventDefault() inside handleSubmit stops the browser from reloading the page when the submit button is clicked, allowing us to handle the form data using React state and JavaScript logic.


Passing Data Up: The Callback Pattern

Remember that data flows down via props. But what if a child component needs to communicate something back up to its parent, often triggered by an event within the child?

The standard way to achieve this is by passing a function from the parent down to the child as a prop. The child then calls this function when the event occurs, potentially passing data as arguments to the function. This function passed down is often called a “callback function”.

Scenario: A parent component displays a name, and a child component contains a button to change that name.

src/components/ChildButton.tsx
interface ChildButtonProps { // Prop to receive the function from the parent onNameChange: (newName: string) => void; // Expects a function taking a string currentName: string; // Just to show context } function ChildButton({ onNameChange, currentName }: ChildButtonProps) { const handleClick = () => { // When the button is clicked, call the function passed down via props const newName = currentName === "Alice" ? "Bob" : "Alice"; onNameChange(newName); // Pass the new name back up to the parent }; return ( <button onClick={handleClick}> Change Name (Current: {currentName}) </button> ); } export default ChildButton;
src/components/ParentComponent.tsx
import { useState } from 'react'; import ChildButton from './ChildButton'; function ParentComponent() { const [name, setName] = useState("Alice"); // This function will be passed down to the child // It knows how to update the parent's state const handleNameChangeFromChild = (updatedName: string) => { setName(updatedName); }; return ( <div style={{ border: '1px solid blue', padding: '15px' }}> <h2>Parent Component</h2> <p>Current Name in Parent: {name}</p> <hr /> {/* Pass the handler function down as a prop named 'onNameChange' */} <ChildButton onNameChange={handleNameChangeFromChild} currentName={name} /> </div> ); } export default ParentComponent;

Explanation:

  1. Parent Defines Handler: ParentComponent defines handleNameChangeFromChild, which knows how to update the parent’s name state using setName.
  2. Parent Passes Handler: ParentComponent passes handleNameChangeFromChild as a prop named onNameChange to ChildButton.
  3. Child Defines Prop Type: ChildButtonProps declares that it expects an onNameChange prop which is a function accepting a string and returning void.
  4. Child Calls Handler: When the button inside ChildButton is clicked, its handleClick function calls the onNameChange prop, passing the desired newName as an argument.
  5. Parent State Updates: This executes handleNameChangeFromChild back in the ParentComponent, which updates the parent’s state, causing a re-render of both parent and child with the new name.

This callback pattern is fundamental for communication between components in React.


Activity 1: Input Change Handler & Callback

  1. Create a child component src/components/ColorInput.tsx.
    • It should render a text input field (<input type="text">).
    • It should accept a prop onColorChange which is a function (color: string) => void.
    • When the input value changes (onChange), it should call the onColorChange prop with the input’s current value (event.target.value).
  2. Create a parent component src/components/ColorPicker.tsx.
    • It should use useState to manage a color state variable (initially, say, “white”).
    • It should define a handler function handleColorChange(newColor: string) that updates the color state.
    • It should render the ColorInput component, passing handleColorChange as the onColorChange prop.
    • It should also display the current color state visually, perhaps by setting the background color of a div.

Color Input and Picker Example

Here’s an example of these completed components, demonstrating a parent-child component relationship with state management:

Color Picker

Current color: white

This activity demonstrates:

  • Parent-child component communication through props
  • State management using the useState hook
  • Event handling and callback functions
  • Dynamic styling based on state

Lab 2 Introduction: State & Events

The second graded lab will build directly upon the concepts of state (useState) from the previous lesson and event handling from this one.

Scenario: You are tasked with building a small interactive feature for a web application, such as a simple counter with customizable steps, a basic character-count tracker for a text area, or a color previewer.

Task Overview: You will need to:

  • Create components that manage their own state using useState.
  • Attach event handlers (onClick, onChange, etc.) to elements within your components.
  • Update the component’s state in response to user events.
  • Potentially use the callback pattern if the lab involves multiple interacting components.
  • Ensure the UI correctly reflects the current state.
  • Commit your work frequently using Git best practices.

Knowledge Check

How do you attach a click event handler function named 'handleClick' to a button in JSX?

  • Select an answer to view feedback.

What is event.preventDefault() typically used for in a form's onSubmit handler?

  • Select an answer to view feedback.

What is React's SyntheticEvent?

  • Select an answer to view feedback.

How can a child component send data back up to its parent component when an event occurs?

  • Select an answer to view feedback.

Summary

In this lesson, you learned how to make React components interactive by handling user events. We covered the camelCase syntax (onClick, onChange) for attaching event listeners in JSX and how to provide handler functions (either inline or separately defined). You were introduced to the SyntheticEvent object and the important event.preventDefault() method for controlling default browser actions, especially in forms. Crucially, you learned the callback pattern, where parent components pass functions down as props, allowing child components to communicate information back up the tree upon user interaction. These skills are vital for building dynamic and responsive applications.


References

Additional Resources