Skip to content

TypeScript React Events — Complete Guide

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about TypeScript React Events. We cover key concepts, practical examples, and best practices to help you master this topic.

React event handling with TypeScript gives you typed event objects, preventing access to non-existent properties and ensuring your event handlers only receive the events they can handle.

What You'll Learn

  • Typed event handler functions
  • ChangeEvent, MouseEvent, KeyboardEvent, FormEvent
  • Form handling with typed inputs
  • Custom event patterns

Why It Matters

Event objects in React are synthetic wrappers around native events. Without types, you might access e.target.value on a button click (buttons don't have value). TypeScript catches these mistakes at compile time.

Real-World Use

The Doda Browser's settings page has complex forms with typed event handlers. An input change handler typed as ChangeEvent<HTMLInputElement> guarantees access to e.target.value, while a form submit handler typed as FormEvent<HTMLFormElement> ensures e.preventDefault() is available.

Learning Path

flowchart LR
  A[React Hooks] --> B[React Events]
  B --> C[React Context]
  B --> D[You Are Here]
  C --> E[State Management]
  E --> F[React Advanced]

Basic Event Handler Types

import { ChangeEvent, FormEvent, MouseEvent, KeyboardEvent } from 'react';

Mouse Events

function handleClick(e: MouseEvent<HTMLButtonElement>): void {
  console.log(e.clientX, e.clientY); // Mouse coordinates
  // e.target.value; // Error — buttons don't have value
}

<button onClick={handleClick}>Click me</button>

Change Events

function handleInputChange(e: ChangeEvent<HTMLInputElement>): void {
  console.log(e.target.value); // string
}

function handleSelectChange(e: ChangeEvent<HTMLSelectElement>): void {
  console.log(e.target.value); // string
}

<input type="text" onChange={handleInputChange} />
<select onChange={handleSelectChange}>
  <option value="a">A</option>
</select>

Form Events

function handleSubmit(e: FormEvent<HTMLFormElement>): void {
  e.preventDefault();
  const formData = new FormData(e.currentTarget);
  console.log(Object.fromEntries(formData));
}

<form onSubmit={handleSubmit}>
  <input name="email" type="email" />
  <button type="submit">Submit</button>
</form>

Keyboard Events

function handleKeyDown(e: KeyboardEvent<HTMLInputElement>): void {
  if (e.key === 'Enter') {
    console.log('Submitted via Enter');
  }
  if (e.key === 'Escape') {
    console.log('Cancelled');
  }
}

<input onKeyDown={handleKeyDown} />

Event Handler Type Syntax

Two ways to type inline event handlers:

// Option 1: Inline parameter type
<input onChange={(e: ChangeEvent<HTMLInputElement>) => {
  setValue(e.target.value);
}} />

// Option 2: Separated function
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
  setValue(e.target.value);
};
<input onChange={handleChange} />

Typed Forms

interface FormData {
  email: string;
  password: string;
  remember: boolean;
}

function LoginForm() {
  const [formData, setFormData] = useState<FormData>({
    email: '',
    password: '',
    remember: false,
  });

  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    const { name, value, type, checked } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value,
    }));
  };

  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    console.log('Login:', formData.email, formData.password);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" value={formData.email} onChange={handleChange} />
      <input name="password" type="password" value={formData.password} onChange={handleChange} />
      <input name="remember" type="checkbox" checked={formData.remember} onChange={handleChange} />
      <button type="submit">Login</button>
    </form>
  );
}

Custom Events

For event emitters or custom components:

interface TabChangeEvent {
  tabId: string;
  previousTab: string;
}

interface TabsProps {
  tabs: { id: string; label: string }[];
  activeTab: string;
  onTabChange: (event: TabChangeEvent) => void;
}

function Tabs({ tabs, activeTab, onTabChange }: TabsProps) {
  return (
    <div className="tabs">
      {tabs.map(tab => (
        <button
          key={tab.id}
          className={tab.id === activeTab ? 'active' : ''}
          onClick={() => onTabChange({
            tabId: tab.id,
            previousTab: activeTab,
          })}
        >
          {tab.label}
        </button>
      ))}
    </div>
  );
}

// Usage
<Tabs
  tabs={[{ id: 'scan', label: 'Scan' }, { id: 'results', label: 'Results' }]}
  activeTab="scan"
  onTabChange={(e) => console.log(`Switched from ${e.previousTab} to ${e.tabId}`)}
