How to Handle Events in React JS

Recently, I was working on a React project that required creating a highly interactive feedback form for a US-based healthcare application. The challenge was handling various user events efficiently, clicks, keyboard inputs, form submissions, and more.

The issue is that many developers struggle with implementing event handlers properly in React, which can lead to performance issues or unexpected behavior in their applications.

In this article, I’ll cover several methods to handle events in React JS based on my decade of experience.

Events in React JS

Events in React are actions or occurrences that happen in the browser, such as mouse clicks, keyboard inputs, or form submissions. React events are similar to regular DOM events, but with some key differences.

React uses a synthetic event system that provides cross-browser compatibility while maintaining the standard browser event interface you’re already familiar with.

Basic Event Handling in React

React’s event handling syntax is similar to HTML but with some important differences:

  1. React events use camelCase naming (onClick instead of onclick)
  2. Event handlers are passed as JavaScript functions rather than strings
  3. You must explicitly prevent default behavior

Here’s a simple example of how to handle a click event:

function ClickButton() {
  const handleClick = () => {
    alert('Button was clicked!');
  };

  return (
    <button onClick={handleClick}>
      Click Me
    </button>
  );
}

Method 1 – Handle Form Events

Forms are a core part of most web applications. In React, you typically want to control form elements and respond to user inputs.

Here are the steps to handle form events in React:

  1. Create state variables for form inputs
  2. Add event handlers for changes
  3. Create a submission handler
import React, { useState } from 'react';

function VoterRegistrationForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    state: 'California'
  });

  const handleChange = (e) => {
    setFormData({
      ...formData,
      [e.target.name]: e.target.value
    });
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted:', formData);
    // Here you would typically send data to an API
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Name:</label>
        <input 
          type="text" 
          name="name" 
          value={formData.name} 
          onChange={handleChange} 
        />
      </div>
      <div>
        <label>Email:</label>
        <input 
          type="email" 
          name="email" 
          value={formData.email} 
          onChange={handleChange} 
        />
      </div>
      <div>
        <label>State:</label>
        <select 
          name="state" 
          value={formData.state} 
          onChange={handleChange}
        >
          <option value="California">California</option>
          <option value="New York">New York</option>
          <option value="Texas">Texas</option>
        </select>
      </div>
      <button type="submit">Register</button>
    </form>
  );
}

You can see the output in the screenshot below.

Handle Events in React JS

The handleChange function efficiently updates the state by using the input’s name attribute as the key. This makes the form handler reusable across multiple inputs.

Method 2 – Handle Events with Arguments

Sometimes you need to pass additional data to your event handlers. There are two common ways to do this:

Use Arrow Functions

function ProductList() {
  const products = [
    { id: 1, name: 'MacBook Pro', price: 1999 },
    { id: 2, name: 'iPhone 13', price: 999 },
    { id: 3, name: 'AirPods Pro', price: 249 }
  ];

  const handleAddToCart = (product) => {
    console.log(`Added ${product.name} to cart`);
    // Add to cart logic
  };

  return (
    <div>
      <h2>Popular Products</h2>
      <ul>
        {products.map(product => (
          <li key={product.id}>
            {product.name} - ${product.price}
            <button onClick={() => handleAddToCart(product)}>
              Add to Cart
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

Use bind()

class ProductList extends React.Component {
  constructor(props) {
    super(props);
    this.handleAddToCart = this.handleAddToCart.bind(this);
  }

  handleAddToCart(product) {
    console.log(`Added ${product.name} to cart`);
    // Add to cart logic
  }

  render() {
    const products = [
      { id: 1, name: 'MacBook Pro', price: 1999 },
      { id: 2, name: 'iPhone 13', price: 999 },
      { id: 3, name: 'AirPods Pro', price: 249 }
    ];

    return (
      <div>
        <h2>Popular Products</h2>
        <ul>
          {products.map(product => (
            <li key={product.id}>
              {product.name} - ${product.price}
              <button onClick={this.handleAddToCart.bind(this, product)}>
                Add to Cart
              </button>
            </li>
          ))}
        </ul>
      </div>
    );
  }
}

I generally prefer the arrow function approach in functional components as it’s more readable and aligns with modern React practices.

Method 3 – Handle Keyboard Events

Keyboard events are crucial for enhancing user experience, especially in forms and interactive applications.

function SearchComponent() {
  const [query, setQuery] = useState('');

  const handleKeyPress = (e) => {
    if (e.key === 'Enter') {
      performSearch();
    }
  };

  const handleChange = (e) => {
    setQuery(e.target.value);
  };

  const performSearch = () => {
    console.log(`Searching for: ${query}`);
    // Search API call
  };

  return (
    <div>
      <input
        type="text"
        placeholder="Search US stocks..."
        value={query}
        onChange={handleChange}
        onKeyPress={handleKeyPress}
      />
      <button onClick={performSearch}>Search</button>
    </div>
  );
}

You can see the output in the screenshot below.

How to Handle Events in React JS

This component handles both onChange events to update the query state and onKeyPress events to trigger the search when the user presses Enter.

Method 4 – Create Custom Event Handlers

For complex applications, you might want to create custom event handlers that combine multiple operations:

function WeatherApp() {
  const [location, setLocation] = useState('New York');
  const [temperature, setTemperature] = useState(null);
  const [loading, setLoading] = useState(false);

  const handleLocationChange = (e) => {
    setLocation(e.target.value);
  };

  const handleWeatherRequest = async () => {
    // Combined handler that manages UI state and API calls
    setLoading(true);
    try {
      // Simulate API call
      const response = await fetch(`https://api.example.com/weather?location=${location}`);
      const data = await response.json();
      setTemperature(data.temperature);
    } catch (error) {
      console.error('Failed to fetch weather data', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <h2>US Weather Checker</h2>
      <input
        type="text"
        value={location}
        onChange={handleLocationChange}
        placeholder="Enter US city"
      />
      <button onClick={handleWeatherRequest} disabled={loading}>
        {loading ? 'Loading...' : 'Check Weather'}
      </button>
      {temperature && (
        <p>Current temperature in {location}: {temperature}°F</p>
      )}
    </div>
  );
}

You can see the output in the screenshot below.

Custom Event Handle in React JS

Method 5 – Use Event Delegation in React

Event delegation is a technique where you attach a single event listener to a parent element rather than attaching it to all child elements. React’s event system already uses a form of event delegation internally, but you can also leverage this pattern in your components.

function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Schedule meeting with marketing team', completed: false },
    { id: 2, text: 'Prepare quarterly sales report', completed: true },
    { id: 3, text: 'Update company website', completed: false }
  ]);

  const handleListClick = (e) => {
    // Check if the clicked element is a list item
    if (e.target.tagName === 'LI') {
      const todoId = Number(e.target.dataset.id);
      toggleTodo(todoId);
    }
  };

  const toggleTodo = (id) => {
    setTodos(todos.map(todo => 
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };

  return (
    <div>
      <h2>Task Manager</h2>
      <ul onClick={handleListClick}>
        {todos.map(todo => (
          <li 
            key={todo.id} 
            data-id={todo.id}
            style={{ 
              textDecoration: todo.completed ? 'line-through' : 'none',
              cursor: 'pointer'
            }}
          >
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  );
}

This approach is particularly useful for long lists or when elements are dynamically added or removed.

Best Practices for Event Handling in React

Based on my experience, here are some important best practices to follow:

  1. Avoid Inline Function Definitions for performance-critical components as they create new function instances on each render.
  2. Use Event Pooling Carefully – React reuses event objects for performance. If you need to access event properties asynchronously, use e.persist().
  3. Debounce Event Handlers for events that fire rapidly, like scrolling or resizing:
import { debounce } from 'lodash';

function SearchInput() {
  const [query, setQuery] = useState('');

  // Debounced search function
  const debouncedSearch = useCallback(
    debounce((searchTerm) => {
      console.log('Searching for:', searchTerm);
      // API call here
    }, 500),
    []
  );

  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value);
    debouncedSearch(value);
  };

  return (
    <input
      type="text"
      value={query}
      onChange={handleChange}
      placeholder="Search products..."
    />
  );
}
  1. Clean Up Event Listeners that are attached outside React’s synthetic event system:
function WindowSizeTracker() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };

    window.addEventListener('resize', handleResize);
    
    // Clean up function
    return () => {
      window.removeEventListener('resize', handleResize);
    };
    }, []);

  return (
    <div>
      <h2>Current Window Size</h2>
      <p>Width: {windowSize.width}px</p>
      <p>Height: {windowSize.height}px</p>
    </div>
  );
}

Method 6 – Handle Events in Class Components

While functional components with hooks are now more popular, you might still encounter class components in existing projects. Here’s how to handle events in class components:

class CounterComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    
    // Binding in constructor
    this.handleIncrement = this.handleIncrement.bind(this);
  }
  
  handleIncrement() {
    this.setState({ count: this.state.count + 1 });
  }
  
  // Alternative: use class fields with arrow functions
  handleDecrement = () => {
    this.setState({ count: this.state.count - 1 });
  };
  
  render() {
    return (
      <div>
        <h2>Vote Counter</h2>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleIncrement}>+1</button>
        <button onClick={this.handleDecrement}>-1</button>
      </div>
    );
  }
}

The key difference here is that you need to bind this for your event handlers, either in the constructor or by using class field syntax with arrow functions.

Method 7 – Create Reusable Event Handlers with Custom Hooks

For more complex applications, you can create custom hooks to encapsulate event handling logic and make it reusable:

// Custom hook for form handling
function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);
  
  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    setValues({
      ...values,
      [name]: type === 'checkbox' ? checked : value
    });
  };
  
  const resetForm = () => {
    setValues(initialValues);
  };
  
  return {
    values,
    handleChange,
    resetForm
  };
}

