Skip to content

React Event Handling

Overview

React provides a powerful event handling system that allows us to respond to various user interactions. This chapter will learn how React events work, how to write event handlers, how to use event objects, and various common user interaction scenarios.

🎯 React Event System

SyntheticEvent

jsx
function EventBasics() {
  const handleClick = (event) => {
    console.log('Event type:', event.type);
    console.log('Target element:', event.target);
    console.log('Current element:', event.currentTarget);

    // Prevent default behavior
    event.preventDefault();

    // Stop event propagation
    event.stopPropagation();
  };

  return (
    <div>
      <button onClick={handleClick}>Click Me</button>
      <a href="#" onClick={handleClick}>
        Click Link (Default Behavior Prevented)
      </a>
    </div>
  );
}

Event Binding Methods

jsx
// Function component recommended approach
function ModernEventBinding() {
  const [count, setCount] = React.useState(0);

  // Recommended: Use useCallback for performance optimization
  const handleClick = React.useCallback(() => {
    setCount(prev => prev + 1);
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

// Class component event binding
class ClassEventBinding extends React.Component {
  state = { count: 0 };

  // Recommended: Arrow function property
  handleClick = () => {
    this.setState(prev => ({ count: prev.count + 1 }));
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>Increment</button>
      </div>
    );
  }
}

🖱️ Mouse Events

Basic Mouse Events

jsx
function MouseEvents() {
  const [position, setPosition] = React.useState({ x: 0, y: 0 });
  const [clicked, setClicked] = React.useState(false);

  const handleMouseMove = (event) => {
    setPosition({ x: event.clientX, y: event.clientY });
  };

  const handleClick = () => {
    setClicked(true);
    setTimeout(() => setClicked(false), 1000);
  };

  return (
    <div
      onMouseMove={handleMouseMove}
      style={{ height: '200px', border: '1px solid #ccc', padding: '20px' }}
    >
      <p>Mouse position: ({position.x}, {position.y})</p>
      <button
        onClick={handleClick}
        style={{ backgroundColor: clicked ? 'green' : 'blue', color: 'white' }}
      >
        {clicked ? 'Clicked!' : 'Click Me'}
      </button>
    </div>
  );
}

Drag and Drop

jsx
function DragAndDrop() {
  const [items, setItems] = React.useState([
    { id: 1, text: 'Drag me', x: 0, y: 0 }
  ]);
  const [dragging, setDragging] = React.useState(null);

  const handleMouseDown = (event, item) => {
    setDragging({
      id: item.id,
      offsetX: event.clientX - item.x,
      offsetY: event.clientY - item.y
    });
  };

  const handleMouseMove = (event) => {
    if (dragging) {
      setItems(items.map(item =>
        item.id === dragging.id
          ? {
              ...item,
              x: event.clientX - dragging.offsetX,
              y: event.clientY - dragging.offsetY
            }
          : item
      ));
    }
  };

  const handleMouseUp = () => {
    setDragging(null);
  };

  React.useEffect(() => {
    if (dragging) {
      document.addEventListener('mousemove', handleMouseMove);
      document.addEventListener('mouseup', handleMouseUp);
      return () => {
        document.removeEventListener('mousemove', handleMouseMove);
        document.removeEventListener('mouseup', handleMouseUp);
      };
    }
  }, [dragging]);

  return (
    <div style={{ position: 'relative', height: '300px', border: '1px solid #ccc' }}>
      {items.map(item => (
        <div
          key={item.id}
          onMouseDown={(e) => handleMouseDown(e, item)}
          style={{
            position: 'absolute',
            left: item.x,
            top: item.y,
            padding: '10px',
            backgroundColor: 'lightblue',
            cursor: 'move',
            userSelect: 'none'
          }}
        >
          {item.text}
        </div>
      ))}
    </div>
  );
}

⌨️ Keyboard Events

Keyboard Input Handling

jsx
function KeyboardEvents() {
  const [input, setInput] = React.useState('');
  const [history, setHistory] = React.useState([]);

  const handleKeyDown = (event) => {
    if (event.key === 'Enter') {
      if (input.trim()) {
        setHistory([...history, input]);
        setInput('');
      }
    } else if (event.key === 'Escape') {
      setInput('');
    }
  };

  return (
    <div>
      <input
        type="text"
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyDown={handleKeyDown}
        placeholder="Enter content, press Enter to submit, Escape to clear"
        style={{ width: '300px', padding: '10px' }}
      />

      <h3>History:</h3>
      <ul>
        {history.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

📝 Form Events

Form Control Events

jsx
function FormEvents() {
  const [formData, setFormData] = React.useState({
    name: '',
    email: '',
    gender: '',
    agree: false
  });

  const handleChange = (event) => {
    const { name, value, type, checked } = event.target;
    setFormData({
      ...formData,
      [name]: type === 'checkbox' ? checked : value
    });
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('Form data:', formData);
    alert('Form submitted successfully!');
  };

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: '400px' }}>
      <div style={{ marginBottom: '15px' }}>
        <label>Name:</label>
        <input
          type="text"
          name="name"
          value={formData.name}
          onChange={handleChange}
          style={{ width: '100%', padding: '8px' }}
        />
      </div>

      <div style={{ marginBottom: '15px' }}>
        <label>Email:</label>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          style={{ width: '100%', padding: '8px' }}
        />
      </div>

      <div style={{ marginBottom: '15px' }}>
        <label>Gender:</label>
        <select name="gender" value={formData.gender} onChange={handleChange}>
          <option value="">Please select</option>
          <option value="male">Male</option>
          <option value="female">Female</option>
        </select>
      </div>

      <div style={{ marginBottom: '15px' }}>
        <label>
          <input
            type="checkbox"
            name="agree"
            checked={formData.agree}
            onChange={handleChange}
          />
          I agree to the terms
        </label>
      </div>

      <button type="submit" disabled={!formData.agree}>
        Submit
      </button>
    </form>
  );
}

⚡ Event Performance Optimization

Debounce and Throttle

jsx
function EventOptimization() {
  const [searchTerm, setSearchTerm] = React.useState('');
  const [debouncedTerm, setDebouncedTerm] = React.useState('');

  // Debounce processing
  React.useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedTerm(searchTerm);
    }, 300);

    return () => clearTimeout(timer);
  }, [searchTerm]);

  // Throttle processing
  const [scrollPosition, setScrollPosition] = React.useState(0);

  const throttledScroll = React.useCallback(
    React.useMemo(() => {
      let lastRun = 0;
      return (event) => {
        if (Date.now() - lastRun >= 100) {
          setScrollPosition(event.target.scrollTop);
          lastRun = Date.now();
        }
      };
    }, []),
    []
  );

  return (
    <div>
      <h3>Search (Debounced)</h3>
      <input
        type="text"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="Enter search content..."
      />
      <p>Search term: {debouncedTerm}</p>

      <h3>Scroll (Throttled)</h3>
      <div
        onScroll={throttledScroll}
        style={{ height: '100px', overflow: 'auto', border: '1px solid #ccc' }}
      >
        <div style={{ height: '300px', padding: '10px' }}>
          Scroll this area to view position info
          <br />Scroll position: {scrollPosition}
        </div>
      </div>
    </div>
  );
}

🎪 Event Delegation

Efficient Event Handling

jsx
function EventDelegation() {
  const [items, setItems] = React.useState([
    { id: 1, name: 'Item 1', active: false },
    { id: 2, name: 'Item 2', active: false },
    { id: 3, name: 'Item 3', active: false }
  ]);

  // Event delegation handling
  const handleContainerClick = (event) => {
    const button = event.target.closest('button');
    if (button) {
      const action = button.dataset.action;
      const itemId = parseInt(button.dataset.itemId);

      if (action === 'toggle') {
        setItems(prev =>
          prev.map(item =>
            item.id === itemId ? { ...item, active: !item.active } : item
          )
        );
      } else if (action === 'delete') {
        setItems(prev => prev.filter(item => item.id !== itemId));
      }
    }
  };

  const addItem = () => {
    const newId = Math.max(...items.map(item => item.id)) + 1;
    setItems([...items, { id: newId, name: `Item ${newId}`, active: false }]);
  };

  return (
    <div onClick={handleContainerClick}>
      <button onClick={addItem} style={{ marginBottom: '10px' }}>
        Add Item
      </button>

      {items.map(item => (
        <div
          key={item.id}
          style={{
            padding: '10px',
            margin: '5px 0',
            border: '1px solid #ddd',
            backgroundColor: item.active ? '#e7f3ff' : '#f8f9fa'
          }}
        >
          {item.name} {item.active ? '(Active)' : ''}
          <button
            data-action="toggle"
            data-item-id={item.id}
            style={{ marginLeft: '10px' }}
          >
            Toggle
          </button>
          <button
            data-action="delete"
            data-item-id={item.id}
            style={{ marginLeft: '5px', color: 'red' }}
          >
            Delete
          </button>
        </div>
      ))}
    </div>
  );
}

📝 Chapter Summary

Through this chapter, you should have mastered:

Core Concepts

  • ✅ How React synthetic events work
  • ✅ Properties and methods of event objects
  • ✅ Best practices for event binding
  • ✅ Event bubbling and default behavior control

Event Types

  • ✅ Mouse events: click, move, drag
  • ✅ Keyboard events: key handling, shortcuts
  • ✅ Form events: input, submit, validation
  • ✅ Touch events: mobile interactions

Performance Optimization

  • ✅ Event delegation to reduce listener count
  • ✅ Debounce and throttle for frequent events
  • ✅ useCallback to cache event handlers
  • ✅ Proper event binding timing

Best Practices

  1. Use synthetic events: Good compatibility, complete functionality
  2. Avoid inline functions: Use useCallback for optimization
  3. Use event delegation reasonably: Reduce memory usage
  4. Handle cleanup logic: Prevent memory leaks
  5. Monitor performance: Pay attention to event handling performance

Continue Learning: Next Chapter - React Conditional and List Rendering

Content is for learning and research only.