javascriptreactjstypescriptmaterial-uiselection-api

Property 'selectionStart' does not exist on type 'EventTarget'


I am using selectionStart and selectionEnd in order to get the starting and ending point of a text selection.

Code: https://codesandbox.io/s/busy-gareth-mr04o

However, I am struggling to defined the type of the event on which they can be called on. If I use any, the code works properly but I would prefer to know the right event.

I tried with these types: Element React.SyntheticEvent<HTMLDivElement> <HTMLDivElement> with no luck

export default function App() {
  const [startText, setStartText] = useState<number | undefined>();
  const [endText, setEndText] = useState<number | undefined>();

  const handleOnSelect = (event: any) => { <--- I CANNOT FIND THE RIGHT EVENT TYPE
    setStartText(event.target.selectionStart);
    setEndText(event.target.selectionEnd);
  };

  return (
    <Grid container direction="column" className="App">
      You can type here below:
      <TextField
        value={"This is a example, select a word from this string"}
        onSelect={(event) => handleOnSelect(event)}
      />
      <br />
      <Grid item>The selected word starts at character: {startText}</Grid>
      <Grid item>The selected word ends at character: {endText}</Grid>
    </Grid>
  );
}

Solution

  • This is a tricky one because the material-ui TextField component involves multiple nested nodes. The argument that is passed to the onSelect function is a div. However the event itself occurs on an input inside the div.

    const handleOnSelect = (event: React.SyntheticEvent<HTMLDivElement, Event>) => {
        console.log(event.target, event.currentTarget);
    };
    

    This logs the input and then the div.

    Using event.currentTarget gets very specific Typescript information. We know that it is an HTMLDivElement. But the div doesn't have the properties selectionStart and selectionEnd that we want to access. Those exist on the input.

    event.target gives us a very vague type for an EventTarget. We don't know that the target is an input.

    One option is to verify the element at runtime.

    const handleOnSelect = (event: React.SyntheticEvent<HTMLDivElement, Event>) => {
        if ( event.target instanceof HTMLInputElement ) {
            setStartText(event.target.selectionStart);
            setEndText(event.target.selectionEnd);
        }
    };
    

    Since you know that the event will always occur on an HTMLInputElement, I think it's safe to make an assertion.

    const handleOnSelect = (event: React.SyntheticEvent<HTMLDivElement, Event>) => {
        const target = event.target as HTMLInputElement;
        setStartText(target.selectionStart);
        setEndText(target.selectionEnd);
    };
    

    Note that selectionStart and selectionEnd properties use null instead of undefined. So you'll want to either change your state type to <number | null> or replace null with undefined by using null coalescing event.target.selectionStart ?? undefined.