NeuroAgent

Optimizing Event Handlers in React: A Complete Guide

Learn how to optimize event handlers in React, reduce code duplication, and improve performance. Create universal handlers and use useCallback for effective event management.

Question

How can I shorten or optimize event handlers in React?

I’m developing a component with multiple event handlers that have similar structure. Here’s an example of the code:

javascript
const [selectedValue, setSelectedValue] = React.useState(1);
const [selectedColorValue, setSelectedColorValue] = React.useState("black");

const handleSelectChange = (event) => {
  setSelectedValue(event.target.value);
};
const handleSelectColorChange = (event) => {
  setSelectedColorValue(event.target.value);
};

Is it possible to create a universal handler or use other approaches to reduce code duplication? Should I optimize such code early in development or leave it as is until refactoring becomes necessary?

NeuroAgent

Main approach to reducing code duplication in React event handlers

The main approach to reducing code duplication in React event handlers involves creating a universal handler using useCallback for performance optimization. You can create a single handler that accepts a parameter to determine which state to update, which significantly reduces the amount of duplicated code and improves component performance.

Table of Contents

Optimizing Event Handlers in React

Event handlers are one of the main sources of performance issues in React applications. When a component re-renders, handler functions are recreated even if their logic hasn’t changed. This leads to unnecessary re-renders of child components, especially when using optimized components like React.memo or PureComponent source.

Main performance issues:

  • Creating new function instances on every render
  • Passing new handler references to child components
  • Needing to use complex techniques to prevent memory leaks

There are several approaches to optimization, from simple handler combination to advanced patterns with hooks.

Creating a Universal Handler

In your case with two similar handlers, you can create a universal handler that accepts a parameter to determine which state to update. This will not only reduce code but also make it more scalable.

javascript
const [selectedValue, setSelectedValue] = React.useState(1);
const [selectedColorValue, setSelectedColorValue] = React.useState("black");

const handleUniversalChange = React.useCallback((setter, event) => {
  setter(event.target.value);
}, []);

// Usage in components
<select onChange={(e) => handleUniversalChange(setSelectedValue, e)}>
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
</select>

<select onChange={(e) => handleUniversalChange(setSelectedColorValue, e)}>
  <option value="black">Black</option>
  <option value="white">White</option>
</select>

This approach allows you to:

  • Reduce the amount of duplicated code
  • Provide a single pattern for all change handlers
  • Easily add new fields in the future

An alternative is to create an object of handlers:

javascript
const handlers = {
  selectedValue: setSelectedValue,
  selectedColorValue: setSelectedColorValue
};

const handleChange = React.useCallback((key) => (event) => {
  handlers[key](event.target.value);
}, []);

Using useCallback for Performance

useCallback is a React hook that memoizes functions, preserving them between renders. This is critical for optimizing performance when passing handlers to child components source.

javascript
const [selectedValue, setSelectedValue] = React.useState(1);
const [selectedColorValue, setSelectedColorValue] = React.useState("black");

const handleSelectChange = React.useCallback((event) => {
  setSelectedValue(event.target.value);
}, []);

const handleSelectColorChange = React.useCallback((event) => {
  setSelectedColorValue(event.target.value);
}, []);

// Or for the universal handler
const handleUniversalChange = React.useCallback((setter) => (event) => {
  setter(event.target.value);
}, []);

Why useCallback is important:

  • Prevents unnecessary re-renders of child components
  • Maintains a stable function reference between renders
  • Works better with React.memo and PureComponent source

When working with large lists of elements, useCallback becomes especially important:

javascript
const handleItemClick = React.useCallback((itemId) => {
  setSelectedItems(prev => [...prev, itemId]);
}, []);

Other Optimization Methods

1. Debouncing for Input Fields

For input fields where users type quickly, you can use debouncing to optimize performance source:

javascript
const debounce = (func, delay) => {
  let debounceTimer;
  return function() {
    const context = this;
    const args = arguments;
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(() => func.apply(context, args), delay);
  }
};

const handleInputChange = debounce((event) => {
  setSearchTerm(event.target.value);
}, 300);

2. Handling Multiple Events

If elements have similar handlers, you can use a single handler for multiple events source:

javascript
const handleEvent = React.useCallback((eventType) => (event) => {
  switch(eventType) {
    case 'click':
      handleClick(event);
      break;
    case 'change':
      handleChange(event);
      break;
    // other cases
  }
}, []);

3. Creating Custom Hooks

For complex scenarios, you can create custom hooks that encapsulate handler logic source:

