dev.log / syntax diaries

Practical code notes, tools, and guided learning for developers.

Practical guides, developer tools, and tutorials for modern web developers, with the same focused tone across writing, utilities, and learning tracks.

BlogToolsTutorialsAboutContactAdmin Login
Privacy PolicyTerms of ServiceCookie Policy

© 2026 The Syntax Diaries · System_Operational

The Syntax Diaries logoThe Syntax Diaries
BlogToolsTutorialsAbout
build log live
Tutorial / React
State And Events25 minbeginner

Event Handling

Learn how to handle user interactions in React through event handlers and create responsive, interactive components.

On This Page

React's Event SystemBasic Event Handler SyntaxCommon Event Types1. Click Events2. Form Events - Input Changes3. Select and Checkbox Events4. Form Submission5. Keyboard Events6. Mouse EventsEvent Handler Patterns1. Event Handler with Parameters2. Event Delegation3. Debounced EventsBest Practices1. Prevent Default and Stop Propagation2. Accessible Event Handling3. Performance ConsiderationsCommon Event Handling Mistakes1. Calling Function Instead of Passing Reference2. Forgetting to Bind ContextSummaryWhat's Next?

Event Handling#

Event handling is how React components respond to user interactions. React uses a unified event system called SyntheticEvents that works consistently across all browsers and provides a clean API for handling user input.

React's Event System#

React wraps native DOM events in SyntheticEvent objects that:

  • Work consistently across all browsers
  • Have the same interface as native events
  • Provide additional features like event pooling
  • Include preventDefault() and stopPropagation()

Basic Event Handler Syntax#

function Button() {
  // Event handler function
  const handleClick = (event) => {
    console.log('Button clicked!');
    console.log('Event object:', event);
  };
  
  return (
    <button onClick={handleClick}>
      Click me!
    </button>
  );
}

// Inline event handler (for simple actions)
function SimpleButton() {
  return (
    <button onClick={() => console.log('Clicked!')}>
      Click me!
    </button>
  );
}

Common Event Types#

1. Click Events#

function ClickExample() {
  const [count, setCount] = useState(0);
  const [message, setMessage] = useState('');
  
  const handleClick = () => {
    setCount(count + 1);
    setMessage(`Button clicked \${count + 1} times!`);
  };
  
  const handleDoubleClick = () => {
    setMessage('Double clicked!');
    setCount(count + 2);
  };
  
  const handleRightClick = (event) => {
    event.preventDefault(); // Prevent context menu
    setMessage('Right clicked!');
  };
  
  return (
    <div>
      <h3>Click Events Demo</h3>
      <p>{message}</p>
      <p>Count: {count}</p>
      
      <button onClick={handleClick}>
        Single Click (+1)
      </button>
      
      <button onDoubleClick={handleDoubleClick}>
        Double Click (+2)
      </button>
      
      <button onContextMenu={handleRightClick}>
        Right Click (no menu)
      </button>
    </div>
  );
}

2. Form Events - Input Changes#

function InputExample() {
  const [text, setText] = useState('');
  const [email, setEmail] = useState('');
  const [number, setNumber] = useState(0);
  
  const handleTextChange = (event) => {
    setText(event.target.value);
  };
  
  const handleEmailChange = (event) => {
    setEmail(event.target.value);
  };
  
  const handleNumberChange = (event) => {
    setNumber(parseInt(event.target.value) || 0);
  };
  
  return (
    <div>
      <h3>Input Events Demo</h3>
      
      <div className="form-group">
        <label>Text Input:</label>
        <input
          type="text"
          value={text}
          onChange={handleTextChange}
          placeholder="Type something..."
        />
        <p>You typed: {text}</p>
        <p>Character count: {text.length}</p>
      </div>
      
      <div className="form-group">
        <label>Email Input:</label>
        <input
          type="email"
          value={email}
          onChange={handleEmailChange}
          placeholder="Enter email..."
        />
        <p>Email: {email}</p>
        <p>Valid email: {email.includes('@') ? 'Yes' : 'No'}</p>
      </div>
      
      <div className="form-group">
        <label>Number Input:</label>
        <input
          type="number"
          value={number}
          onChange={handleNumberChange}
        />
        <p>Number: {number}</p>
        <p>Double: {number * 2}</p>
      </div>
    </div>
  );
}

3. Select and Checkbox Events#

function FormControls() {
  const [selectedOption, setSelectedOption] = useState('');
  const [isChecked, setIsChecked] = useState(false);
  const [selectedCheckboxes, setSelectedCheckboxes] = useState([]);
  const [selectedRadio, setSelectedRadio] = useState('');
  
  const handleSelectChange = (event) => {
    setSelectedOption(event.target.value);
  };
  
  const handleCheckboxChange = (event) => {
    setIsChecked(event.target.checked);
  };
  
  const handleMultiCheckboxChange = (value) => {
    setSelectedCheckboxes(prev => {
      if (prev.includes(value)) {
        return prev.filter(item => item !== value);
      } else {
        return [...prev, value];
      }
    });
  };
  
  const handleRadioChange = (event) => {
    setSelectedRadio(event.target.value);
  };
  
  return (
    <div>
      <h3>Form Controls Demo</h3>
      
      {/* Select Dropdown */}
      <div className="form-group">
        <label>Choose a color:</label>
        <select value={selectedOption} onChange={handleSelectChange}>
          <option value="">-- Select a color --</option>
          <option value="red">Red</option>
          <option value="blue">Blue</option>
          <option value="green">Green</option>
          <option value="yellow">Yellow</option>
        </select>
        <p>Selected: {selectedOption}</p>
      </div>
      
      {/* Single Checkbox */}
      <div className="form-group">
        <label>
          <input
            type="checkbox"
            checked={isChecked}
            onChange={handleCheckboxChange}
          />
          Subscribe to newsletter
        </label>
        <p>Subscribed: {isChecked ? 'Yes' : 'No'}</p>
      </div>
      
      {/* Multiple Checkboxes */}
      <div className="form-group">
        <label>Select your interests:</label>
        {['React', 'JavaScript', 'CSS', 'Node.js'].map(interest => (
          <label key={interest}>
            <input
              type="checkbox"
              checked={selectedCheckboxes.includes(interest)}
              onChange={() => handleMultiCheckboxChange(interest)}
            />
            {interest}
          </label>
        ))}
        <p>Selected: {selectedCheckboxes.join(', ')}</p>
      </div>
      
      {/* Radio Buttons */}
      <div className="form-group">
        <label>Choose your experience level:</label>
        {['Beginner', 'Intermediate', 'Advanced'].map(level => (
          <label key={level}>
            <input
              type="radio"
              name="experience"
              value={level}
              checked={selectedRadio === level}
              onChange={handleRadioChange}
            />
            {level}
          </label>
        ))}
        <p>Experience: {selectedRadio}</p>
      </div>
    </div>
  );
}

4. Form Submission#

function ContactForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: '',
    newsletter: false
  });
  
  const [errors, setErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [submitMessage, setSubmitMessage] = useState('');
  
  const handleChange = (event) => {
    const { name, value, type, checked } = event.target;
    
    setFormData(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value
    }));
    
    // Clear error when user starts typing
    if (errors[name]) {
      setErrors(prev => ({
        ...prev,
        [name]: ''
      }));
    }
  };
  
  const validateForm = () => {
    const newErrors = {};
    
    if (!formData.name.trim()) {
      newErrors.name = 'Name is required';
    }
    
    if (!formData.email.trim()) {
      newErrors.email = 'Email is required';
    } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
      newErrors.email = 'Email is invalid';
    }
    
    if (!formData.message.trim()) {
      newErrors.message = 'Message is required';
    } else if (formData.message.length < 10) {
      newErrors.message = 'Message must be at least 10 characters';
    }
    
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };
  
  const handleSubmit = async (event) => {
    event.preventDefault(); // Prevent page reload
    
    if (!validateForm()) {
      return;
    }
    
    setIsSubmitting(true);
    setSubmitMessage('');
    
    try {
      // Simulate API call
      await new Promise(resolve => setTimeout(resolve, 2000));
      
      setSubmitMessage('Message sent successfully!');
      setFormData({
        name: '',
        email: '',
        message: '',
        newsletter: false
      });
    } catch (error) {
      setSubmitMessage('Error sending message. Please try again.');
    } finally {
      setIsSubmitting(false);
    }
  };
  
  return (
    <div>
      <h3>Contact Form</h3>
      
      <form onSubmit={handleSubmit}>
        <div className="form-group">
          <label>Name:</label>
          <input
            type="text"
            name="name"
            value={formData.name}
            onChange={handleChange}
            className={errors.name ? 'error' : ''}
          />
          {errors.name && <span className="error-message">{errors.name}</span>}
        </div>
        
        <div className="form-group">
          <label>Email:</label>
          <input
            type="email"
            name="email"
            value={formData.email}
            onChange={handleChange}
            className={errors.email ? 'error' : ''}
          />
          {errors.email && <span className="error-message">{errors.email}</span>}
        </div>
        
        <div className="form-group">
          <label>Message:</label>
          <textarea
            name="message"
            value={formData.message}
            onChange={handleChange}
            rows="4"
            className={errors.message ? 'error' : ''}
          />
          {errors.message && <span className="error-message">{errors.message}</span>}
        </div>
        
        <div className="form-group">
          <label>
            <input
              type="checkbox"
              name="newsletter"
              checked={formData.newsletter}
              onChange={handleChange}
            />
            Subscribe to newsletter
          </label>
        </div>
        
        <button 
          type="submit" 
          disabled={isSubmitting}
          className="submit-button"
        >
          {isSubmitting ? 'Sending...' : 'Send Message'}
        </button>
        
        {submitMessage && (
          <p className={submitMessage.includes('Error') ? 'error-message' : 'success-message'}>
            {submitMessage}
          </p>
        )}
      </form>
      
      {/* Debug info */}
      <div className="debug-info">
        <h4>Form Data:</h4>
        <pre>{JSON.stringify(formData, null, 2)}</pre>
      </div>
    </div>
  );
}

5. Keyboard Events#

function KeyboardExample() {
  const [pressedKey, setPressedKey] = useState('');
  const [inputValue, setInputValue] = useState('');
  const [keyHistory, setKeyHistory] = useState([]);
  
  const handleKeyDown = (event) => {
    setPressedKey(`Key Down: \${event.key}`);
    
    // Handle special keys
    if (event.key === 'Enter') {
      console.log('Enter pressed!');
    }
    
    if (event.key === 'Escape') {
      setInputValue('');
      console.log('Input cleared!');
    }
    
    // Handle key combinations
    if (event.ctrlKey && event.key === 's') {
      event.preventDefault();
      console.log('Save shortcut pressed!');
    }
  };
  
  const handleKeyUp = (event) => {
    setPressedKey(`Key Up: \${event.key}`);
  };
  
  const handleKeyPress = (event) => {
    // Note: keypress is deprecated, use keydown instead
    setKeyHistory(prev => [...prev.slice(-9), event.key]);
  };
  
  const handleInputKeyDown = (event) => {
    if (event.key === 'Enter') {
      alert(`You typed: \${inputValue}`);
    }
  };
  
  return (
    <div>
      <h3>Keyboard Events Demo</h3>
      
      <div className="keyboard-area">
        <p>Click here and press any key:</p>
        <div
          tabIndex="0" // Makes div focusable
          onKeyDown={handleKeyDown}
          onKeyUp={handleKeyUp}
          style={{
            border: '2px solid #ccc',
            padding: '20px',
            backgroundColor: '#f9f9f9',
            outline: 'none'
          }}
        >
          Focus this area and press keys
        </div>
        <p>{pressedKey}</p>
      </div>
      
      <div className="input-area">
        <label>Type and press Enter:</label>
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          onKeyDown={handleInputKeyDown}
          placeholder="Press Enter to alert"
        />
      </div>
      
      <div className="key-history">
        <h4>Last 10 keys pressed:</h4>
        <p>{keyHistory.join(' → ')}</p>
      </div>
      
      <div className="shortcuts">
        <h4>Try these shortcuts:</h4>
        <ul>
          <li>Ctrl+S: Save (prevented)</li>
          <li>Escape: Clear input</li>
          <li>Enter: Submit input</li>
        </ul>
      </div>
    </div>
  );
}

6. Mouse Events#

function MouseExample() {
  const [mouseInfo, setMouseInfo] = useState({
    x: 0,
    y: 0,
    isOver: false,
    isDown: false
  });
  
  const [clickCount, setClickCount] = useState(0);
  
  const handleMouseMove = (event) => {
    setMouseInfo(prev => ({
      ...prev,
      x: event.clientX,
      y: event.clientY
    }));
  };
  
  const handleMouseEnter = () => {
    setMouseInfo(prev => ({ ...prev, isOver: true }));
  };
  
  const handleMouseLeave = () => {
    setMouseInfo(prev => ({ ...prev, isOver: false, isDown: false }));
  };
  
  const handleMouseDown = () => {
    setMouseInfo(prev => ({ ...prev, isDown: true }));
  };
  
  const handleMouseUp = () => {
    setMouseInfo(prev => ({ ...prev, isDown: false }));
    setClickCount(prev => prev + 1);
  };
  
  const handleDragStart = (event) => {
    event.dataTransfer.setData('text/plain', 'Dragged item');
  };
  
  const handleDrop = (event) => {
    event.preventDefault();
    const data = event.dataTransfer.getData('text/plain');
    console.log('Dropped:', data);
  };
  
  const handleDragOver = (event) => {
    event.preventDefault(); // Allow drop
  };
  
  return (
    <div>
      <h3>Mouse Events Demo</h3>
      
      <div
        className="mouse-area"
        onMouseMove={handleMouseMove}
        onMouseEnter={handleMouseEnter}
        onMouseLeave={handleMouseLeave}
        onMouseDown={handleMouseDown}
        onMouseUp={handleMouseUp}
        style={{
          width: '300px',
          height: '200px',
          border: '2px solid #333',
          backgroundColor: mouseInfo.isOver ? '#e6f3ff' : '#f9f9f9',
          cursor: mouseInfo.isDown ? 'grabbing' : 'grab',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          userSelect: 'none'
        }}
      >
        <div>
          <p>Mouse Position: ({mouseInfo.x}, {mouseInfo.y})</p>
          <p>Mouse Over: {mouseInfo.isOver ? 'Yes' : 'No'}</p>
          <p>Mouse Down: {mouseInfo.isDown ? 'Yes' : 'No'}</p>
          <p>Click Count: {clickCount}</p>
        </div>
      </div>
      
      <div className="drag-drop-demo">
        <h4>Drag and Drop:</h4>
        <div
          draggable
          onDragStart={handleDragStart}
          style={{
            padding: '10px',
            backgroundColor: '#4CAF50',
            color: 'white',
            display: 'inline-block',
            cursor: 'grab',
            margin: '10px'
          }}
        >
          Drag me!
        </div>
        
        <div
          onDrop={handleDrop}
          onDragOver={handleDragOver}
          style={{
            width: '200px',
            height: '100px',
            border: '2px dashed #ccc',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            margin: '10px'
          }}
        >
          Drop zone
        </div>
      </div>
    </div>
  );
}

Event Handler Patterns#

1. Event Handler with Parameters#

function ButtonList() {
  const [selectedId, setSelectedId] = useState(null);
  
  const items = [
    { id: 1, name: 'Item 1', color: 'red' },
    { id: 2, name: 'Item 2', color: 'blue' },
    { id: 3, name: 'Item 3', color: 'green' }
  ];
  
  // Method 1: Arrow function in JSX
  const handleClick1 = (id, name) => {
    setSelectedId(id);
    console.log(`Clicked \${name}`);
  };
  
  // Method 2: Curried function
  const handleClick2 = (id, name) => (event) => {
    setSelectedId(id);
    console.log(`Clicked \${name}`, event);
  };
  
  // Method 3: Data attributes
  const handleClick3 = (event) => {
    const id = parseInt(event.target.dataset.id);
    const name = event.target.dataset.name;
    setSelectedId(id);
    console.log(`Clicked \${name}`);
  };
  
  return (
    <div>
      <h3>Event Handlers with Parameters</h3>
      <p>Selected ID: {selectedId}</p>
      
      <div>
        <h4>Method 1: Arrow function in JSX</h4>
        {items.map(item => (
          <button
            key={item.id}
            onClick={() => handleClick1(item.id, item.name)}
            style={{ backgroundColor: item.color, margin: '5px' }}
          >
            {item.name}
          </button>
        ))}
      </div>
      
      <div>
        <h4>Method 2: Curried function</h4>
        {items.map(item => (
          <button
            key={item.id}
            onClick={handleClick2(item.id, item.name)}
            style={{ backgroundColor: item.color, margin: '5px' }}
          >
            {item.name}
          </button>
        ))}
      </div>
      
      <div>
        <h4>Method 3: Data attributes</h4>
        {items.map(item => (
          <button
            key={item.id}
            data-id={item.id}
            data-name={item.name}
            onClick={handleClick3}
            style={{ backgroundColor: item.color, margin: '5px' }}
          >
            {item.name}
          </button>
        ))}
      </div>
    </div>
  );
}

2. Event Delegation#

function EventDelegation() {
  const [clickedItem, setClickedItem] = useState('');
  
  const handleContainerClick = (event) => {
    // Check if clicked element is a button
    if (event.target.tagName === 'BUTTON') {
      const action = event.target.dataset.action;
      const value = event.target.textContent;
      
      setClickedItem(`Action: \${action}, Value: \${value}`);
    }
  };
  
  return (
    <div>
      <h3>Event Delegation Example</h3>
      <p>Clicked: {clickedItem}</p>
      
      {/* Single event handler for multiple buttons */}
      <div onClick={handleContainerClick}>
        <button data-action="save">Save</button>
        <button data-action="delete">Delete</button>
        <button data-action="edit">Edit</button>
        <button data-action="cancel">Cancel</button>
      </div>
    </div>
  );
}

3. Debounced Events#

function SearchInput() {
  const [query, setQuery] = useState('');
  const [searchResults, setSearchResults] = useState([]);
  const [isSearching, setIsSearching] = useState(false);
  
  // Debounce search to avoid too many API calls
  useEffect(() => {
    if (!query.trim()) {
      setSearchResults([]);
      return;
    }
    
    setIsSearching(true);
    
    const timeoutId = setTimeout(async () => {
      try {
        // Simulate API call
        await new Promise(resolve => setTimeout(resolve, 500));
        
        // Mock search results
        const results = [
          `Result for "\${query}" #1`,
          `Result for "\${query}" #2`,
          `Result for "\${query}" #3`
        ];
        
        setSearchResults(results);
      } catch (error) {
        console.error('Search failed:', error);
      } finally {
        setIsSearching(false);
      }
    }, 300); // 300ms delay
    
    return () => clearTimeout(timeoutId);
  }, [query]);
  
  return (
    <div>
      <h3>Debounced Search</h3>
      
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
      />
      
      {isSearching && <p>Searching...</p>}
      
      <ul>
        {searchResults.map((result, index) => (
          <li key={index}>{result}</li>
        ))}
      </ul>
    </div>
  );
}

Best Practices#

1. Prevent Default and Stop Propagation#

function LinkExample() {
  const handleLinkClick = (event) => {
    event.preventDefault(); // Don't navigate
    console.log('Link clicked but navigation prevented');
  };
  
  const handleContainerClick = () => {
    console.log('Container clicked');
  };
  
  const handleButtonClick = (event) => {
    event.stopPropagation(); // Don't bubble to container
    console.log('Button clicked');
  };
  
  return (
    <div onClick={handleContainerClick} style={{ padding: '20px', border: '1px solid #ccc' }}>
      <p>Container (click anywhere)</p>
      
      <a href="https://example.com" onClick={handleLinkClick}>
        Prevented Link
      </a>
      
      <button onClick={handleButtonClick}>
        Button (stops propagation)
      </button>
    </div>
  );
}

2. Accessible Event Handling#

function AccessibleButton() {
  const [isPressed, setIsPressed] = useState(false);
  
  const handleActivation = () => {
    setIsPressed(!isPressed);
    console.log('Button activated');
  };
  
  const handleKeyDown = (event) => {
    // Handle Enter and Space keys for accessibility
    if (event.key === 'Enter' || event.key === ' ') {
      event.preventDefault();
      handleActivation();
    }
  };
  
  return (
    <div
      role="button"
      tabIndex="0"
      aria-pressed={isPressed}
      onClick={handleActivation}
      onKeyDown={handleKeyDown}
      style={{
        padding: '10px 20px',
        backgroundColor: isPressed ? '#007bff' : '#f8f9fa',
        color: isPressed ? 'white' : 'black',
        border: '1px solid #ccc',
        cursor: 'pointer',
        outline: 'none'
      }}
    >
      {isPressed ? 'Pressed' : 'Not Pressed'}
    </div>
  );
}

3. Performance Considerations#

function PerformantList() {
  const [items, setItems] = useState(
    Array.from({ length: 1000 }, (_, i) => ({ id: i, name: `Item \${i}` }))
  );
  
  // ❌ Bad - creates new function on every render
  const badExample = () => {
    return items.map(item => (
      <div key={item.id} onClick={() => console.log(item.id)}>
        {item.name}
      </div>
    ));
  };
  
  // ✅ Good - stable function reference
  const handleItemClick = useCallback((event) => {
    const id = parseInt(event.target.dataset.id);
    console.log('Clicked item:', id);
  }, []);
  
  return (
    <div onClick={handleItemClick}>
      {items.slice(0, 10).map(item => (
        <div key={item.id} data-id={item.id}>
          {item.name}
        </div>
      ))}
    </div>
  );
}

Common Event Handling Mistakes#

1. Calling Function Instead of Passing Reference#

// ❌ Wrong - calls function immediately
<button onClick={handleClick()}>Click me</button>

// ✅ Correct - passes function reference
<button onClick={handleClick}>Click me</button>

// ✅ Correct - arrow function for parameters
<button onClick={() => handleClick(id)}>Click me</button>

2. Forgetting to Bind Context#

// ❌ Problematic with class components
class MyComponent extends Component {
  handleClick() {
    console.log(this); // undefined in strict mode
  }
  
  render() {
    return <button onClick={this.handleClick}>Click</button>;
  }
}

// ✅ Solutions for class components
class MyComponent extends Component {
  // Method 1: Arrow function
  handleClick = () => {
    console.log(this); // correctly bound
  }
  
  // Method 2: Bind in constructor
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  
  // Method 3: Arrow function in JSX
  render() {
    return <button onClick={() => this.handleClick()}>Click</button>;
  }
}

Summary#

Event handling is essential for creating interactive React components:

✅ Use consistent event handler naming (handleEventName)
✅ Prevent default behavior when needed with preventDefault()
✅ Stop event bubbling with stopPropagation() when appropriate
✅ Handle keyboard events for accessibility
✅ Use controlled components for form inputs
✅ Consider performance with large lists and frequent events

What's Next?#

In the next lesson, we'll explore Conditional Rendering - how to show or hide content based on state and props. You'll learn:

  • Different conditional rendering techniques
  • Ternary operators vs logical AND
  • Switch statements for multiple conditions
  • Guard clauses and early returns
  • Complex conditional patterns

Conditional rendering lets you create dynamic UIs that respond to user interactions and application state!

Previous

Conditional Rendering

Next

Lists and Keys