React: how to use event.data in onBeforeInput handler without TypeScript error

Viewed 1211

So the event received by an onBeforeInput handler is typed as React.FormEvent<HTMLInputElement>. This is quite a general type, and doesn't include the data property.

As far as I'm aware the events that onBeforeInput receives (nativeEvents being KeyboardEvent in Firefox, TextEvent in Chrome) will have the data property.

What's the right way to write a handler that uses event.data without TypeScript complaining that Property 'data' does not exist on type 'FormEvent<HTMLInputElement>'?

onBeforeInput={(e) => {
  handleInput(e.data);
  e.preventDefault();
}}
3 Answers

I just check the type definition and found this,

type of e is FormEvent<HTMLInputElement>,

Then the FormEvent is defined as,

interface FormEvent<T = Element> extends SyntheticEvent<T> {
}

The FormEvent is extended the SyntheticEvent, which defined as,

interface SyntheticEvent<T = Element, E = Event> extends BaseSyntheticEvent<E, EventTarget & T, EventTarget> {} 

And when I check the definition of BaseSyntheticEvent, I found this,

interface BaseSyntheticEvent<E = object, C = any, T = any> {
    nativeEvent: E;
    currentTarget: C;
    target: T;
    bubbles: boolean;
    cancelable: boolean;
    defaultPrevented: boolean;
    eventPhase: number;
    isTrusted: boolean;
    preventDefault(): void;
    isDefaultPrevented(): boolean;
    stopPropagation(): void;
    isPropagationStopped(): boolean;
    persist(): void;
    timeStamp: number;
    type: string;
}  

Here we don't have a property call data. Then I just saw that there is a type call, CompositionEvent which extends SyntheticEvent.

interface CompositionEvent<T = Element> extends SyntheticEvent<T, NativeCompositionEvent> {
    data: string;
}

And it has the field data;

So I did,

<input type="text" onBeforeInput={(e:SyntheticEvent) => { 
        let event = e as CompositionEvent; 
        console.log(event.data); 
}} /> 

Or,

interface CustomEvent extends SyntheticEvent {
  data ?: string
}
<input type="text" onBeforeInput={(event:CustomEvent) => { 
        console.log(event.data); 
}} /> 

The correct way to do this is:

onBeforeInput={(e: React.FormEvent<HTMLInputElement>) => {
  handleInput(e.nativeEvent.data);
  e.preventDefault();
}}

This is because react sends back a wrapper (SyntheticEvent) around the native event. This SyntheticEvent does not have a data property, so typescript is telling you the truth. Luckily react still provides a reference to the native event at nativeEvent. Keep in mind that typescript treats properties like data as any on the HTMLInputEvent so you'll need to be careful to handle all possible values that could exist there.

I've found you can also do the following and you'll have no type errors:

onBeforeInput={(event: React.CompositionEvent<HTMLInputElement>) => {
  const data = event.data
}}

If you want to see what the next value in the input would be with the next value you can do the following:

onBeforeInput={(event: React.CompositionEvent<HTMLInputElement>) => {
  const str = event.currentTarget.value
  const sub = event.data
  const posStart = event.currentTarget.selectionStart || 0
  const posEnd = event.currentTarget.selectionEnd || posStart

  const nextValue = `${str.slice(0, posStart)}${sub}${str.slice(posEnd)}`
}}
Related