reactjs
/

React Events – Handling User Interactions

Last Sync: Today

On this page

12
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

reactjs

React Events – Handling User Interactions

What are Synthetic Events?

React uses Synthetic Events – a cross-browser wrapper around native browser events. Synthetic events provide a consistent API across all browsers and improve performance through event pooling (in React 16 and earlier).

Basic Event Handling

React JSXRead-only
1
function ButtonClick() {
  // Basic event handler
  const handleClick = () => {
    console.log('Button clicked!');
  };

  return <button onClick={handleClick}>Click Me</button>;
}

// Inline event handler
function InlineHandler() {
  return (
    <button onClick={() => console.log('Clicked!')}>
      Click Me
    </button>
  );
}

// With event parameter
function EventParam() {
  const handleClick = (event) => {
    console.log('Event type:', event.type);
    console.log('Target:', event.target);
    console.log('Current target:', event.currentTarget);
  };

  return <button onClick={handleClick}>Click Me</button>;
}

// Passing additional arguments
function WithArgs() {
  const handleClick = (message, event) => {
    console.log(message, event);
  };

  return (
    <button onClick={(e) => handleClick('Hello!', e)}>
      Click Me
    </button>
  );
}

Common Event Types

React JSXRead-only
1
function EventTypes() {
  // Mouse events
  const handleClick = (e) => console.log('Click', e.clientX, e.clientY);
  const handleDoubleClick = (e) => console.log('Double click');
  const handleMouseEnter = (e) => console.log('Mouse entered');
  const handleMouseLeave = (e) => console.log('Mouse left');
  const handleMouseMove = (e) => console.log('Mouse moving', e.clientX, e.clientY);
  const handleContextMenu = (e) => {
    e.preventDefault();
    console.log('Right click prevented');
  };

  // Keyboard events
  const handleKeyDown = (e) => {
    console.log('Key down:', e.key, e.code, e.ctrlKey);
    if (e.key === 'Enter') {
      console.log('Enter pressed');
    }
  };
  const handleKeyUp = (e) => console.log('Key up:', e.key);
  const handleKeyPress = (e) => console.log('Key press:', e.key);

  // Form events
  const handleChange = (e) => console.log('Value changed:', e.target.value);
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted');
  };
  const handleFocus = (e) => console.log('Input focused');
  const handleBlur = (e) => console.log('Input blurred');
  const handleInput = (e) => console.log('Input event:', e.target.value);

  // Clipboard events
  const handleCopy = (e) => {
    console.log('Copied!');
    e.clipboardData.setData('text/plain', 'Custom copy text');
    e.preventDefault();
  };
  const handlePaste = (e) => console.log('Pasted:', e.clipboardData.getData('text'));

  // Focus events
  const handleFocusIn = (e) => console.log('Focus in');
  const handleFocusOut = (e) => console.log('Focus out');

  return (
    <div>
      <button onClick={handleClick}>Click</button>
      <button onDoubleClick={handleDoubleClick}>Double Click</button>
      <div onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
        Hover me
      </div>
      <input onKeyDown={handleKeyDown} placeholder="Press any key" />
      <form onSubmit={handleSubmit}>
        <input onChange={handleChange} onFocus={handleFocus} onBlur={handleBlur} />
        <button type="submit">Submit</button>
      </form>
      <div onCopy={handleCopy}>Copy this text</div>
    </div>
  );
}

Event Pooling (React 16 and earlier)

React JSXRead-only
1
// In React 16 and earlier, SyntheticEvents are pooled
// Event properties are nullified after the handler runs

// ❌ Problem in React 16 - async access
function ProblemHandler() {
  const handleClick = (event) => {
    setTimeout(() => {
      console.log(event.type); // null - event has been pooled
    }, 100);
  };
  return <button onClick={handleClick}>Click</button>;
}

// ✅ Solution 1: Call persist()
function PersistHandler() {
  const handleClick = (event) => {
    event.persist(); // Removes event from pool
    setTimeout(() => {
      console.log(event.type); // Works!
    }, 100);
  };
  return <button onClick={handleClick}>Click</button>;
}

// ✅ Solution 2: Extract needed properties
function ExtractHandler() {
  const handleClick = (event) => {
    const { type, target } = event;
    setTimeout(() => {
      console.log(type, target); // Works with primitives
    }, 100);
  };
  return <button onClick={handleClick}>Click</button>;
}

// Note: React 17+ removed event pooling
// Events are not pooled in React 17 and later

Preventing Default Behavior

React JSXRead-only
1
function FormWithPrevention() {
  // Prevent form submission
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted but page did not reload');
  };

  // Prevent link navigation
  const handleLinkClick = (e) => {
    e.preventDefault();
    console.log('Link clicked but navigation prevented');
  };

  // Prevent context menu
  const handleContextMenu = (e) => {
    e.preventDefault();
    console.log('Right-click menu prevented');
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input type="text" />
        <button type="submit">Submit</button>
      </form>
      
      <a href="https://example.com" onClick={handleLinkClick}>
        Prevented Link
      </a>
      
      <div onContextMenu={handleContextMenu}>
        Right-click here (menu prevented)
      </div>
    </div>
  );
}

Event Propagation

React JSXRead-only
1
function EventPropagation() {
  // Stop propagation
  const handleInnerClick = (e) => {
    e.stopPropagation();
    console.log('Inner clicked - propagation stopped');
  };

  const handleOuterClick = () => {
    console.log('Outer clicked');
  };

  // Stop immediate propagation
  const handleFirstButton = (e) => {
    e.stopImmediatePropagation();
    console.log('First handler - stops others');
  };

  const handleSecondButton = () => {
    console.log('Second handler - will not run');
  };

  return (
    <div>
      {/* Bubbling example */}
      <div onClick={handleOuterClick} style={{ padding: '20px', background: '#f0f0f0' }}>
        <button onClick={handleInnerClick}>Click me (stops propagation)</button>
      </div>

      {/* Multiple handlers on same element */}
      <button onClick={handleFirstButton} onClickCapture={handleSecondButton}>
        Click me
      </button>
    </div>
  );
}

// Capture phase (useCapture equivalent)
function CapturePhase() {
  const handleParentCapture = () => {
    console.log('Parent capture phase');
  };

  const handleParentBubble = () => {
    console.log('Parent bubble phase');
  };

  const handleChildClick = () => {
    console.log('Child clicked');
  };

  return (
    <div
      onClickCapture={handleParentCapture}  // Capture phase
      onClick={handleParentBubble}          // Bubble phase
    >
      <button onClick={handleChildClick}>Click me</button>
    </div>
  );
}

Common Event Patterns

React JSXRead-only
1
// Debouncing events
function SearchInput() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  useEffect(() => {
    const debounceTimer = setTimeout(() => {
      if (query) {
        searchAPI(query).then(setResults);
      }
    }, 500);

    return () => clearTimeout(debounceTimer);
  }, [query]);

  return (
    <input
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="Search..."
    />
  );
}

// Throttling events
function ScrollTracker() {
  const [scrollPos, setScrollPos] = useState(0);

  useEffect(() => {
    let ticking = false;
    
    const handleScroll = () => {
      if (!ticking) {
        requestAnimationFrame(() => {
          setScrollPos(window.scrollY);
          ticking = false;
        });
        ticking = true;
      }
    };

    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  return <div>Scroll position: {scrollPos}</div>;
}

// Keyboard shortcuts
function KeyboardShortcuts() {
  useEffect(() => {
    const handleKeyPress = (e) => {
      // Ctrl/Cmd + S to save
      if ((e.ctrlKey || e.metaKey) && e.key === 's') {
        e.preventDefault();
        console.log('Save shortcut triggered');
      }
      
      // Escape to close
      if (e.key === 'Escape') {
        console.log('Close modal');
      }
      
      // Arrow keys
      if (e.key === 'ArrowUp') {
        console.log('Navigate up');
      }
      if (e.key === 'ArrowDown') {
        console.log('Navigate down');
      }
    };

    window.addEventListener('keydown', handleKeyPress);
    return () => window.removeEventListener('keydown', handleKeyPress);
  }, []);

  return <div>Try pressing Ctrl+S or Escape</div>;
}

Event Best Practices

  • Use arrow functions or bind in constructor – Preserve this context
  • Extract event handlers – Avoid inline functions in render for performance
  • Use useCallback for memoized handlers – Prevent unnecessary re-renders
  • Prevent default when needed – Especially for forms and links
  • Stop propagation carefully – Can break event flow in complex UIs
  • Use event pooling awareness – For React 16 and earlier
  • Handle keyboard accessibility – Support Enter, Space, Escape keys
  • Debounce expensive handlers – Search inputs, scroll events
  • Remove event listeners – In useEffect cleanup functions
  • Use TypeScript for event typing – Better developer experience

TypeScript Event Types

React TSXRead-only
1
import { ChangeEvent, FormEvent, KeyboardEvent, MouseEvent } from 'react';

function TypedEvents() {
  // Mouse event
  const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
    console.log(e.clientX, e.clientY);
  };

  // Change event
  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };

  // Keyboard event
  const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter') {
      console.log('Enter pressed');
    }
  };

  // Form event
  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    console.log('Form submitted');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        onChange={handleChange}
        onKeyDown={handleKeyDown}
        placeholder="Type something..."
      />
      <button onClick={handleClick}>Submit</button>
    </form>
  );
}

Common Mistakes

React JSXRead-only
1
// ❌ Wrong: Calling function instead of passing reference
<button onClick={handleClick()}>Click</button> // Executes on render

// ✅ Correct: Pass reference
<button onClick={handleClick}>Click</button>

// ❌ Wrong: Missing binding in class components
class MyComponent extends Component {
  handleClick() {
    console.log(this); // undefined
  }
  render() {
    return <button onClick={this.handleClick}>Click</button>;
  }
}

// ✅ Correct: Bind in constructor or use arrow function
class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  // OR use class property arrow function
  handleClick = () => {
    console.log(this); // works
  }
}

// ❌ Wrong: Forgetting to prevent default
const handleSubmit = (e) => {
  // Missing e.preventDefault()
  console.log('Form submitted');
};

// ✅ Correct
const handleSubmit = (e) => {
  e.preventDefault();
  console.log('Form submitted');
};

// ❌ Wrong: Using event asynchronously without persist (React 16)
const handleClick = (e) => {
  setTimeout(() => {
    console.log(e.target); // null in React 16
  }, 100);
};

// ✅ Correct: Extract values or call persist()
const handleClick = (e) => {
  const target = e.target;
  setTimeout(() => {
    console.log(target); // Works
  }, 100);
};

Event Reference Table

CategoryEventsUse Case
MouseonClick, onDoubleClick, onMouseEnter, onMouseLeave, onMouseMove, onContextMenuUser clicks, hovers, right-clicks
KeyboardonKeyDown, onKeyUp, onKeyPressKeyboard input, shortcuts
FormonChange, onSubmit, onFocus, onBlur, onInputForm inputs, validation
ClipboardonCopy, onCut, onPasteCopy/paste operations
FocusonFocus, onBlur, onFocusIn, onFocusOutInput focus tracking
TouchonTouchStart, onTouchEnd, onTouchMoveMobile touch events
DragonDragStart, onDragEnd, onDragOver, onDropDrag and drop

Conclusion

React events provide a consistent, cross-browser interface for handling user interactions. Understanding synthetic events, event pooling, propagation, and common patterns is crucial for building interactive applications. Always use proper event handling patterns and consider performance implications.

Try it yourself

import { useState } from 'react';

function EventDemo() {
  const [clicks, setClicks] = useState([]);
  const [keyLog, setKeyLog] = useState([]);
  const [formData, setFormData] = useState({ name: '', email: '' });

  // Mouse events
  const handleClick = (e, buttonName) => {
    const newClick = {
      button: buttonName,
      time: new Date().toLocaleTimeString(),
      x: e.clientX,
      y: e.clientY
    };
    setClicks([newClick, ...clicks]);
  };

  // Keyboard events
  const handleKeyDown = (e) => {
    setKeyLog([`Key pressed: ${e.key}`, ...keyLog].slice(0, 5));
    
    if (e.key === 'Enter') {
      alert('Enter key pressed!');
    }
  };

  // Form events
  const handleChange = (e) => {
    setFormData({
      ...formData,
      [e.target.name]: e.target.value
    });
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    alert(`Submitted: ${formData.name}, ${formData.email}`);
  };

  // Mouse enter/leave
  const [isHovering, setIsHovering] = useState(false);

  return (
    <div style={{ padding: '20px', fontFamily: 'Arial' }}>
      <h1>React Events Demo</h1>

      {/* Mouse Events */}
      <div style={{ marginBottom: '20px', padding: '15px', border: '1px solid #ddd', borderRadius: '8px' }}>
        <h2>Mouse Events</h2>
        <button onClick={(e) => handleClick(e, 'Left')}>Left Click</button>
        <button onContextMenu={(e) => {
          e.preventDefault();
          handleClick(e, 'Right');
        }}>Right Click</button>
        <button onDoubleClick={(e) => handleClick(e, 'Double')}>Double Click</button>
        
        <div
          onMouseEnter={() => setIsHovering(true)}
          onMouseLeave={() => setIsHovering(false)}
          style={{
            marginTop: '10px',
            padding: '10px',
            background: isHovering ? '#e0e0e0' : '#f5f5f5',
            textAlign: 'center',
            transition: 'background 0.3s'
          }}
        >
          {isHovering ? 'Mouse is over me!' : 'Hover over me'}
        </div>
        
        {clicks.length > 0 && (
          <div style={{ marginTop: '10px' }}>
            <strong>Click Log:</strong>
            <ul>
              {clicks.map((click, idx) => (
                <li key={idx}>
                  {click.button} click at {click.time} (X: {click.x}, Y: {click.y})
                </li>
              ))}
            </ul>
          </div>
        )}
      </div>

      {/* Keyboard Events */}
      <div style={{ marginBottom: '20px', padding: '15px', border: '1px solid #ddd', borderRadius: '8px' }}>
        <h2>Keyboard Events</h2>
        <input
          type="text"
          onKeyDown={handleKeyDown}
          placeholder="Press any key..."
          style={{ width: '100%', padding: '8px', marginBottom: '10px' }}
        />
        {keyLog.length > 0 && (
          <div>
            <strong>Key Log:</strong>
            <ul>
              {keyLog.map((log, idx) => (
                <li key={idx}>{log}</li>
              ))}
            </ul>
          </div>
        )}
      </div>

      {/* Form Events */}
      <div style={{ marginBottom: '20px', padding: '15px', border: '1px solid #ddd', borderRadius: '8px' }}>
        <h2>Form Events</h2>
        <form onSubmit={handleSubmit}>
          <div style={{ marginBottom: '10px' }}>
            <label>Name: </label>
            <input
              type="text"
              name="name"
              value={formData.name}
              onChange={handleChange}
              onFocus={() => console.log('Name focused')}
              onBlur={() => console.log('Name blurred')}
            />
          </div>
          <div style={{ marginBottom: '10px' }}>
            <label>Email: </label>
            <input
              type="email"
              name="email"
              value={formData.email}
              onChange={handleChange}
            />
          </div>
          <button type="submit">Submit</button>
        </form>
        <div style={{ marginTop: '10px' }}>
          <strong>Form Data:</strong> {JSON.stringify(formData)}
        </div>
      </div>

      {/* Tips */}
      <div style={{ padding: '15px', background: '#f0f7ff', borderRadius: '8px' }}>
        <h3>💡 Tips:</h3>
        <ul>
          <li>Try clicking with left, right, or double-click</li>
          <li>Hover over the gray box</li>
          <li>Press keys in the input field (try Enter!)</li>
          <li>Submit the form (page won't reload)</li>
        </ul>
      </div>
    </div>
  );
}

export default EventDemo;

Test Your Knowledge

Q1
of 4

What is React's event system called?

A
Native Events
B
DOM Events
C
Synthetic Events
D
Virtual Events
Q2
of 4

How do you prevent default behavior in React?

A
return false
B
event.preventDefault()
C
event.stop()
D
preventDefault()
Q3
of 4

What method stops event bubbling?

A
event.stopBubble()
B
event.stopPropagation()
C
event.cancelBubble()
D
event.halt()
Q4
of 4

What is event pooling?

A
Collecting events
B
Reusing event objects
C
Queuing events
D
Batching events

Frequently Asked Questions

What are synthetic events?

React's cross-browser wrapper around native browser events with consistent API.

Do React events work the same across browsers?

Yes, React normalizes events to work identically across all browsers.

What is event pooling?

A performance optimization in React 16 where event objects are reused and nullified.

How to pass extra data to event handlers?

Use arrow functions: onClick={(e) => handleClick(data, e)}

Previous

react state

Next

react conditional rendering

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.