React Events vs Vanilla JavaScript

In vanilla JavaScript, you attach event listeners directly to DOM elements. React takes a different approach with SyntheticEvents that provide consistency across browsers and integrate seamlessly with React's component system.

Vanilla JavaScript
JavaScript
// HTML
<button id="myButton">Click me</button>
<input id="myInput" type="text">

// JavaScript
const button = document.getElementById('myButton');
const input = document.getElementById('myInput');

// Adding event listeners
button.addEventListener('click', function(event) {
  console.log('Button clicked!');
  console.log(event.target);
});

input.addEventListener('change', function(event) {
  console.log('Input changed:', event.target.value);
});

// Form submission
const form = document.getElementById('myForm');
form.addEventListener('submit', function(event) {
  event.preventDefault();
  console.log('Form submitted');
});
React Events
React JSX
function MyComponent() {
  // Event handlers as functions
  const handleClick = (event) => {
    console.log('Button clicked!');
    console.log(event.target);
  };

  const handleChange = (event) => {
    console.log('Input changed:', event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('Form submitted');
  };

  return (
    <div>
      <button onClick={handleClick}>Click me</button>
      <input type="text" onChange={handleChange} />
      
      <form onSubmit={handleSubmit}>
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

Understanding SyntheticEvent

React wraps native events in SyntheticEvent objects. These provide the same interface as native events but work consistently across all browsers and integrate with React's event system.

🎯 SyntheticEvent Benefits

  • Cross-browser compatibility - Same API across all browsers
  • Event pooling - Better performance through object reuse
  • preventDefault() and stopPropagation() - Standard methods work everywhere
  • Consistent behavior - No browser-specific quirks
SyntheticEvent Properties
function EventExample() {
  const handleEvent = (event) => {
    console.log('Event type:', event.type);           // 'click', 'change', etc.
    console.log('Target element:', event.target);     // Element that triggered event
    console.log('Current target:', event.currentTarget); // Element with event handler
    console.log('Native event:', event.nativeEvent);  // Access to original DOM event
    
    // Prevent default behavior
    event.preventDefault();
    
    // Stop event propagation
    event.stopPropagation();
  };

  return (
    <div onClick={handleEvent}>
      <button onClick={handleEvent}>
        Click me to see event details
      </button>
    </div>
  );
}

Common Event Types

React supports all standard DOM events. Here are the most commonly used ones:

🖱️ Mouse Events

Mouse Events
<button 
  onClick={handleClick}
  onDoubleClick={handleDoubleClick}
  onMouseDown={handleMouseDown}
  onMouseUp={handleMouseUp}
  onMouseEnter={handleMouseEnter}
  onMouseLeave={handleMouseLeave}
  onMouseOver={handleMouseOver}
  onMouseMove={handleMouseMove}
>
  Interactive Button
</button>

⌨️ Keyboard Events

Keyboard Events
<input
  onKeyDown={handleKeyDown}
  onKeyUp={handleKeyUp}
  onKeyPress={handleKeyPress}
  onFocus={handleFocus}
  onBlur={handleBlur}
  placeholder="Type something..."
/>

// Example key handler
const handleKeyDown = (event) => {
  if (event.key === 'Enter') {
    console.log('Enter pressed!');
  }
};

📝 Form Events

Form Events
<form onSubmit={handleSubmit}>
  <input 
    onChange={handleChange}
    onInput={handleInput}
    onFocus={handleFocus}
    onBlur={handleBlur}
  />
  
  <select onChange={handleSelectChange}>
    <option value="1">Option 1</option>
    <option value="2">Option 2</option>
  </select>
  
  <button type="submit">Submit</button>
</form>

🎯 Focus Events

Focus Events
<div>
  <input
    onFocus={() => console.log('Input focused')}
    onBlur={() => console.log('Input blurred')}
    placeholder="Focus me"
  />
  
  <div
    tabIndex={0}
    onFocus={() => console.log('Div focused')}
    onBlur={() => console.log('Div blurred')}
  >
    Focusable div
  </div>
</div>

Event Handler Patterns

There are several ways to write event handlers in React. Let's explore the most common patterns:

1. Inline Arrow Functions

Quick for simple handlers, but creates new function on each render.

Inline Functions
function Component() {
  const [count, setCount] = useState(0);

  return (
    <div>
      {/* Simple inline handler */}
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      
      {/* Inline with parameters */}
      <button onClick={() => setCount(count + 5)}>
        Add 5
      </button>
      
      {/* Inline with event object */}
      <input onChange={(e) => console.log(e.target.value)} />
    </div>
  );
}

2. Defined Function Handlers

Better performance for complex logic, function reference stays the same.

Function Handlers
function Component() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  // Defined handlers
  const handleNameChange = (event) => {
    setName(event.target.value);
  };

  const handleEmailChange = (event) => {
    setEmail(event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('Submitting:', { name, email });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        value={name}
        onChange={handleNameChange}
        placeholder="Name"
      />
      <input 
        value={email}
        onChange={handleEmailChange}
        placeholder="Email"
      />
      <button type="submit">Submit</button>
    </form>
  );
}

3. Generic Handlers with Parameters

Reusable handlers that work with multiple elements.

Generic Handlers
function FormComponent() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    age: ''
  });

  // Generic handler for all form fields
  const handleInputChange = (fieldName) => (event) => {
    setFormData(prev => ({
      ...prev,
      [fieldName]: event.target.value
    }));
  };

  // Alternative: single handler using name attribute
  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };

  return (
    <div>
      {/* Using generic handler with parameter */}
      <input 
        value={formData.name}
        onChange={handleInputChange('name')}
        placeholder="Name"
      />
      
      {/* Using single handler with name attribute */}
      <input 
        name="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="Email"
      />
      
      <input 
        name="age"
        value={formData.age}
        onChange={handleChange}
        placeholder="Age"
      />
    </div>
  );
}