javascript
function useInput(initialValue) {
  const [value, setValue] = useState(initialValue);
  
  return {
    value,
    onChange: useCallback((event) => setValue(event.target.value), []),
    reset: useCallback(() => setValue(initialValue), [initialValue])
  };
}

// Usage
const { value: selectedValue, onChange: handleSelectChange } = useInput(1);
const { value: selectedColorValue, onChange: handleColorChange } = useInput("black");

When to Start Optimizing

Early Development Stages

Optimize early when:

  • The component has multiple similar handlers
  • You plan to use React.memo or PureComponent
  • The component will pass handlers to child elements
  • Code has already started duplicating in multiple places

Early optimization allows you to:

  • Create consistent patterns for the team
  • Avoid future refactoring
  • Improve performance from the start

Later Development Stages

You can postpone optimization if:

  • The component is simple and has no performance issues
  • Code isn’t duplicated and is easy to read
  • The team is focused on functionality rather than optimization
  • The application is in development and architecture may change

Practical Examples and Patterns

Example 1: Universal Handler for Forms

javascript
function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);
  
  const handleChange = useCallback((name) => (event) => {
    setValues(prev => ({
      ...prev,
      [name]: event.target.value
    }));
  }, []);
  
  const resetForm = useCallback(() => {
    setValues(initialValues);
  }, [initialValues]);
  
  return {
    values,
    handleChange,
    resetForm
  };
}

// Usage
const { values, handleChange, resetForm } = useForm({
  selectedValue: 1,
  selectedColorValue: "black"
});

// In JSX
<input 
  name="selectedValue"
  value={values.selectedValue}
  onChange={handleChange('selectedValue')}
/>

Example 2: Optimization for Large Lists

javascript
const ItemList = React.memo(({ items, onItemClick }) => {
  return (
    <div>
      {items.map(item => (
        <div 
          key={item.id} 
          onClick={() => onItemClick(item.id)}
          style={{ cursor: 'pointer' }}
        >
          {item.name}
        </div>
      ))}
    </div>
  );
});

const ParentComponent = ({ availableItems }) => {
  const [selectedItems, setSelectedItems] = useState([]);
  
  const handleItemClick = useCallback((itemId) => {
    setSelectedItems(prev => [...prev, itemId]);
  }, []);
  
  return (
    <ItemList 
      items={availableItems} 
      onItemClick={handleItemClick} 
    />
  );
};

Example 3: Combined Approach

javascript
function useEventHandlers() {
  const [state, setState] = useState({
    selectedValue: 1,
    selectedColorValue: "black"
  });
  
  const handleChange = useCallback((field) => (event) => {
    setState(prev => ({
      ...prev,
      [field]: event.target.value
    }));
  }, []);
  
  return {
    state,
    handleChange
  };
}

// Usage
const { state, handleChange } = useEventHandlers();

// In JSX
<select 
  value={state.selectedValue}
  onChange={handleChange('selectedValue')}
>
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
</select>

<select 
  value={state.selectedColorValue}
  onChange={handleChange('selectedColorValue')}
>
  <option value="black">Black</option>
  <option value="white">White</option>
</select>

Conclusion

Optimizing event handlers in React is an important practice for creating performant and maintainable applications. Key takeaways:

  1. Universal handlers significantly reduce code duplication and create consistent patterns for form components.

  2. useCallback is an essential tool for preventing unnecessary re-renders and maintaining stable function references between renders.

  3. Custom hooks encapsulate complex logic and ensure code reusability, which is especially useful for large projects.

  4. Optimization timing depends on context: for components with multiple handlers and complex logic, optimization is needed early on; for simple components, it can be postponed until necessary.

  5. Combined approaches that combine universal handlers, useCallback, and custom hooks provide maximum performance and code cleanliness.

It’s recommended to start with simple universal handlers and gradually implement more complex patterns as the application grows and components become more complex.

Sources

  1. Optimizing React component event handlers - Nicholas Tsim
  2. Optimizing Event Handlers in React using useCallback - DEV Community
  3. JavaScript Event Listeners: How to Handle Multiple Events - The Daily Frontend
  4. Optimizing event handler of input without affecting browser and application performance in React.js - Medium
  5. React Performance: Event Handlers using useCallback hook - Medium
  6. Demystifying useCallback in React - Medium
  7. React useCallback: When and how to use it for better performance - LogRocket
  8. Advanced React useCallback patterns that actually improve performance - Medium
  9. React ‘useCallback’ for event handler - Stack Overflow
  10. When to use useCallback — Antoine Quinquenel