You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have two use cases that I cannot seem to type correctly in TypeScript. Both use cases involve functions that take a parameter of type "an object with any subset of the members of interface X".
Use case 1: options merging
interface Options {
a: string;
b: string;
}
const defaults: Options = {
a: "a",
b: "b"
}
function merge<T>(base: T, additional: <subtype of T>): T {
// clone all members of 'additional' onto 'base'
}
let opts: Options = merge<Options>(defaults, { b: "some other b" });
A React component is a class with three generic parameters Props, State and Context. The base class defines a setState(newState: State) method. In reality though, the setState() method accepts any sub-type of State.
This becomes particularly painful when having multiple layers of inheritance in React components:
BaseWidget.tsx
// one React component
class BaseWidget<P extends BaseWidget.Props, S extends BaseWidget.State, C extends BaseWidget.Context> extends ReactComponent<P, S, C> {
public componentWillMount()
// valid React call, TypeScript error 'argument of type foo is not assignable to parameter bar'
this.setState({ a: "string" });
}
}
module BaseWidget {
"use strict";
export interface Props extends React.Props<BaseWidget<Props, State, Context>> {
}
// note that all members must have ? on them to work
export interface State {
a?: string;
}
export interface Context {
}
}
export = BaseWidget
Hi Daniel Rosenwasser (@DanielRosenwasser) thanks for your reply. It definitely addresses the first use case but not the second. This is because it is not possible to explicitly mention the supertype (and you were of course right about it being a supertype) and therefore you cannot make a proper signature for the setState method.
Using public setState(newState: S): void would not work; the React library allows any supertype of S, and you have to be able to state that somehow.
/** * This class is a given by the React framework */classReactComponent<S>{protected_state: S;/** * This method actually accepts any supertype of S */protectedsetState<SextendsT>(newState: T): void{for(letnameinnewState){if(newState.hasOwnProperty(name)){this._state[name]=newState[name];}}}protectedcomponentWillMount(): void{// abstract}}
/**
* Some state interface declaration. Note all members are optional to allow setState to
* be called with supertypes of BaseState
*/
interface BaseState {a?: string;}/** * My own base class for certain React widgets */classBaseWidget<SextendsBaseState> extends ReactComponent<S>{constructor(){super();this._state={};}
protected componentWillMount(): void {this.setState({a: "boo"});}}
$ tsc v2.ts
v2.ts(11,34): error TS2304: Cannot find name 'T'.
v2.ts(11,47): error TS2304: Cannot find name 'T'.
v2.ts(40,9): error TS2322: Type '{}' is not assignable to type 'S'.
/** * This class is a given by the React framework */classReactComponent<S>{protected_state: S;/** * This method actually accepts any supertype of S */protectedsetState(newState: S): void{for(letnameinnewState){if(newState.hasOwnProperty(name)){this._state[name]=newState[name];}}}protectedcomponentWillMount(): void{// abstract}}
/**
* Some state interface declaration. Note all members are optional to allow setState to
* be called with supertypes of BaseState
*/
interface BaseState {a?: string;}/** * My own base class for certain React widgets */classBaseWidget<SextendsBaseState> extends ReactComponent<S>{constructor(){super();this._state={};}
protected componentWillMount(): void {this.setState({a: "boo"});}}
$ tsc v1.ts
v1.ts(39,9): error TS2322: Type '{}' is not assignable to type 'S'.
v1.ts(43,23): error TS2345: Argument of type '{ a: string; }' is not assignable to parameter of type 'S'.
This is because the compiler cannot know that the S that the BaseWidget is instantiated with doesn't have any additional required members. It is for this reason we need the supertype designation in the setState parameter so the compiler knows that this doesn't matter.
protected setState<T, S extends T>(newState: T): void
But... this is too unrestricted. When S is {a: string}, setState({a: 4}) will work just fine. And for setState adding new fields is also not acceptable. Ideally there must be some language construct to turn all fields into optionals, I think flow has something about this.
Guilherme Bernal (@lbguilherme) Yes I agree, there should be another language construct. Can somebody maybe remove the Question label from this issue and put something in that reflects this?
At this point I'd prefer we have a new issue putting forth a concrete proposal (hopefully specifically addressing typing of setState, which is indeed problematic) - there's a lot going on here and it would be confusing to read top-down at this point.
same issue here. If you use React + Typescript and you have some React components extending other components and you try to properly define the states with interfaces (with all properties being optional) it leads exactly to the problem described in usecase 2 above.
In other words, even though you provide a proper state object {someStateProp:true}, and all properties are optional still leads to the error:
Argument of type '{ someStateProp: boolean; }' is not assignable to parameter of type 'S'.
Sadly the only solution that I could find to make it work for now is to turn off the typecheck by manually editing react.d.ts and replacing S with any: setState(state: any)
Activity
DanielRosenwasser commentedon Dec 23, 2015
I think you mean a supertype, not a subtype.
{ b: string }is a supertype ofOptions.In TypeScript 1.8, you'll be able to write
Try it out by running
npm install -g typescript@next.Does this address your use cases?
rogierschouten commentedon Dec 28, 2015
Hi Daniel Rosenwasser (@DanielRosenwasser) thanks for your reply. It definitely addresses the first use case but not the second. This is because it is not possible to explicitly mention the supertype (and you were of course right about it being a supertype) and therefore you cannot make a proper signature for the setState method.
Using
public setState(newState: S): voidwould not work; the React library allows any supertype of S, and you have to be able to state that somehow.lbguilherme commentedon Dec 28, 2015
Rogier Schouten (@rogierschouten) What about this? (didn't try)
DanielRosenwasser commentedon Dec 28, 2015
Rogier Schouten (@rogierschouten) what if you just made all the properties optional? I think that's the usual approach.
rogierschouten commentedon Dec 28, 2015
Guilherme Bernal (@lbguilherme) do you mean this? It generates the compile errors below.
rogierschouten commentedon Dec 28, 2015
Daniel Rosenwasser (@DanielRosenwasser) your solution only works when you don't have an intermediary base class:
This is because the compiler cannot know that the S that the BaseWidget is instantiated with doesn't have any additional required members. It is for this reason we need the supertype designation in the setState parameter so the compiler knows that this doesn't matter.
lbguilherme commentedon Dec 28, 2015
Rogier Schouten (@rogierschouten) Specifying a
Twill work:But... this is too unrestricted. When S is
{a: string},setState({a: 4})will work just fine. And forsetStateadding new fields is also not acceptable. Ideally there must be some language construct to turn all fields into optionals, I think flow has something about this.rogierschouten commentedon Jan 14, 2016
Guilherme Bernal (@lbguilherme) Yes I agree, there should be another language construct. Can somebody maybe remove the Question label from this issue and put something in that reflects this?
RyanCavanaugh commentedon Jan 14, 2016
At this point I'd prefer we have a new issue putting forth a concrete proposal (hopefully specifically addressing typing of
setState, which is indeed problematic) - there's a lot going on here and it would be confusing to read top-down at this point.flyon commentedon Jan 24, 2016
same issue here. If you use React + Typescript and you have some React components extending other components and you try to properly define the states with interfaces (with all properties being optional) it leads exactly to the problem described in usecase 2 above.
In other words, even though you provide a proper state object
{someStateProp:true}, and all properties are optional still leads to the error:Argument of type '{ someStateProp: boolean; }' is not assignable to parameter of type 'S'.Sadly the only solution that I could find to make it work for now is to turn off the typecheck by manually editing react.d.ts and replacing
Swithany:setState(state: any)mDibyo commentedon Nov 17, 2016
FYI: Looks like this has been resolved with #12114.