// Using the custom hook
function UserSignupForm() {
  const { values, handleChange, resetForm } = useForm({
    firstName: '',
    lastName: '',
    email: '',
    state: 'California',
    agreeToTerms: false
  });
  
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted:', values);
    
    // After successful submission
    alert(`Thank you for signing up, ${values.firstName}!`);
    resetForm();
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>First Name:</label>
        <input 
          type="text" 
          name="firstName" 
          value={values.firstName} 
          onChange={handleChange} 
        />
      </div>
      <div>
        <label>Last Name:</label>
        <input 
          type="text" 
          name="lastName" 
          value={values.lastName} 
          onChange={handleChange} 
        />
      </div>
      <div>
        <label>Email:</label>
        <input 
          type="email" 
          name="email" 
          value={values.email} 
          onChange={handleChange} 
        />
      </div>
      <div>
        <label>State:</label>
        <select 
          name="state" 
          value={values.state} 
          onChange={handleChange}
        >
          <option value="California">California</option>
          <option value="New York">New York</option>
          <option value="Texas">Texas</option>
          <option value="Florida">Florida</option>
        </select>
      </div>
      <div>
        <label>
          <input 
            type="checkbox" 
            name="agreeToTerms" 
            checked={values.agreeToTerms} 
            onChange={handleChange} 
          />
          I agree to the terms and conditions
        </label>
      </div>
      <button type="submit" disabled={!values.agreeToTerms}>
        Sign Up
      </button>
    </form>
  );
}

This custom hook approach makes your forms more maintainable and reduces duplicate code across different components.

Handle Touch Events for Mobile Applications

For mobile-friendly React applications, you’ll want to handle touch events properly:

function SwipeableCard() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [startPosition, setStartPosition] = useState({ x: 0, y: 0 });
  const [swiping, setSwiping] = useState(false);
  
  const handleTouchStart = (e) => {
    const touch = e.touches[0];
    setStartPosition({ x: touch.clientX, y: touch.clientY });
    setSwiping(true);
  };
  
  const handleTouchMove = (e) => {
    if (!swiping) return;
    
    const touch = e.touches[0];
    const deltaX = touch.clientX - startPosition.x;
    const deltaY = touch.clientY - startPosition.y;
    
    setPosition({ x: deltaX, y: deltaY });
  };
  
  const handleTouchEnd = () => {
    setSwiping(false);
    
    // Reset position or take action based on swipe direction
    if (Math.abs(position.x) > 100) {
      // Swiped far enough for an action
      console.log(`Swiped ${position.x > 0 ? 'right' : 'left'}`);
      // You could dismiss a card, navigate, etc.
    }
    
    setPosition({ x: 0, y: 0 });
  };
  
  return (
    <div 
      style={{
        padding: 20,
        backgroundColor: '#f0f0f0',
        borderRadius: 8,
        transform: `translateX(${position.x}px)`,
        transition: swiping ? 'none' : 'transform 0.3s ease',
        userSelect: 'none'
      }}
      onTouchStart={handleTouchStart}
      onTouchMove={handleTouchMove}
      onTouchEnd={handleTouchEnd}
    >
      <h3>US National Parks Tour Package</h3>
      <p>Swipe left to dismiss or right to bookmark</p>
    </div>
  );
}

This example creates a card element that users can swipe left or right, similar to many popular mobile apps.

Handle Events with External Libraries

Sometimes you might need to integrate third-party libraries that have their own event systems. Here’s an example using a chart library:

import React, { useRef, useEffect } from 'react';
import Chart from 'chart.js/auto';

function StockPriceChart() {
  const chartRef = useRef(null);
  const chartInstance = useRef(null);
  
  useEffect(() => {
    if (chartInstance.current) {
      chartInstance.current.destroy();
    }
    
    const ctx = chartRef.current.getContext('2d');
    
    chartInstance.current = new Chart(ctx, {
      type: 'line',
      data: {
        labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
        datasets: [{
          label: 'Apple Stock Price 2023',
          data: [145, 152, 165, 168, 172, 180],
          borderColor: 'rgba(75, 192, 192, 1)',
        }]
      },
      options: {
        onClick: (event, elements) => {
          if (elements.length > 0) {
            const index = elements[0].index;
            console.log(`Clicked on ${['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'][index]}`);
            // You could show more detailed information for that month
          }
        }
      }
    });
    
    return () => {
      if (chartInstance.current) {
        chartInstance.current.destroy();
      }
    };
  }, []);
  
  return (
    <div>
      <h2>Stock Performance</h2>
      <canvas ref={chartRef} width="400" height="200"></canvas>
      <p>Click on a point for more details</p>
    </div>
  );
}

In this example, we’re handling the click event through Chart.js’s event system rather than React’s, but integrating it into our React component lifecycle.

Read Form Validation in React.js

Wrapping Up

Event handling is at the core of creating interactive React applications. By mastering these techniques, you’ll be able to build responsive and user-friendly interfaces that provide a great experience for your users.

Remember these key points:

  • Use camelCase for event names (onClick, onChange)
  • Pass functions, not strings, to event handlers
  • Bind methods in class components or use arrow functions
  • Create custom hooks for reusable event handling logic
  • Clean up any manually added event listeners when components unmount

Whether you’re building a simple form or a complex interactive dashboard, these event handling patterns will help you create clean, maintainable React code that delivers a smooth user experience.

Have you implemented any of these event handling patterns in your React projects? I’d love to hear about your experiences or any questions you might have in the comments.

Related tutorials:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.