React onBlur Event Type in TypeScript

4 min read.
Tags: ReactTypeScript

The TypeScript type for a React onBlur event is React.FocusEvent<T>, where T is the element that has the handler. For an input, that's React.FocusEvent<HTMLInputElement>.

Quick answer

const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
  console.log(e.target.value)
}

<input onBlur={handleBlur} />

Or type the whole function with the handler type:

const handleBlur: React.FocusEventHandler<HTMLInputElement> = (e) => {
  console.log(e.target.value)
}

Both compile the same. The second is handy when you pass the handler around as a prop, because onBlur?: React.FocusEventHandler<HTMLInputElement> reads better than spelling out the function signature.

onFocus uses the same types.

You don't need a type for inline handlers

If you write the handler inline, TypeScript infers the event from the element:

<input onBlur={(e) => console.log(e.target.value)} />

e is already React.FocusEvent<HTMLInputElement> here. You only need the explicit type when the handler is a separate function.

Pick the right element type

The generic decides what e.target is. Get it wrong and .value disappears:

const handleBlur = (e: React.FocusEvent) => {
  console.log(e.target.value)
}
error TS2339: Property 'value' does not exist on type 'EventTarget & Element'.

Without a generic, T defaults to Element, which has no value. The same thing happens with React.FocusEvent<HTMLDivElement>, because a div has no value either.

Use HTMLTextAreaElement for a textarea and HTMLSelectElement for a select. If one handler serves several fields, use a union:

const handleBlur = (
  e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
) => {
  validateField(e.target.name, e.target.value)
}

<input name="email" onBlur={handleBlur} />
<textarea name="bio" onBlur={handleBlur} />
<select name="country" onBlur={handleBlur} />

name and value exist on all three, so the union compiles.

A wrong event type can still compile

You'd expect TypeScript to reject a ChangeEvent handler on onBlur. It doesn't:

const handleBlur = (e: React.ChangeEvent<HTMLInputElement>) => {
  console.log(e.target.value)
}

<input onBlur={handleBlur} /> // no error

With @types/react 19.3 and strict: true, this compiles cleanly. ChangeEvent<T> only adds a target typed as T on top of the base synthetic event, and FocusEvent<T> has that same target plus relatedTarget. A function that accepts the smaller type can safely receive the bigger one, so TypeScript allows it. It works at runtime too, since you only read target.value. Use FocusEvent anyway: it's the type React actually passes, and it gives you relatedTarget.

Don't use the DOM FocusEvent

Writing FocusEvent without the React. prefix gives you the browser's native event type, and that one does get rejected:

const handleBlur = (e: FocusEvent) => {}

<input onBlur={handleBlur} />
error TS2322: Type '(e: FocusEvent) => void' is not assignable to type 'FocusEventHandler<HTMLInputElement>'.
  Types of parameters 'e' and 'event' are incompatible.

React passes its own synthetic event, not the native one. Use React.FocusEvent, or import type { FocusEvent } from 'react', so the name doesn't shadow the global.

relatedTarget: where focus went

e.relatedTarget is the element that receives focus next, or null if focus left the page or went to nothing focusable. It's typed as (EventTarget & Element) | null.

React's onBlur bubbles, unlike the browser's native blur event, so you can put it on a container. Combined with relatedTarget, that tells you when focus leaves a whole group of inputs:

function AddressFields() {
  const handleBlur = (e: React.FocusEvent<HTMLDivElement>) => {
    if (!e.currentTarget.contains(e.relatedTarget)) {
      saveDraft()
    }
  }

  return (
    <div onBlur={handleBlur}>
      <input name="street" />
      <input name="city" />
    </div>
  )
}

Moving between the two inputs doesn't call saveDraft. Tabbing out of the group does. contains(null) returns false, so clicking on empty page space counts as leaving too.

Inside this handler, e.currentTarget is the div and e.target is whichever input lost focus.

Common mistakes

Leaving out the generic. React.FocusEvent alone types target as a plain Element, so .value is an error.

Using MouseEvent or ChangeEvent. MouseEvent fails to compile. ChangeEvent compiles, as shown above, but hides relatedTarget.

Typing the native FocusEvent. Without the React. prefix you get the DOM type, which doesn't match what React passes.

Conclusion

Type onBlur handlers as React.FocusEvent<HTMLInputElement>, or swap the element type for textareas, selects, and containers. Use React.FocusEventHandler<T> when you type the handler as a prop, and read relatedTarget when you need to know where focus went.