React Events - Complete Guide

React Events - Complete Guide

Events make React applications interactive. Understanding how to handle events properly is essential for building user interfaces that respond to user actions. This guide covers everything you need to know about React events.

What are React Events?

React events are actions that happen in the browser, like clicks, key presses, form submissions, and mouse movements. React wraps native browser events in a synthetic event system that provides consistent behavior across all browsers.

Basic Event Handling

Event Handler Syntax

React events are named using camelCase and passed as functions rather than strings:

// HTML (old way)
<button onclick="handleClick()">Click me</button>

// React (new way)
<button onClick={handleClick}>Click me</button>

Simple Click Event

function ClickButton() {
  const handleClick = () => {
    console.log('Button clicked!');
    alert('Hello from React!');
  };
  
  return <button onClick={handleClick}>Click me</button>;
}

Event Handler with Parameters

function UserButtons() {
  const handleUserClick = (userName) => {
    alert(`Hello, ${userName}!`);
  };
  
  return (
    <div>
      <button onClick={() => handleUserClick('Alice')}>Greet Alice</button>
      <button onClick={() => handleUserClick('Bob')}>Greet Bob</button>
    </div>
  );
}

Common Event Types

Mouse Events

function MouseEvents() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [isClicked, setIsClicked] = useState(false);
  
  const handleMouseMove = (e) => {
    setPosition({ x: e.clientX, y: e.clientY });
  };
  
  const handleMouseDown = () => setIsClicked(true);
  const handleMouseUp = () => setIsClicked(false);
  const handleMouseEnter = () => console.log('Mouse entered');
  const handleMouseLeave = () => console.log('Mouse left');
  
  return (
    <div 
      className="mouse-area"
      onMouseMove={handleMouseMove}
      onMouseDown={handleMouseDown}
      onMouseUp={handleMouseUp}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
      style={{
        width: '300px',
        height: '200px',
        border: '2px solid #ccc',
        backgroundColor: isClicked ? '#e0e0e0' : '#f0f0f0',
        cursor: 'pointer'
      }}
    >
      <p>Mouse position: {position.x}, {position.y}</p>
      <p>Mouse is {isClicked ? 'pressed' : 'not pressed'}</p>
    </div>
  );
}

Keyboard Events

function KeyboardEvents() {
  const [keys, setKeys] = useState([]);
  const [lastKey, setLastKey] = useState('');
  
  const handleKeyDown = (e) => {
    setLastKey(e.key);
    if (!keys.includes(e.key)) {
      setKeys([...keys, e.key]);
    }
  };
  
  const handleKeyUp = (e) => {
    setKeys(keys.filter(key => key !== e.key));
  };
  
  const handleKeyPress = (e) => {
    console.log('Key pressed:', e.key);
  };
  
  return (
    <div>
      <input
        type="text"
        placeholder="Type here..."
        onKeyDown={handleKeyDown}
        onKeyUp={handleKeyUp}
        onKeyPress={handleKeyPress}
        style={{ width: '300px', padding: '10px' }}
      />
      <p>Last key: {lastKey}</p>
      <p>Currently pressed: {keys.join(', ') || 'None'}</p>
    </div>
  );
}

Form Events

function FormEvents() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    select: 'option1'
  });
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };
  
  const handleSubmit = (e) => {
    e.preventDefault(); // Prevent default form submission
    console.log('Form submitted:', formData);
    alert(`Form submitted with: ${JSON.stringify(formData)}`);
  };
  
  const handleReset = () => {
    setFormData({
      name: '',
      email: '',
      select: 'option1'
    });
  };
  
  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>Select:</label>
        <select
          name="select"
          value={formData.select}
          onChange={handleChange}
        >
          <option value="option1">Option 1</option>
          <option value="option2">Option 2</option>
          <option value="option3">Option 3</option>
        </select>
      </div>
      
      <button type="submit">Submit</button>
      <button type="button" onClick={handleReset}>Reset</button>
    </form>
  );
}

Focus Events

