NeuroAgent

Optimizing Duplicate Event Handlers in React

Learn effective methods for reducing code duplication in React event handlers. Detailed implementation examples with useCallback and unified state structure.

Question

How can I optimize and reduce duplicate event handlers in React?

During the development of a React component, I’ve encountered a need for multiple event handlers with similar structure. The code below demonstrates two handlers that are practically identical in logic but work with different states:

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 that works with different states, or are there other approaches to reducing this kind of code duplication? Which method would be most efficient in terms of performance and long-term code maintainability?

NeuroAgent

Yes, you can create a universal handler that works with different states using several approaches. The most effective methods are using a single state object with a dynamic updating handler, or applying a parameterized handler with useCallback for performance optimization.

Contents

Creating a Universal Event Handler

The most common approach to eliminate code duplication is creating a universal handler that can work with any state. Here’s how you can implement this in your case:

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

const handleGenericChange = (setState) => (event) => {
  setState(event.target.value);
};

// Usage
<select value={selectedValue} onChange={handleGenericChange(setSelectedValue)}>
  <option value={1}>Option 1</option>
  <option value={2}>Option 2</option>
</select>

<select value={selectedColorValue} onChange={handleGenericChange(setSelectedColorValue)}>
  <option value="black">Black</option>
  <option value="white">White</option>
</select>

This approach allows you to use the same pattern for any state by passing the required setState function as an argument.


Using a Single State Object

A more modern and often recommended approach is to combine related states into a single object. This allows you to use a single handler for all fields:

javascript
const [formData, setFormData] = React.useState({
  selectedValue: 1,
  selectedColorValue: "black"
});

const handleFormChange = (event) => {
  const { name, value } = event.target;
  setFormData(prevState => ({
    ...prevState,
    [name]: value
  }));
};

// In JSX, you need to specify name for each element
<select 
  name="selectedValue"
  value={formData.selectedValue} 
  onChange={handleFormChange}
>
  <option value={1}>Option 1</option>
  <option value={2}>Option 2</option>
</select>

<select 
  name="selectedColorValue"
  value={formData.selectedColorValue} 
  onChange={handleFormChange}
>
  <option value="black">Black</option>
  <option value="white">White</option>
</select>

This approach is preferred because:

  • Reduces the number of state variables
  • Ensures atomic updates of multiple related states
  • Simplifies data passing between components
  • Easier to extend with new fields

Parameterized Handlers

Another approach is using a handler with parameters that can be partially applied:

javascript
const handleStateChange = (setState) => (event) => {
  setState(event.target.value);
};

// Or a more flexible version
const handleChange = (key, setState) => (event) => {
  setState(prevState => ({
    ...prevState,
    [key]: event.target.value
  }));
};

// Usage
<select 
  value={selectedValue} 
  onChange={handleChange('selectedValue', setSelectedValue)}
>

This approach is particularly useful when you have many similar states that aren’t logically connected in a single object.


Optimization with useCallback

When using universal handlers, it’s important to consider performance. Each time a component renders, new functions are created, which can lead to unnecessary re-renders of child components.

Use useCallback to memoize handlers:

javascript
import React, { useState, useCallback } from 'react';

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

const handleGenericChange = useCallback((setState) => (event) => {
  setState(event.target.value);
}, []); // Empty array means the function won't be recreated

// For the single form data handler
const handleFormChange = useCallback((event) => {
  const { name, value } = event.target;
  setFormData(prevState => ({
    ...prevState,
    [name]: value
  }));
}, []); // Dependencies aren't needed here since we're using a callback function

Important: As noted in performance research, useCallback with an empty dependency array can cause the handler to never update. Make sure this is safe for your specific use case.


Performance and Maintainability Comparison

Performance

Approach Performance Recommendation
Individual handlers Most efficient for isolated states Good for small components
Universal handler Requires optimization with useCallback Moderate performance
Single state object Best balance of performance and maintainability Recommended for forms and related data

Maintainability

  1. Single state object - most maintainable approach because:

    • Easy to add new fields
    • Convenient validation of all data
    • Simple submission to server
    • Easier to implement change rollback
  2. Universal handlers - good for components with the same structure but different states

  3. Individual handlers - acceptable only for very simple cases with 1-2 states


Practical Implementation Examples

Example 1: Form with multiple fields

javascript
import React, { useState, useCallback } from 'react';

function UserForm() {
  const [user, setUser] = useState({
    name: '',
    email: '',
    age: '',
    city: ''
  });

  const handleChange = useCallback((event) => {
    const { name, value } = event.target;
    setUser(prevUser => ({
      ...prevUser,
      [name]: value
    }));
  }, []);

  const handleSubmit = useCallback((event) => {
    event.preventDefault();
    console.log('Submitting data:', user);
  }, [user]);

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        name="name"
        value={user.name}
        onChange={handleChange}
        placeholder="Name"
      />
      <input
        type="email"
        name="email"
        value={user.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <input
        type="number"
        name="age"
        value={user.age}
        onChange={handleChange}
        placeholder="Age"
      />
      <button type="submit">Submit</button>
    </form>
  );
}

Example 2: Dynamic list with universal handlers

javascript
import React, { useState, useCallback } from 'react';

function DynamicList() {
  const [items, setItems] = useState([
    { id: 1, name: 'Item 1', value: '' },
    { id: 2, name: 'Item 2', value: '' }
  ]);

  const handleItemChange = useCallback((id) => (event) => {
    setItems(prevItems => 
      prevItems.map(item =>
        item.id === id 
          ? { ...item, value: event.target.value }
          : item
      )
    );
  }, []);

  return (
    <div>
      {items.map(item => (
        <div key={item.id}>
          <label>{item.name}</label>
          <input
            type="text"
            value={item.value}
            onChange={handleItemChange(item.id)}
          />
        </div>
      ))}
    </div>
  );
}

Sources

  1. React interactivity: Events and state - MDN
  2. How to use same state for multiple controlled components in react hooks - DEV Community
  3. How to properly optimize event handlers in react - Stack Overflow
  4. Performance penalty of creating handlers on every render with react-hooks - Stack Overflow
  5. Optimizing React component event handlers - Nicholas Tsim
  6. React useState hook event handler using initial state - Stack Overflow
  7. useState – React

Conclusion

To optimize and reduce duplicate event handlers in React, the following approaches are recommended:

  1. Single state object - the most preferred method for forms and related data, providing better performance and maintainability
  2. Universal handlers with useCallback - effective for components with the same structure but require performance optimization
  3. Parameterized handlers - flexible approach for dynamic lists and repetitive components

The choice of a specific method depends on your application context, but in most cases, combining related states into a single object with a single handler is most efficient in terms of both performance and long-term code maintainability.