/>

Event Type Reference

// Common event types
MouseEvent<HTMLElement>     // onClick, onMouseEnter, onMouseLeave
ChangeEvent<HTMLInputElement> // onChange for inputs, selects, textareas
FormEvent<HTMLFormElement>    // onSubmit, onReset
KeyboardEvent<HTMLElement>    // onKeyDown, onKeyUp, onKeyPress
FocusEvent<HTMLElement>       // onFocus, onBlur
DragEvent<HTMLElement>        // onDrag, onDrop
WheelEvent<HTMLElement>       // onWheel
TouchEvent<HTMLElement>       // onTouchStart, onTouchEnd

Common Mistakes

1. Using any for Event Type

// Bad — no autocompletion or safety
const handleChange = (e: any) => { setValue(e.target.value); };

// Good — typed
const handleChange = (e: ChangeEvent<HTMLInputElement>) => { setValue(e.target.value); };

2. Confusing target and currentTarget

  • e.target — the element that triggered the event (may be a child)
  • e.currentTarget — the element the handler is attached to

3. Not Using e.preventDefault() in Form Submit

Forgetting it causes page reload and state loss.

4. Wrong Generic Parameter on Event

// Wrong — div doesn't have value
<div onClick={(e: MouseEvent<HTMLInputElement>) => {}} />
// Correct — button
<button onClick={(e: MouseEvent<HTMLButtonElement>) => {}} />

5. Not Handling Checkbox Inputs Correctly

Checkboxes use checked not value. Always check the type property.

Practice Questions

  1. What is the type of a button click handler parameter? MouseEvent<HTMLButtonElement>

  2. What is the difference between e.target and e.currentTarget? target is the element that triggered the event (may be a child). currentTarget is the element the handler is on.

  3. How do you handle a checkbox input in TypeScript? Check e.target.type === 'checkbox' and use e.target.checked.

  4. What event type should you use for form submission? FormEvent<HTMLFormElement>.

Challenge: Build a multi-step form with typed event handlers. Each step has different input types (text, email, select, checkbox). Use a discriminated union to track the current step and form data.

FAQ

Are React SyntheticEvents the same as native DOM events?

React wraps native events in SyntheticEvent objects for cross-browser consistency. The TypeScript types reflect the synthetic wrapper.

How do I type `onChange` for a custom component?

Define a custom event interface and use it in your component's props: onValueChange: (value: string) => void.

What is `ChangeEvent` vs `ChangeEvent`?

They distinguish which element's properties are available. Textarea elements have different properties.

Can I use native DOM event types in React?

You can but it's not recommended. React's synthetic events have the same interface but work with React's event system.

How do I access `data-*` attributes from an event?

Use e.currentTarget.getAttribute('data-myattr') or the dataset property: e.currentTarget.dataset.myattr.

Mini Project: Typed Search Form

interface SearchFormData {
  query: string;
  category: string;
  dateFrom: string;
  dateTo: string;
  exactMatch: boolean;
}

function SearchForm() {
  const [form, setForm] = useState<SearchFormData>({
    query: '', category: '', dateFrom: '', dateTo: '', exactMatch: false,
  });

  const handleInput = (e: ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
    const { name, value, type } = e.target;
    const checked = type === 'checkbox' ? (e.target as HTMLInputElement).checked : undefined;
    setForm(prev => ({ ...prev, [name]: checked ?? value }));
  };

  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    console.log('Search:', form);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="query" value={form.query} onChange={handleInput} placeholder="Search..." />
      <select name="category" value={form.category} onChange={handleInput}>
        <option value="">All</option>
        <option value="malware">Malware</option>
        <option value="phishing">Phishing</option>
      </select>
      <label>
        <input name="exactMatch" type="checkbox" checked={form.exactMatch} onChange={handleInput} />
        Exact match
      </label>
      <button type="submit">Search</button>
    </form>
  );
}

What's Next

Now explore React Context with TypeScript:

Lesson Description
{{< ref "/programming-languages/typescript/38-react-hooks" >}} Review hooks
{{< ref "/programming-languages/typescript/40-react-context" >}} Typed context and providers
{{< ref "/programming-languages/typescript/41-react-state-management" >}} Zustand, Redux Toolkit

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro