Skip to main content

Adding Interactivity

React lets you add event handlers to your JSX. Event handlers are functions that execute in response to user interactions like clicking, hovering, or typing.

Event Handlers

To add an event handler, define a function and pass it as a prop to the appropriate JSX element:
function Button() {
  function handleClick() {
    alert('Button clicked!');
  }

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}
Common mistake: Don’t call the function when passing it!
  • ✅ Correct: onClick={handleClick}
  • ❌ Wrong: onClick={handleClick()} (calls immediately)

Inline Event Handlers

For simple handlers, you can define them inline:
function Button() {
  return (
    <button onClick={() => alert('Clicked!')}>
      Click me
    </button>
  );
}

Common Event Types

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

Click Events

function ClickExample() {
  const handleClick = () => {
    console.log('Single click');
  };

  const handleDoubleClick = () => {
    console.log('Double click');
  };

  return (
    <div>
      <button onClick={handleClick}>Single Click</button>
      <button onDoubleClick={handleDoubleClick}>Double Click</button>
    </div>
  );
}

Input Events

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

  const handleFocus = () => {
    console.log('Input focused');
  };

  const handleBlur = () => {
    console.log('Input blurred');
  };

  return (
    <input
      type="text"
      onChange={handleChange}
      onFocus={handleFocus}
      onBlur={handleBlur}
      placeholder="Type something..."
    />
  );
}

Mouse Events

function MouseExample() {
  return (
    <div
      onMouseEnter={() => console.log('Mouse entered')}
      onMouseLeave={() => console.log('Mouse left')}
      onMouseMove={(e) => console.log(`Position: ${e.clientX}, ${e.clientY}`)}
      style={{ padding: '20px', border: '1px solid black' }}
    >
      Hover over me!
    </div>
  );
}

Keyboard Events

function KeyboardExample() {
  const handleKeyDown = (event) => {
    if (event.key === 'Enter') {
      console.log('Enter pressed!');
    }
    if (event.key === 'Escape') {
      console.log('Escape pressed!');
    }
  };

  return (
    <input
      type="text"
      onKeyDown={handleKeyDown}
      placeholder="Press Enter or Escape"
    />
  );
}

The Event Object

Event handlers receive an event object as their first argument:
function EventObjectExample() {
  const handleClick = (event) => {
    console.log('Event type:', event.type);
    console.log('Target element:', event.target);
    console.log('Click coordinates:', event.clientX, event.clientY);
  };

  const handleSubmit = (event) => {
    event.preventDefault(); // Prevent form submission
    console.log('Form submitted!');
  };

  return (
    <form onSubmit={handleSubmit}>
      <button type="button" onClick={handleClick}>
        Click for event info
      </button>
      <button type="submit">Submit</button>
    </form>
  );
}

Useful Event Properties

  • event.target - The element that triggered the event
  • event.currentTarget - The element the handler is attached to
  • event.type - The type of event (“click”, “change”, etc.)
  • event.key - The key pressed (for keyboard events)
  • event.clientX / event.clientY - Mouse position

Preventing Default Behavior

