Question

React Event Types in TypeScript: onChange, onSubmit, and target

typescriptjavascriptreactjs

Question

What are the correct TypeScript types for React events in a form? I want to remove any from this component.

In particular, React.SyntheticEvent<EventTarget> does not allow access to e.target.name or e.target.value, because EventTarget does not guarantee that those properties exist.

export interface LoginProps {
  login: {
    [key: string]: string | Function
    uname: string
    passw: string
    logIn: Function
  }
}

@inject('login')
@observer
export class Login extends Component<LoginProps, {}> {
  update = (e: React.SyntheticEvent<EventTarget>): void => {
    this.props.login[e.target.name] = e.target.value
  }

  submit = (e: any): void => {
    this.props.login.logIn()
    e.preventDefault()
  }

  render() {
    const { uname, passw } = this.props.login

    return (
      <div id="login">
        <form>
          <input
            placeholder="Username"
            type="text"
            name="uname"
            value={uname}
            onChange={this.update}
          />
          <input
            placeholder="Password"
            type="password"
            name="passw"
            value={passw}
            onChange={this.update}
          />
          <button type="submit" onClick={this.submit}>
            Submit
          </button>
        </form>
      </div>
    )
  }
}

Which event types should be used for the input change handler and form submission handler, and how can event properties be accessed safely?

Short Answer

React gives each JSX event a specific TypeScript event type. Use React.ChangeEvent<HTMLInputElement> when an input changes and React.FormEvent<HTMLFormElement> when a form is submitted. These types tell TypeScript which DOM element produced the event, so properties such as value and name are available safely.

Concept

React event handlers receive SyntheticEvent objects. A synthetic event is React's cross-browser wrapper around a native browser event.

The important part is that an event has an associated element type:

  • An <input> change handler works with an HTMLInputElement.
  • A <form> submit handler works with an HTMLFormElement.
  • A <button> click handler works with an HTMLButtonElement.

EventTarget is deliberately broad. It represents any possible event source, including objects that do not have name or value. Therefore, TypeScript correctly rejects this:

const target: EventTarget = event.target
target.value // Error: EventTarget does not guarantee a value property

For form controls, use the specific React event type. In an input change handler, React.ChangeEvent<HTMLInputElement> makes the input's value, name, checked, and other input properties available.

For submitting a form, prefer putting the handler on the <form onSubmit={...}> element rather than handling a button click. This supports clicking the button, pressing Enter in an input, and other normal browser form behavior.

Mental Model

Think of EventTarget as a package delivered from an unknown sender. You cannot assume the sender has a name or value.

HTMLInputElement is a package clearly labelled input. Since TypeScript knows it came from an input, it lets you read input-specific details such as value and name.

An event type such as React.ChangeEvent<HTMLInputElement> is that label: it says, “this is a change event from an input element.”

Take Quiz

Syntax and Examples

Use the React event type that matches both the event and the element.

function LoginFields() {
  const update = (event: React.ChangeEvent<HTMLInputElement>) => {
    console.log(event.currentTarget.name)
    console.log(event.currentTarget.value)
  }

  const submit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault()
    console.log('Form submitted')
  }

  return (
    <form onSubmit={submit}>
      <input name="uname" onChange={update} />
      <input name="passw" type="password" onChange={update} />
      <button type="submit">Submit</button>
    </form>
  )
}

event.currentTarget is usually the clearest choice in a handler:

  • currentTarget is the element whose JSX prop owns the handler.
  • target is the element where the event started. It can be a nested element when events bubble.

For an onChange handler directly attached to an <input>, both commonly refer to the input, but currentTarget communicates the safer intention.

Common React event types include:

React.ChangeEvent<HTMLInputElement>
React.FormEvent<HTMLFormElement>
React.MouseEvent<HTMLButtonElement>
React.KeyboardEvent<HTMLInputElement>
React.FocusEvent<HTMLInputElement>
React.ClipboardEvent<HTMLInputElement>

