React 7,897 's synthetic events (The Synthetic Event System) have their own generic types, parameterized by the element the handler is attached to. Write the handler inline and you never meet them — onChange={e => ...} infers e from the <input> it sits on. Pull it into a named function and inference is gone, leaving error TS7006: Parameter 'e' implicitly has an 'any' type. Annotate the parameter or, better, the whole function with the matching handler type.
| Handler prop | Event type | Handler alias |
|---|---|---|
| onChange, onInput | ChangeEvent<T> | ChangeEventHandler<T> |
| onSubmit, onReset | FormEvent<T> | FormEventHandler<T> |
| onClick, onMouseOver | MouseEvent<T> | MouseEventHandler<T> |
| onKeyDown, onKeyUp | KeyboardEvent<T> | KeyboardEventHandler<T> |
import { useRef, useState } from 'react';
import type { ChangeEventHandler, FormEvent, Ref } from 'react';
function SearchBox({ ref }: { ref?: Ref<HTMLInputElement> }) {
const [term, setTerm] = useState('');
const onChange: ChangeEventHandler<HTMLInputElement> = e => setTerm(e.target.value);
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log(new FormData(e.currentTarget).get('q'));
};
return <form onSubmit={onSubmit}>
<input name="q" ref={ref} value={term} onChange={onChange} />
<button onClick={e => e.currentTarget.blur()}>Go</button>
</form>;
}
export function Page() {
const input = useRef<HTMLInputElement>(null);
const timer = useRef<number | null>(null);
return <><SearchBox ref={input} />
<button onClick={() => input.current?.focus()}>Focus</button></>;
}The element parameter earns its keep. e.currentTarget is typed as the element you named, so currentTarget.blur() on a MouseEvent<HTMLButtonElement> is valid while currentTarget.value is not, and new FormData(e.currentTarget) compiles only because the form event named HTMLFormElement. e.target is whichever element fired, typed loosely for most events; ChangeEvent<T> is the exception that types target as T, so e.target.value above is a string.
Since React 19, ref is an ordinary prop, typed Ref<T> — no forwardRef, no ForwardedRef. On the receiving side useRef<HTMLInputElement>(null) produces a RefObject<HTMLInputElement | null>: the null belongs to the type because the ref holds nothing until React attaches the node after the first commit, and strict makes you acknowledge it (error TS18047: 'input.current' is possibly 'null'). The optional chain is not busywork: the ref is null on the first render and after unmount. For a mutable box, declare the union yourself, as timer does.