Practical Event Handling Examples

🎮 Interactive Counter with Multiple Events

Interactive Counter
function InteractiveCounter() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(1);

  const increment = () => setCount(prev => prev + step);
  const decrement = () => setCount(prev => prev - step);
  const reset = () => setCount(0);
  
  const handleKeyPress = (event) => {
    switch(event.key) {
      case 'ArrowUp':
        increment();
        break;
      case 'ArrowDown':
        decrement();
        break;
      case 'r':
        reset();
        break;
      default:
        break;
    }
  };

  return (
    <div 
      tabIndex={0} 
      onKeyDown={handleKeyPress}
      style={{ padding: '20px', outline: 'none' }}
    >
      <h2>Count: {count}</h2>
      
      <div>
        <button onClick={decrement}>-{step}</button>
        <button onClick={increment}>+{step}</button>
        <button onClick={reset}>Reset</button>
      </div>
      
      <div>
        <label>
          Step size: 
          <input 
            type="number" 
            value={step}
            onChange={(e) => setStep(Number(e.target.value))}
            min="1"
          />
        </label>
      </div>
      
      <p>Try using arrow keys and 'r' to reset!</p>
    </div>
  );
}

📝 Dynamic Form with Validation

Form with Validation
function ContactForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: ''
  });
  const [errors, setErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  const validateField = (name, value) => {
    switch(name) {
      case 'name':
        return value.length < 2 ? 'Name must be at least 2 characters' : '';
      case 'email':
        return !/\S+@\S+\.\S+/.test(value) ? 'Email is invalid' : '';
      case 'message':
        return value.length < 10 ? 'Message must be at least 10 characters' : '';
      default:
        return '';
    }
  };

  const handleChange = (event) => {
    const { name, value } = event.target;
    
    // Update form data
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
    
    // Validate field and update errors
    const error = validateField(name, value);
    setErrors(prev => ({
      ...prev,
      [name]: error
    }));
  };

  const handleSubmit = async (event) => {
    event.preventDefault();
    setIsSubmitting(true);
    
    // Validate all fields
    const newErrors = {};
    Object.keys(formData).forEach(key => {
      const error = validateField(key, formData[key]);
      if (error) newErrors[key] = error;
    });
    
    setErrors(newErrors);
    
    // Submit if no errors
    if (Object.keys(newErrors).length === 0) {
      try {
        // Simulate API call
        await new Promise(resolve => setTimeout(resolve, 1000));
        console.log('Form submitted:', formData);
        
        // Reset form
        setFormData({ name: '', email: '', message: '' });
        alert('Message sent successfully!');
      } catch (error) {
        console.error('Submission failed:', error);
      }
    }
    
    setIsSubmitting(false);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">Name:</label>
        <input
          id="name"
          name="name"
          type="text"
          value={formData.name}
          onChange={handleChange}
          onBlur={handleChange} // Validate on blur too
        />
        {errors.name && <span className="error">{errors.name}</span>}
      </div>
      
      <div>
        <label htmlFor="email">Email:</label>
        <input
          id="email"
          name="email"
          type="email"
          value={formData.email}
          onChange={handleChange}
          onBlur={handleChange}
        />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>
      
      <div>
        <label htmlFor="message">Message:</label>
        <textarea
          id="message"
          name="message"
          value={formData.message}
          onChange={handleChange}
          onBlur={handleChange}
          rows={4}
        />
        {errors.message && <span className="error">{errors.message}</span>}
      </div>
      
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Sending...' : 'Send Message'}
      </button>
    </form>
  );
}

Event Handling Best Practices

✅ Performance Tips

  • Use defined functions for complex handlers
  • Avoid creating new functions in render
  • Use useCallback for expensive operations
  • Debounce frequently fired events

✅ Accessibility

  • Support keyboard navigation
  • Use semantic HTML elements
  • Provide ARIA labels when needed
  • Handle focus management properly

✅ Error Handling

  • Always validate user input
  • Prevent form submission on errors
  • Show clear error messages
  • Handle async operation failures

❌ Common Mistakes

  • Forgetting to call preventDefault()
  • Not handling async operations properly
  • Creating new functions on every render
  • Forgetting to validate form inputs

What's Next?

You now know how to handle user interactions in React using the powerful event system. Next, you'll learn how to conditionally render content based on state and props.

🚀 Continue Learning

  1. Conditional Rendering & Lists - Show different content dynamically
  2. React Hooks - Advanced state management with useEffect
  3. State Management - Managing complex application state
  4. Transition Guide - Complete migration strategies