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:
- React events use camelCase naming (onClick instead of onclick)
- Event handlers are passed as JavaScript functions rather than strings
- 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:
- Create state variables for form inputs
- Add event handlers for changes
- 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.

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.

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.

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:
- Avoid Inline Function Definitions for performance-critical components as they create new function instances on each render.
- Use Event Pooling Carefully – React reuses event objects for performance. If you need to access event properties asynchronously, use
e.persist(). - 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..."
/>
);
}- 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:
- How to Reset Form in React JS
- How to Upload Files in React JS
- React Cancel Button: 5 Methods with Examples

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.