Use event.preventDefault() to stop the browser’s default action:
function Form() {
  const handleSubmit = (event) => {
    event.preventDefault(); // Don't reload the page
    const formData = new FormData(event.target);
    console.log('Email:', formData.get('email'));
  };

  const handleLinkClick = (event) => {
    event.preventDefault(); // Don't navigate
    console.log('Link clicked, but not navigating');
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input name="email" type="email" required />
        <button type="submit">Subscribe</button>
      </form>
      
      <a href="https://example.com" onClick={handleLinkClick}>
        Click me (won't navigate)
      </a>
    </div>
  );
}
When to use preventDefault:
  • Form submissions (to handle with JavaScript)
  • Link clicks (for client-side routing)
  • Context menu events (to show custom menus)
  • Drag and drop operations

Event Propagation

Events “bubble up” from child to parent elements:
function PropagationExample() {
  const handleParentClick = () => {
    console.log('Parent clicked');
  };

  const handleChildClick = (event) => {
    console.log('Child clicked');
    // event.stopPropagation(); // Uncomment to stop bubbling
  };

  return (
    <div 
      onClick={handleParentClick}
      style={{ padding: '40px', background: 'lightblue' }}
    >
      Parent
      <button 
        onClick={handleChildClick}
        style={{ margin: '20px' }}
      >
        Child (clicking triggers both handlers)
      </button>
    </div>
  );
}

Stopping Propagation

Use event.stopPropagation() to prevent the event from bubbling:
function StopPropagationExample() {
  return (
    <div onClick={() => alert('Outer div')}>
      <button onClick={(e) => {
        e.stopPropagation();
        alert('Button only');
      }}>
        Click me (won't trigger outer div)
      </button>
    </div>
  );
}

Passing Arguments to Event Handlers

There are two ways to pass additional arguments:

Using Arrow Functions

function TodoList() {
  const handleDelete = (id) => {
    console.log('Deleting todo:', id);
  };

  const todos = [
    { id: 1, text: 'Learn React' },
    { id: 2, text: 'Build a project' },
  ];

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.text}
          <button onClick={() => handleDelete(todo.id)}>
            Delete
          </button>
        </li>
      ))}
    </ul>
  );
}

Using bind

function TodoList() {
  const handleDelete = (id, event) => {
    console.log('Deleting todo:', id);
    console.log('Event:', event);
  };

  const todos = [
    { id: 1, text: 'Learn React' },
    { id: 2, text: 'Build a project' },
  ];

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.text}
          <button onClick={handleDelete.bind(null, todo.id)}>
            Delete
          </button>
        </li>
      ))}
    </ul>
  );
}

Complete Interactive Example

Here’s a complete example with multiple event types:
function InteractiveCard() {
  const handleLike = () => {
    alert('Post liked!');
  };

  const handleShare = (platform) => {
    alert(`Sharing on ${platform}`);
  };

  const handleComment = (event) => {
    event.preventDefault();
    const comment = event.target.elements.comment.value;
    if (comment.trim()) {
      alert(`Comment added: ${comment}`);
      event.target.reset();
    }
  };

  const handleImageError = (event) => {
    event.target.src = '/placeholder.jpg';
  };

  return (
    <article className="card">
      <img 
        src="/post-image.jpg" 
        alt="Post"
        onError={handleImageError}
      />
      
      <div className="actions">
        <button onClick={handleLike}>❤️ Like</button>
        <button onClick={() => handleShare('Twitter')}>Share on Twitter</button>
        <button onClick={() => handleShare('Facebook')}>Share on Facebook</button>
      </div>

      <form onSubmit={handleComment}>
        <input 
          name="comment"
          type="text" 
          placeholder="Write a comment..."
          onFocus={() => console.log('Comment input focused')}
        />
        <button type="submit">Post Comment</button>
      </form>
    </article>
  );
}

Best Practices

1
Name handlers clearly
2
Use descriptive names that start with “handle”:
3
// Good
handleSubmit, handleClick, handleUserLogout

// Avoid
click, submit, fn
4
Keep handlers focused
5
Each handler should do one thing:
6
// Good - separate concerns
const handleValidation = (data) => { /* validate */ };
const handleSubmit = (event) => {
  event.preventDefault();
  const data = new FormData(event.target);
  if (handleValidation(data)) {
    // submit
  }
};

// Avoid - doing too much in one handler
7
Extract complex handlers
8
Move complex logic outside the component:
9
// utils/eventHandlers.js
export function createSubmitHandler(onSuccess) {
  return (event) => {
    event.preventDefault();
    // complex logic
    onSuccess();
  };
}

Next Steps

Event handlers often need to update the UI. Learn how to manage changing data with state.