Event Handling in React

Making Your Applications Interactive and Responsive

Overview

Welcome to the world of interactive React applications! Events are like the nervous system of your app - they detect user actions and trigger responses. Today, we'll master event handling in React, from basic clicks to complex interactions. By the end, you'll be creating responsive, interactive interfaces that delight users.

React Events vs DOM Events

React events are like a translator between you and the browser. They provide a consistent interface across all browsers while handling the complexity behind the scenes:

graph LR A[User Action] --> B[Native DOM Event] B --> C[React SyntheticEvent] C --> D[Your Event Handler] D --> E[State Update] E --> F[Re-render] F --> G[UI Updates] style C fill:#f9f,stroke:#333,stroke-width:2px style D fill:#9cf,stroke:#333,stroke-width:2px

Basic Event Handling

Handling Click Events

<pre><code> function ButtonExample() { const handleClick = () => { alert('Button clicked!'); }; return ( <button onClick={handleClick}> Click me </button> ); } // Using inline arrow functions function InlineExample() { return ( <button onClick={() => alert('Clicked!')}> Click me </button> ); } // Passing arguments to handlers function ArgumentExample() { const handleClick = (id) => { console.log(`Item ${id} clicked`); }; return ( <div> <button onClick={() => handleClick(1)}>Item 1</button> <button onClick={() => handleClick(2)}>Item 2</button> <button onClick={() => handleClick(3)}>Item 3</button> </div> ); } </code></pre>

Event Object Properties


function EventProperties() {
    const handleClick = (e) => {
        console.log('Event type:', e.type);
        console.log('Target:', e.target);
        console.log('CurrentTarget:', e.currentTarget);
        console.log('NativeEvent:', e.nativeEvent);
        
        // Mouse event specific properties
        console.log('ClientX/Y:', e.clientX, e.clientY);
        console.log('PageX/Y:', e.pageX, e.pageY);
        console.log('ScreenX/Y:', e.screenX, e.screenY);
        
        // Keyboard event specific properties
        console.log('Key:', e.key);
        console.log('KeyCode:', e.keyCode);
        console.log('AltKey:', e.altKey);
        console.log('CtrlKey:', e.ctrlKey);
        console.log('ShiftKey:', e.shiftKey);
        console.log('MetaKey:', e.metaKey);
    };

    return (
        
    );
}
            

Preventing Default Behavior


