How to Work With Event Listeners in React

How to Work With Event Listeners in React

Learn how to respond to user events
Ferenc Almasi β€’ Last updated 2023 April 04 β€’ Read time 11 min read
Learn how to work with various event listeners in React to handle and respond to user events.
  • twitter
  • facebook
React

This lesson is a preview from our interactive course

In this lesson, we are going to dive deeper into how event listeners and handlers work in React, and which events are the most commonly used. Each event listener in React starts with the "on" prefix. For example, onClickonChange, or onKeyUp.


onClick

As a quick recap, here is an example of how we could handle the most common event: click events.

Copied to clipboard! Playground
import React, { useState } from 'react'

const App = () => {
    const [count, setCount] = useState(0);

    return (
        <React.Fragment>
            <h1>The current count is: {count}</h1>
            <button onClick={() => setCount(count + 1)}>
                Increase
            </button>
        </React.Fragment>
    );
}

export default App;
Handling click events Execute code

Note the difference between event listeners and handlers. onClick is the event listener, while the function passed to it is the event handler.

onClick event listeners can be attached to pretty much any element where a click can occur. However, keep in mind that for semantics, in most cases you want to use interactive elements, such as a button or an anchor.


onChange

Another commonly used event in React is the onChange event. onChange events are mostly used with inputs where the event handler will be triggered on each change event. Take the following example:

Copied to clipboard! Playground
import React, { useState } from 'react'

const App = () => {
    const [value, setValue] = useState('');

    return (
        <React.Fragment>
            <h1>Input value: "{value}"</h1>
            <input onChange={event => setValue(event.target.value)} />
        </React.Fragment>
    );
}

export default App;
Handling change events Execute code

We have access to the event itself inside the event handler. We can use event.target.value to grab the current value of the input and output it into the h1. Each time the input changes, the event handler gets called. Try to recreate the above example using a select element.

Copied to clipboard! Playground
import React, { useState } from 'react'

const App = () => {
    const [value, setValue] = useState('react');

    return (
        <React.Fragment>
            <h1>Input value: "{value}"</h1>
            <select
                onChange={event => setValue(event.target.value)}
                defaultValue={value}
            >
                <option value="react">React</option>
                <option value="js">JavaScript</option>
            </select>
        </React.Fragment>
    );
}

export default App;
Handling change events for select elements

Instead of using the selected attribute on one of the options, we need to use the defaultValue prop on the select element for default values.

Notice that we need to change the initial value of the useState hook to reflect the default selected option. Otherwise, the default value only gets applied if we change to another option and change back to the default value. The same onChange event listener is used for checkboxes:

Copied to clipboard! Playground
import React, { useState } from 'react'

const App = () => {
    const [value, setValue] = useState(false);

    return (
        <React.Fragment>
            <h1>Input checked: "{JSON.stringify(value)}"</h1>
            <input
                type="checkbox"
                onChange={event => setValue(event.target.checked)}
            />
        </React.Fragment>
    );
}

export default App;
Using onChange for checkboxes Execute code

In such cases, we need to look for the event.target.checked value, which tells us whether the checkbox is checked or not. As a result, the value state needs to be a boolean.

But what if we want this to be checked by default? In this case, we also need to pass the value state to the checked property on the input:

Copied to clipboard! Playground
import React, { useState } from 'react'

export const App = () => {
    const [value, setValue] = useState(true);

    return (
        <React.Fragment>
            <h1>Input checked: "{JSON.stringify(value)}"</h1>
            <input
                type="checkbox"
                onChange={event => setValue(event.target.checked)}
                checked={value}
            />
        </React.Fragment>
    );
}

export default App;
Extend the input with a checked property
Looking to improve your skills? Check out our interactive course to master React from start to finish.
Master Reactinfo Remove ads

Passing Event Handlers

So far we only looked at inline event handlers. The most common way to use these functions in React, however, is to define them above the return statement, and pass them to one of the event listeners. Taking our onClick as an example, it would translate to the following:

Copied to clipboard! Playground
import React, { useState } from 'react'

const App = () => {
    const [count, setCount] = useState(0);
    
    // The event object is also available inside the function
    const updateCount = event => setCount(count + 1);

    return (
        <React.Fragment>
            <h1>The current count is: {count}</h1>
            <button onClick={updateCount}>
                Increase
            </button>
        </React.Fragment>
    );
}

export default App;
Outsourcing event handlers Execute code

Notice that the function is only passed to the onClick event listener, but not called. Calling the function inside the event listener would call the function as soon as the component is rendered.

Copied to clipboard! Playground
// ❌ Don't call the function during render
<button onClick={updateCount()}>Increase</button>

// βœ”οΈ Do call it with an inline handler
<button onClick={event => updateCount(event, param)}>Increase</button>

// βœ… Prefer passing the function
<button onClick={updateCount}>Increase</button>
The correct way to pass event handlers

Inline event handlers can also be used when we need to pass extra parameters to the function. Note that, for the third example, the event object is automatically passed, so we don't need to pass it explicitly.

Event handlers as props

Event handlers can also be passed as props. This is useful in case we want to handle events for a component outside of its scope. For example, we might have a generic Button component that needs to handle different events for different buttons. We can achieve this in the following way:

Copied to clipboard! Playground
import React, { useState } from 'react'

const Button = ({ onClick, children }) => {
    return <button onClick={onClick}>{children}</button>;
}

const App = () => {
    const [value, setValue] = useState('');

    return (
        <React.Fragment>
            <h1>Button clicked: {value}</h1>
            <Button onClick={() => setValue('first')}>First</Button>
            <Button onClick={() => setValue('last')}>Last</Button>
        </React.Fragment>
    );
}

export default App;
Passing event handlers as props Execute code

Notice that anything passed between the component will be accessible with a variable called children.


Event Propagation

There is one more thing we need to cover in this lesson. How to prevent event propagation and default browser actions.

Event propagation happens when there are multiple event listeners attached to one of the elements and its descendants. When the event is triggered, it starts to bubble up the DOM, starting from the innermost element. Take the following as an example:

Copied to clipboard! Playground
import React from 'react'

const App = () => {
    const divClick = () => console.log('clicked on div');
    const buttonClick = () => console.log('clicked on button');

    return (
        <div onClick={divClick}>
            <button onClick={buttonClick}>
                Trigger
            </button>
        </div>
    );
}

export default App;
Both events are captured Execute code

We have two different onClick event listeners attached to a div and a button inside of it. Execute the code and click on the button to verify that both logs are logged to the console.

This behavior could be unwanted in certain cases. To prevent it, we need to stop event propagation using the stopPropagation method on the event object:

Copied to clipboard! Playground
import React from 'react'

const App = () => {
    const divClick = () => console.log('clicked on div');
    const buttonClick = e => {
        e.stopPropagation();
        console.log('clicked on button');
    }

    return (
        <div onClick={divClick}>
            <button onClick={buttonClick}>
                Trigger
            </button>
        </div>
    );
}

export default App;
Use event.stopPropagation to stop event propagation Execute code

The event object is often abbreviated to e.

Execute the code again, and click on the button. This time, only the onClick attached to the button will be triggered.


Preventing Default Actions

Event propagation is not the only thing that can alter the behavior of our event listeners. Some events also come with default browser actions. A classic example is the onSubmit event for a form.

By default, browsers refresh the page whenever the submit event is triggered. For single-page applications, this usually doesn't make much sense as we want to handle user events on the client for a better experience. Take the following as an example:

Copied to clipboard! Playground
import React from 'react'

const App = () => {
    const submit = () => {
        console.log('Submitting form...');
    }

    return (
        <form onSubmit={submit}>
            <button>Submit</button>
        </form>
    );
}

export default App;
Submitting form without prevent default browser action Execute code

In this example, the page gets reloaded when the form is submitted. To prevent this, try to modify the submit function by adding e.preventDefault above the console.log.

Copied to clipboard!
const submit = event => {
    event.preventDefault();
    console.log('Submitting form...');
}
Adding e.preventDefault will prevent default browser actions

Don't forget to also pass the event as the parameter to the function. You can also use e.preventDefault and e.stopPropagation together when both actions need to be suppressed.

Now that we have a good understanding of how we can respond to user events in React, there is only one more thing we need to cover before the next section. That is how to achieve routing.

  • twitter
  • facebook
React
Did you find this page helpful?
πŸ“š More Webtips
Frontend Course Dashboard
Master the Art of Frontend
  • check Access 100+ interactive lessons
  • check Unlimited access to hundreds of tutorials
  • check Prepare for technical interviews
Become a Pro

Courses

Recommended

This site uses cookies We use cookies to understand visitors and create a better experience for you. By clicking on "Accept", you accept its use. To find out more, please see our privacy policy.