119

I have figured out how to tie up an event handler on a SELECT element using an ugly cast of the event to any.

Is it possible to retrieve the value in a type-safe manner without casting to any?

import React = require('react');

interface ITestState {
    selectedValue: string;
}

export class Test extends React.Component<{}, ITestState> {

    constructor() {
        super();
        this.state = { selectedValue: "A" };
    }

    change(event: React.FormEvent) {
        console.log("Test.change");
        console.log(event.target); // in chrome => <select class="form-control" id="searchType" data-reactid=".0.0.0.0.3.1">...</select>

        // Use cast to any works but is not type safe
        var unsafeSearchTypeValue = ((event.target) as any).value;

        console.log(unsafeSearchTypeValue); // in chrome => B

        this.setState({
            selectedValue: unsafeSearchTypeValue
        });
    }

    render() {
        return (
            <div>
                <label htmlFor="searchType">Safe</label>
                <select className="form-control" id="searchType" onChange={ e => this.change(e) } value={ this.state.selectedValue }>
                    <option value="A">A</option>
                    <option value="B">B</option>
                </select>
                <h1>{this.state.selectedValue}</h1>
            </div>
        );
    }
}
2
  • 2
    what does it mean typesafe ? Commented Oct 21, 2015 at 10:09
  • 1
    I guess it means compiled by typescript and check that all variables assignement are allright from their type. We are talking about typescript, of course, not javascript Commented Oct 21, 2015 at 12:05

10 Answers 10

134

I tried using React.FormEvent<HTMLSelectElement> but it led to an error in the editor, even though there is no EventTarget visible in the code:

The property 'value' does not exist on value of type 'EventTarget'

Then I changed React.FormEvent to React.ChangeEvent and it helped:

private changeName(event: React.ChangeEvent<HTMLSelectElement>) {
    event.preventDefault();
    this.props.actions.changeName(event.target.value);
}
Sign up to request clarification or add additional context in comments.

2 Comments

this should be the top asnwer, FormEvent always shows error if I access e.target.value
This seems to be corrected as I can now successfully access event.target.value without issues; it is typed as string, so double casting might be necessary depending on the value type used in the concrete situation. For instance event.target.value as unknown as number.
81

Since upgrading my typings to react 0.14.43 (I'm not sure exactly when this was introduced), the React.FormEvent type is now generic and this removes the need for a cast.

import React = require('react');

interface ITestState {
    selectedValue: string;
}

export class Test extends React.Component<{}, ITestState> {

    constructor() {
        super();
        this.state = { selectedValue: "A" };
    }

    change(event: React.FormEvent<HTMLSelectElement>) {
        // No longer need to cast to any - hooray for react!
        var safeSearchTypeValue: string = event.currentTarget.value;

        console.log(safeSearchTypeValue); // in chrome => B

        this.setState({
            selectedValue: safeSearchTypeValue
        });
    }

    render() {
        return (
            <div>
                <label htmlFor="searchType">Safe</label>
                <select className="form-control" id="searchType" onChange={ e => this.change(e) } value={ this.state.selectedValue }>
                    <option value="A">A</option>
                    <option value="B">B</option>
                </select>
                <h1>{this.state.selectedValue}</h1>
            </div>
        );
    }
}

3 Comments

As of Dec. 2016 one has to use event.currentTarget.value to get the data from the form.
Dave, consider updating your example.
@MartinMajewski I have edited to use currentTarget (joequery.me/code/event-target-vs-event-currenttarget-30-seconds is a good explanation)
29

In my case onChange event was typed as React.ChangeEvent<HTMLSelectElement>:

onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
  console.warn('onChange TextInput value: ' + e.target.value);
}}

Comments

12

Update: the official type-definitions for React have been including event types as generic types for some time now, so you now have full compile-time checking, and this answer is obsolete.


Is it possible to retrieve the value in a type-safe manner without casting to any?

Yes. If you are certain about the element your handler is attached to, you can do:

<select onChange={ e => this.selectChangeHandler(e) }>
    ...