function PreventDefaultExample() {
    const handleSubmit = (e) => {
        e.preventDefault(); // Prevent form submission
        console.log('Form submitted via JavaScript');
    };

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

    const handleContextMenu = (e) => {
        e.preventDefault(); // Prevent context menu
        console.log('Custom context menu');
    };

    return (
        <div>
            <form onSubmit={handleSubmit}>
                <input type="text" placeholder="Type something" />
                <button type="submit">Submit</button>
            </form>
            
            <a href="https://example.com" onClick={handleLinkClick}>
                Click me (won't navigate)
            </a>
            
            <div 
                onContextMenu={handleContextMenu}
                style={{ padding: '20px', backgroundColor: '#f0f0f0' }}
            >
                Right-click me for custom menu
            </div>
        </div>
    );
}

            

Event Propagation

graph TD A[Event Capturing Phase] --> B[Target Phase] B --> C[Event Bubbling Phase] D[Document] --> E[Parent Element] E --> F[Target Element] F --> G[Parent Element] G --> H[Document] A --> D B --> F C --> H style B fill:#f96,stroke:#333,stroke-width:2px style F fill:#f96,stroke:#333,stroke-width:2px

function EventPropagation() {
    const handleOuterClick = (e) => {
        console.log('Outer div clicked');
    };

    const handleInnerClick = (e) => {
        console.log('Inner div clicked');
        // e.stopPropagation(); // Uncomment to stop bubbling
    };

    const handleButtonClick = (e) => {
        console.log('Button clicked');
        e.stopPropagation(); // Stop event from bubbling up
    };

    return (
        <div 
            onClick={handleOuterClick}
            style={{ padding: '20px', backgroundColor: '#e0e0e0' }}
        >
            Outer Div
            <div 
                onClick={handleInnerClick}
                style={{ padding: '20px', backgroundColor: '#c0c0c0' }}
            >
                Inner Div
                <button onClick={handleButtonClick}>
                    Click me (stops propagation)
                </button>
            </div>
        </div>
    );
}

// Capturing phase example
function CapturingExample() {
    return (
        <div
            onClickCapture={() => console.log('Outer capture')}
            onClick={() => console.log('Outer bubble')}
        >
            <button
                onClickCapture={() => console.log('Button capture')}
                onClick={() => console.log('Button bubble')}
            >
                Click me
            </button>
        </div>
    );
}

            

Custom Events


// Custom event component
function CustomEventComponent({ onCustomEvent }) {
    const triggerCustomEvent = () => {
        const event = new CustomEvent('myCustomEvent', {
            detail: { message: 'Hello from custom event!' }
        });
        window.dispatchEvent(event);
    };

    return (
        
    );
}

// Listening to custom events
function CustomEventListener() {
    const [message, setMessage] = useState('');

    useEffect(() => {
        const handleCustomEvent = (e) => {
            setMessage(e.detail.message);
        };

        window.addEventListener('myCustomEvent', handleCustomEvent);

        return () => {
            window.removeEventListener('myCustomEvent', handleCustomEvent);
        };
    }, []);

    return (
        <div>
            <CustomEventComponent />
            <p>Custom event message: {message}</p>
        </div>
    );

}
            

Touch Events


function TouchEvents() {
    const [touchPosition, setTouchPosition] = useState({ x: 0, y: 0 });
    const [touchStatus, setTouchStatus] = useState('No touch');

    const handleTouchStart = (e) => {
        setTouchStatus('Touch started');
        const touch = e.touches[0];
        setTouchPosition({ x: touch.clientX, y: touch.clientY });
    };

    const handleTouchMove = (e) => {
        setTouchStatus('Touch moving');
        const touch = e.touches[0];
        setTouchPosition({ x: touch.clientX, y: touch.clientY });
    };

    const handleTouchEnd = () => {
        setTouchStatus('Touch ended');
    };

    return (
        <div
            onTouchStart={handleTouchStart}
            onTouchMove={handleTouchMove}
            onTouchEnd={handleTouchEnd}
            style={{
                height: '200px',
                backgroundColor: '#f0f0f0',
                touchAction: 'none' // Prevent scrolling
            }}
        >
            <p>Status: {touchStatus}</p>
            <p>Position: {touchPosition.x}, {touchPosition.y}</p>
        </div>
    );
}

            

Performance Considerations

Event Handler Binding


// Inefficient - creates new function on each render
function InefficientComponent() {
    const [items, setItems] = useState([1, 2, 3]);

    return (
        <div>
            {items.map(item => (
                <button
                    key={item}
                    onClick={() => handleClick(item)} // New function each render
                >
                    Item {item}
                </button>
            ))}
        </div>
    );
}

// Better - use a stable function reference
function EfficientComponent() {
    const [items, setItems] = useState([1, 2, 3]);

    const handleClick = useCallback((item) => {
        console.log('Clicked:', item);
    }, []);

    return (
        <div>
            {items.map(item => (
                <ItemButton
                    key={item}
                    item={item}
                    onClick={handleClick}
                />
            ))}
        </div>
    );
}

const ItemButton = React.memo(({ item, onClick }) => {
    return (
        <button onClick={() => onClick(item)}>
            Item {item}
        </button>
    );
});
        

Event Delegation


function EventDelegation() {
    const [items, setItems] = useState(
        Array.from({ length: 100 }, (_, i) => ({ id: i, name: `Item ${i}` }))
    );

    // Single event handler for all items
    const handleListClick = (e) => {
        const itemId = e.target.dataset.itemId;
        if (itemId) {
            console.log('Clicked item:', itemId);
        }
    };

    return (
        
    {items.map(item => (
  • {item.name}
  • ))}
); }

Common Event Patterns

Debounced Search


function DebouncedSearch() {
    const [searchTerm, setSearchTerm] = useState('');
    const [results, setResults] = useState([]);
    const [isSearching, setIsSearching] = useState(false);

    const debouncedSearch = useRef(
        debounce(async (term) => {
            if (!term) {
                setResults([]);
                return;
            }

            setIsSearching(true);
            try {
                const response = await fetch(`/api/search?q=${term}`);
                const data = await response.json();
                setResults(data);
            } finally {
                setIsSearching(false);
            }
        }, 300)
    ).current;

    const handleChange = (e) => {
        const value = e.target.value;
        setSearchTerm(value);
        debouncedSearch(value);
    };

    return (
        
{isSearching &&
Searching...
}
    {results.map(result => (
  • {result.name}
  • ))}
); } // Debounce helper function function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }

Drag and Drop


function DragAndDrop() {
    const [draggedItem, setDraggedItem] = useState(null);
    const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);

    const handleDragStart = (e, item) => {
        setDraggedItem(item);
        e.dataTransfer.setData('text/plain', item);
    };

    const handleDragOver = (e) => {
        e.preventDefault();
    };

    const handleDrop = (e, targetItem) => {
        e.preventDefault();
        
        const draggedIndex = items.indexOf(draggedItem);
        const targetIndex = items.indexOf(targetItem);
        
        const newItems = [...items];
        newItems.splice(draggedIndex, 1);
        newItems.splice(targetIndex, 0, draggedItem);
        
        setItems(newItems);
        setDraggedItem(null);
    };

    return (
        <ul>
            {items.map(item => (
                <li
                    key={item}
                    draggable
                    onDragStart={(e) => handleDragStart(e, item)}
                    onDragOver={handleDragOver}
                    onDrop={(e) => handleDrop(e, item)}
                    style={{
                        padding: '10px',
                        margin: '5px',
                        backgroundColor: '#f0f0f0',
                        cursor: 'move'
                    }}
                >
                    {item}
                </li>
            ))}
        </ul>
    );
}

            

Best Practices

graph TD A[Event Handling Best Practices] --> B[Use Semantic Events] A --> C[Avoid Inline Arrow Functions] A --> D[Prevent Default When Needed] A --> E[Clean Up Event Listeners] A --> F[Consider Performance] B --> G[Use onChange for forms] C --> H[Use useCallback for stability] D --> I[e.preventDefault() for forms] E --> J[Remove listeners in useEffect cleanup] F --> K[Event delegation for lists] style A fill:#9cf,stroke:#333,stroke-width:2px

Practice Exercises

Exercise 1: Image Gallery

Create an image gallery with keyboard navigation:


function ImageGallery({ images }) {
    // Implement:
    // - Click to select image
    // - Arrow keys to navigate
    // - Escape to close
    // - Preload adjacent images
}
            

Exercise 2: Form with Validation

Build a form with real-time validation:


function ValidatedForm() {
    // Implement:
    // - Real-time validation on blur
    // - Show error messages
    // - Prevent submission if invalid
    // - Clear errors on valid input
}
            

Exercise 3: Custom Dropdown

Create a custom dropdown component:


function CustomDropdown({ options, onSelect }) {
    // Implement:
    // - Click to open/close
    // - Click outside to close
    // - Keyboard navigation
    // - Selection with Enter/Space
}
            

Key Takeaways

What's Next?

In our next lesson, we'll dive into React's SyntheticEvent system and learn more about event propagation patterns!

Homework