typesafe select onChange event using reactjs and typescript
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>
);
}
}
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);
}
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>
);
}
}
In my case onChange
event was typed as React.ChangeEvent<HTMLSelectElement>
:
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
console.warn('onChange TextInput value: ' + e.target.value);
}}
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.");
}
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;
}