</select>
private selectChangeHandler(e: React.FormEvent)
{
    var target = e.target as HTMLSelectElement;
    var intval: number = target.value; // Error: 'string' not assignable to 'number'
}

Live demo

The TypeScript compiler will allow this type-assertion, because an HTMLSelectElement is an EventTarget. After that, it should be type-safe, because you know that e.target is an HTMLSelectElement, because you just attached your event handler to it.

However, to guarantee type-safety (which, in this case, is relevant when refactoring), it is also needed to check the actual runtime-type:

if (!(target instanceof HTMLSelectElement))
{
    throw new TypeError("Expected a HTMLSelectElement.");
}

4 Comments

I suppose that is better than casting to any, but you are still using type assertion, and that is what I was trying to avoid.
Yes, but since you are entirely certain that e.target is an HTMLSelectElement, this type-assertion is safe, and all subsequent type-checking is inherently safe as well, such as target.value. If you want to make it absolutely bulletproof, you could do a runtime check for its type as well, and throw a TypeError if e.target is not of the correct type. This will never actually happen, but it adds an additional layer of guaranteed type-safety.
stackoverflow.com/questions/260626/what-is-type-safe what I understand by typesafe is that the type checking is imposed by the compiler. That means no type assertions and not depending on runtime exceptions.
I previously commented that the event handler should be public, not private. I have since realized that was incorrect. Even if we had private methods at run-time (which hopefully we will in the future), a private method would still work fine here, because React doesn't need to call the private method directly but only the arrow function you're passing as the onChange prop.
10

it works:

type HtmlEvent = React.ChangeEvent<HTMLSelectElement>

const onChange: React.EventHandler<HtmlEvent> = 
   (event: HtmlEvent) => { 
       console.log(event.target.value) 
   }

Comments

9

The easiest way is to add a type to the variable that is receiving the value, like this:

var value: string = (event.target as any).value;

Or you could cast the value property as well as event.target like this:

var value = ((event.target as any).value as string);

Edit:

Lastly, you can define what EventTarget.value is in a separate .d.ts file. However, the type will have to be compatible where it's used elsewhere, and you'll just end up using any again anyway.

globals.d.ts

interface EventTarget {
    value: any;
}

4 Comments

I was hoping to get rid of the "as any" cast completely.
Thanks for your thoughts, but that still doesn't answer my question. I think the answer is that the .d.ts of react would need to be modified so that the signature of the onChange of a SELECT element used a new SelectFormEvent. Otherwise as you point out it needs a cast. I guess I could encapsulate that in a MYSELECT tag.
Okay, don't forget to answer and accept your answer :) Self answer
A better way of adding EventTarget.value is to augment the global eventTarget, which can be done via any .ts file: declare global { interface EventTarget { value: any; }}
6

JSX:

<select value={ this.state.foo } onChange={ this.handleFooChange }>
    <option value="A">A</option>
    <option value="B">B</option>
</select>

TypeScript:

private handleFooChange = (event: React.FormEvent<HTMLSelectElement>) => {
    const element = event.target as HTMLSelectElement;
    this.setState({ foo: element.value });
}

Comments

3

As far as I can tell, this is currently not possible - a cast is always needed.

To make it possible, the .d.ts of react would need to be modified so that the signature of the onChange of a SELECT element used a new SelectFormEvent. The new event type would expose target, which exposes value. Then the code could be typesafe.

Otherwise there will always be the need for a cast to any.

I could encapsulate all that in a MYSELECT tag.

Comments

0

In addition to @thoughtrepo's answer:

Until we do not have definitely typed events in React it might be useful to have a special target interface for input controls:

export interface FormControlEventTarget extends EventTarget{
    value: string;
}

And then in your code cast to this type where is appropriate to have IntelliSense support:

 import {FormControlEventTarget} from "your.helper.library"

 (event.target as FormControlEventTarget).value;

Comments

0

you should be using e.currentTarget vs e.target see https://github.com/DefinitelyTyped/DefinitelyTyped/pull/12239 for a good understanding of what is happening

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.