Step by Step Execution

Consider this controlled input:

const update = (event: React.ChangeEvent<HTMLInputElement>) => {
  const field = event.currentTarget.name
  const value = event.currentTarget.value
  console.log(field, value)
}

<input name="uname" value="ada" onChange={update} />

When a user changes ada to adam:

  1. The browser detects the input value change.
  2. React calls update with a change event.
  3. The event type says currentTarget is an HTMLInputElement.
  4. event.currentTarget.name evaluates to "uname".
  5. event.currentTarget.value evaluates to "adam".
  6. The handler can use those values to update the matching piece of state.

For form submission:

const submit = (event: React.FormEvent<HTMLFormElement>) => {
  event.preventDefault()
  // Send login data here.
}
  1. The user clicks a submit button or presses Enter.
  2. The form emits a submit event.
  3. React calls submit.
  4. preventDefault() stops the browser's normal page navigation/reload.
  5. Your application can validate and send the form data.

Real World Use Cases

Typed React events are useful wherever user interaction produces data.

  • Login forms: Read a username or password with ChangeEvent<HTMLInputElement> and send it in a FormEvent<HTMLFormElement> handler.
  • Checkbox settings: Use ChangeEvent<HTMLInputElement> and read event.currentTarget.checked.
  • Search boxes: Use a typed change handler to update a query as the user types.
  • Keyboard shortcuts: Use KeyboardEvent<HTMLInputElement> or KeyboardEvent<HTMLDivElement> to inspect event.key.
  • File uploads: Use ChangeEvent<HTMLInputElement> and read event.currentTarget.files.
  • Buttons and menus: Use MouseEvent<HTMLButtonElement> when a click handler needs button-specific information.

Typing events prevents mistakes such as reading .checked from a text input incorrectly or reading .value from a form element.

Take Quiz

Real Codebase Usage

In production React code, developers usually combine typed handlers with controlled state, validation, and early returns.

A common form pattern is:

const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
  event.preventDefault()

  if (!username.trim() || !password) {
    setError('Username and password are required.')
    return
  }

  await login({ username, password })
}

For a small form, separate handlers are often the most type-safe and readable:

const onUsernameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  setUsername(event.currentTarget.value)
}

const onPasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  setPassword(event.currentTarget.value)
}

When multiple fields share one handler, developers constrain field names with a union type. This prevents accidentally updating an unknown key:

type LoginField = 'uname' | 'passw'

const update = (event: React.ChangeEvent<HTMLInputElement>) => {
  const field = event.currentTarget.name as LoginField
  const value = event.currentTarget.value

  setLogin(previous => ({ ...previous, [field]: value }))
}

In many codebases, inline handlers can avoid explicit event annotations because TypeScript infers them from JSX:

<input onChange={event => setUsername(event.currentTarget.value)} />

Add an explicit type when the handler is declared separately, reused, exported, or when inference is not available.

Common Mistakes

Using EventTarget for input values

This fails because EventTarget might not be an input:

const update = (event: React.SyntheticEvent<EventTarget>) => {
  console.log(event.target.value) // Error
}

Use a specific event type instead:

const update = (event: React.ChangeEvent<HTMLInputElement>) => {
  console.log(event.currentTarget.value)
}

Using any for submit events

const submit = (event: any) => {
  event.preventDefault()
}

This loses checking and autocomplete. Use:

const submit = (event: React.FormEvent<HTMLFormElement>) => {
  event.preventDefault()
}

Handling form submission only with onClick

<button type="submit" onClick={submit}>Submit</button>

This can miss submission paths such as pressing Enter. Put the handler on the form:

<form onSubmit={submit}>
  <button type="submit">Submit</button>
</form>

Using Function as a type

Function accepts nearly any callable value and does not describe its parameters or return value.

logIn: Function

Prefer a precise function type:

logIn: () => void

Allowing arbitrary string keys unnecessarily

An index signature such as [key: string]: string | Function allows unexpected properties and mixes incompatible kinds of values. Keep data and methods explicitly typed when possible.

Casting without checking

This silences TypeScript but can hide a real mismatch:

const value = (event.target as HTMLInputElement).value

Prefer correctly typing the handler. Use a cast only when the event truly may originate from multiple kinds of elements and you have verified the element at runtime.

Comparisons

SituationRecommended typeUseful properties
Text, password, email, or number input changesReact.ChangeEvent<HTMLInputElement>currentTarget.value, name, checked, files
Textarea changesReact.ChangeEvent<HTMLTextAreaElement>currentTarget.value, name
Select changesReact.ChangeEvent<HTMLSelectElement>currentTarget.value, selectedOptions
Form submissionReact.FormEvent<HTMLFormElement>preventDefault(), currentTarget
Button clickReact.MouseEvent<HTMLButtonElement>button, mouse coordinates, currentTarget
Keyboard inputReact.KeyboardEvent<HTMLInputElement>key, code, modifier keys
ChoiceWhen to use it
event.targetYou need the element where the event originally began. It may be a nested descendant.
event.currentTargetYou need the element that owns the handler. This is usually preferable for typed React handlers.
React.SyntheticEventYou only need generic event operations and do not need element-specific properties.
Specific React event typeYou need properties belonging to a particular element or event kind.
Take Quiz

Cheat Sheet

// Input value
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  const { name, value } = event.currentTarget
}

// Checkbox state
const onCheck = (event: React.ChangeEvent<HTMLInputElement>) => {
  const checked = event.currentTarget.checked
}

// Textarea
const onText = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
  const value = event.currentTarget.value
}

// Select
const onSelect = (event: React.ChangeEvent<HTMLSelectElement>) => {
  const value = event.currentTarget.value
}

// Form submit
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
  event.preventDefault()
}

// Button click
const onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
  console.log(event.currentTarget.disabled)
}

// Keyboard event
const onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
  if (event.key === 'Enter') {
    // ...
  }
}
  • Prefer event.currentTarget for the element that owns the handler.
  • Do not use EventTarget when you need .value, .name, or .checked.
  • Put submission logic on form onSubmit, not only on a submit button's onClick.
  • Prefer () => void over Function for known callbacks.

FAQ

What is the TypeScript type for React input onChange?

Use React.ChangeEvent<HTMLInputElement> for an <input> element.

Why does event.target.value cause an error in TypeScript?

The event may be typed with EventTarget, which is too general to guarantee a value property. Type the event as React.ChangeEvent<HTMLInputElement> and use event.currentTarget.value.

What is the React TypeScript type for form submission?

Use React.FormEvent<HTMLFormElement>. Call event.preventDefault() when handling the submission in JavaScript.

Should I use onClick or onSubmit for a form button?

Use onSubmit on the <form>. It handles both submit-button clicks and keyboard submission such as Enter.

Can TypeScript infer React event types automatically?

Yes, for inline JSX handlers. A separately declared handler usually needs an explicit annotation.

What type should I use for checkbox changes?

Use React.ChangeEvent<HTMLInputElement> and read event.currentTarget.checked rather than value.

Is React.SyntheticEvent ever useful?

Yes, when you only need generic event methods such as preventDefault() and do not need properties specific to an input, form, or button.

Why is currentTarget often safer than target?

currentTarget is the element that has the handler. target can be a nested child where the event began, especially for bubbling events.

Take Quiz

Mini Project

Description

Build a typed login form with username and password fields. The project demonstrates how one onChange handler can safely update multiple fields and how a form-level onSubmit handler prevents the browser's default submission behavior.

Goal

Create a login form that displays the submitted username without using any.

Requirements

Use React and TypeScript state for uname and passw. Use React.ChangeEvent<HTMLInputElement> for the shared input handler. Use React.FormEvent<HTMLFormElement> for form submission. Prevent the default browser form submission. Show a validation message when either field is empty.

Take Quiz

Keep learning

Related questions

View more