function FocusEvents() {
  const [isFocused, setIsFocused] = useState(false);
  const [focusCount, setFocusCount] = useState(0);
  
  const handleFocus = () => {
    setIsFocused(true);
    setFocusCount(focusCount + 1);
  };
  
  const handleBlur = () => {
    setIsFocused(false);
  };
  
  return (
    <div>
      <input
        type="text"
        placeholder="Click to focus"
        onFocus={handleFocus}
        onBlur={handleBlur}
        style={{
          border: isFocused ? '2px solid blue' : '2px solid gray',
          padding: '10px'
        }}
      />
      <p>Input is {isFocused ? 'focused' : 'not focused'}</p>
      <p>Focus count: {focusCount}</p>
    </div>
  );
}

Event Object Properties

The event object contains useful information about the event:

function EventInfo() {
  const handleClick = (e) => {
    console.log('Event object:', e);
    console.log('Target:', e.target);
    console.log('Current target:', e.currentTarget);
    console.log('Type:', e.type);
    console.log('Time stamp:', e.timeStamp);
  };
  
  const handleInputChange = (e) => {
    console.log('Input value:', e.target.value);
    console.log('Input name:', e.target.name);
    console.log('Input type:', e.target.type);
  };
  
  return (
    <div>
      <button onClick={handleClick}>Log Event Info</button>
      <input
        type="text"
        placeholder="Type to see input events"
        onChange={handleInputChange}
        style={{ marginLeft: '10px', padding: '5px' }}
      />
    </div>
  );
}

Event Propagation

Bubbling and Capturing

Events in React propagate in two phases: capturing (top-down) and bubbling (bottom-up):

function EventPropagation() {
  const handleDivClick = (e) => {
    console.log('Div clicked - Current target:', e.currentTarget);
  };
  
  const handleButtonClick = (e) => {
    console.log('Button clicked - Current target:', e.currentTarget);
    console.log('Button clicked - Target:', e.target);
    
    // Stop propagation
    // e.stopPropagation();
  };
  
  return (
    <div onClick={handleDivClick} style={{ padding: '20px', backgroundColor: '#f0f0f0' }}>
      <p>Click the button to see event propagation</p>
      <button onClick={handleButtonClick}>Click me</button>
    </div>
  );
}

Preventing Default Behavior

Some events have default browser behavior that you might want to prevent:

function PreventDefault() {
  const handleSubmit = (e) => {
    e.preventDefault(); // Prevent form from submitting/reloading page
    console.log('Form submission prevented');
  };
  
  const handleLinkClick = (e) => {
    e.preventDefault(); // Prevent link from navigating
    console.log('Link navigation prevented');
  };
  
  const handleContextMenu = (e) => {
    e.preventDefault(); // Prevent context menu
    console.log('Context menu prevented');
  };
  
  return (
    <div onContextMenu={handleContextMenu} style={{ padding: '20px' }}>
      <form onSubmit={handleSubmit}>
        <input type="text" placeholder="Type and press Enter" />
        <button type="submit">Submit</button>
      </form>
      
      <a href="https://example.com" onClick={handleLinkClick}>
        This link won't navigate
      </a>
      
      <p>Right-click here to see context menu prevention</p>
    </div>
  );
}

Event Handling Best Practices

1. Use Arrow Functions for Class Components

If you’re using class components, bind event handlers properly:

class ButtonComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    
    // Method 1: Bind in constructor
    this.handleClick = this.handleClick.bind(this);
  }
  
  // Method 2: Use arrow function as class property
  handleIncrement = () => {
    this.setState(prevState => ({ count: prevState.count + 1 }));
  };
  
  // Method 3: Regular method (needs binding)
  handleClick() {
    console.log('Button clicked');
  }
  
  render() {
    return (
      <div>
        <button onClick={this.handleClick}>Click me</button>
        <button onClick={this.handleIncrement}>
          Count: {this.state.count}
        </button>
        {/* Method 4: Inline arrow function */}
        <button onClick={() => console.log('Inline click')}>
          Inline
        </button>
      </div>
    );
  }
}

2. Performance Considerations

Be careful with inline functions in lists:

function TodoList({ todos, onToggle }) {
  // Bad - creates new function on every render
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <button onClick={() => onToggle(todo.id)}>
            Toggle
          </button>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

// Better - use data attributes or useCallback
function TodoList({ todos, onToggle }) {
  const handleToggle = useCallback((e) => {
    const todoId = parseInt(e.currentTarget.dataset.todoId);
    onToggle(todoId);
  }, [onToggle]);
  
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <button 
            data-todo-id={todo.id}
            onClick={handleToggle}
          >
            Toggle
          </button>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

3. Clean Up Event Listeners

When adding event listeners to the DOM, remember to clean them up:

function WindowEvents() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });
  
  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };
    
    window.addEventListener('resize', handleResize);
    
    // Cleanup function
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);
  
  return (
    <div>
      <p>Window size: {windowSize.width} x {windowSize.height}</p>
    </div>
  );
}

Custom Event Handlers

Creating Reusable Event Handlers

function useKeyPress(targetKey) {
  const [keyPressed, setKeyPressed] = useState(false);
  
  useEffect(() => {
    const handleKeyDown = (e) => {
      if (e.key === targetKey) {
        setKeyPressed(true);
      }
    };
    
    const handleKeyUp = (e) => {
      if (e.key === targetKey) {
        setKeyPressed(false);
      }
    };
    
    window.addEventListener('keydown', handleKeyDown);
    window.addEventListener('keyup', handleKeyUp);
    
    return () => {
      window.removeEventListener('keydown', handleKeyDown);
      window.removeEventListener('keyup', handleKeyUp);
    };
  }, [targetKey]);
  
  return keyPressed;
}

function KeyboardShortcuts() {
  const enterPressed = useKeyPress('Enter');
  const escapePressed = useKeyPress('Escape');
  
  useEffect(() => {
    if (enterPressed) {
      console.log('Enter key pressed!');
    }
  }, [enterPressed]);
  
  useEffect(() => {
    if (escapePressed) {
      console.log('Escape key pressed!');
    }
  }, [escapePressed]);
  
  return (
    <div>
      <p>Press Enter or Escape to see console output</p>
      <p>Enter pressed: {enterPressed ? 'Yes' : 'No'}</p>
      <p>Escape pressed: {escapePressed ? 'Yes' : 'No'}</p>
    </div>
  );
}

Common Event Patterns

1. Toggle Pattern

function ToggleExample() {
  const [isVisible, setIsVisible] = useState(false);
  
  const toggle = () => setIsVisible(!isVisible);
  
  return (
    <div>
      <button onClick={toggle}>
        {isVisible ? 'Hide' : 'Show'} Content
      </button>
      {isVisible && <p>This content can be toggled!</p>}
    </div>
  );
}

2. Counter Pattern

function CounterExample() {
  const [count, setCount] = useState(0);
  
  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  const reset = () => setCount(0);
  
  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

3. Search Pattern

function SearchExample() {
  const [searchTerm, setSearchTerm] = useState('');
  const [results, setResults] = useState([]);
  
  const data = ['Apple', 'Banana', 'Orange', 'Grape', 'Strawberry'];
  
  const handleSearch = (e) => {
    const term = e.target.value.toLowerCase();
    setSearchTerm(term);
    
    const filtered = data.filter(item => 
      item.toLowerCase().includes(term)
    );
    setResults(filtered);
  };
  
  return (
    <div>
      <input
        type="text"
        placeholder="Search fruits..."
        value={searchTerm}
        onChange={handleSearch}
      />
      
      <ul>
        {results.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

Accessibility Considerations

Keyboard Navigation

Make sure your interactive elements work with keyboard:

function AccessibleButton() {
  const [isActive, setIsActive] = useState(false);
  
  const handleClick = () => {
    setIsActive(!isActive);
  };
  
  const handleKeyDown = (e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      handleClick();
    }
  };
  
  return (
    <button
      onClick={handleClick}
      onKeyDown={handleKeyDown}
      aria-pressed={isActive}
      style={{
        backgroundColor: isActive ? 'blue' : 'gray',
        color: 'white',
        padding: '10px 20px',
        border: 'none',
        cursor: 'pointer'
      }}
    >
      {isActive ? 'Active' : 'Inactive'}
    </button>
  );
}

Summary

React events are fundamental to building interactive applications. Remember these key points:

  • Use camelCase for event names (onClick, onChange, etc.)
  • Pass event handlers as functions, not strings
  • Use the event object to get information about the event
  • Prevent default behavior when needed with e.preventDefault()
  • Stop event propagation with e.stopPropagation()
  • Clean up event listeners to prevent memory leaks
  • Consider performance when using inline functions
  • Make your components accessible with proper keyboard handling

Mastering React events will help you create rich, interactive user experiences that work seamlessly across all browsers.


External Resources:

Related Tutorials:

Last updated on