-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
What is the right way to use generic components with JSX? #3960
Comments
I can't think of a better way to do it, though I'll try to come up with something more clever. Note that your code isn't quite right (this is why you saw the crash in the other issue) -- it should be let StringSelect: new() => React.Component<SelectProps<string>, any> = Select; Your type ReactCtor<P, S> = new() => ReactComponent<P, S>;
let StringSelect: ReactCtor<SelectProps<string>, any> = Select; |
@RyanCavanaugh yes, I see, you are right. I just could not go further and see error messages because of the compiler crash. I think that generic components is not a common case, so maybe this workaround is quite enough right now (maybe it must be documented somewhere). But I will be glad to see a better way to do this. |
Full working workaround for generic components: import * as React from 'react';
interface JsxClass<P, S> extends React.Component<P, S> {
render(): React.ReactElement<P>
}
interface Render<P> {
render(): React.ReactElement<P>
}
interface ReactCtor<P, S> {
new(props: P): JsxClass<P, S>;
}
interface Props<T> {
val: T
}
class C<T> extends React.Component<Props<T>, {}> implements Render<Props<T>> {
constructor(props: Props<T>, context?: any) {
super(props)
// this.state = /* ... */
}
render(): React.ReactElement<any> {
return null
}
}
let C1: ReactCtor<Props<number>, any> = C;
let a = <C1 val={1} />; |
By the looks of it, this would require a single token of lookahead in the parser. Basically this:
I don't have a lot of time to look into this, since college just started back up for me. |
Instantiation of type aliases (#2559) would make things a bit easier: class Select<T> extends React.Component<SelectProps<T>, any> { ... }
type StringSelect = Select<string>;
<StringSelect /> |
class Select<T> extends React.Component<SelectProps<T>, any> { ... }
type StringSelect = Select<string>;
<StringSelect /> Two things - first, generic type instantiations are allowed now. Second, this code is not correct -- type StringSelect = new () => Select<string>; |
Using the example above, when making use of StringSelect I always receive a "TS2304: Cannot find name 'StringSelect'" using Typescript 1.6.2. Example: class Select<T> extends React.Component<SelectProps<T>, any> { ... }
type StringSelect = new () => Select<string>;
class Form extends React.Component<any,any> {
render(): JSX.Element {
return <StringSelect />;
}
} See gist of full example here: https://gist.github.com/be0453ed4a86c79da68e.git Any ideas? |
You need to write something like this. class Select<T> extends React.Component<SelectProps<T>, any> { ... }
type StringSelect = new () => Select<string>;
var StringSelect = <StringSelect>Select;
class Form extends React.Component<any,any> {
render(): JSX.Element {
return <StringSelect />;
}
} |
Updated @RyanCavanaugh's example to be be something you can copy paste 🌹 /** Generic component */
type SelectProps<T> = { items: T[] }
class Select<T> extends React.Component<SelectProps<T>, any> { }
/** Specialization */
type StringSelect = new () => Select<string>;
const StringSelect = Select as StringSelect;
/** Usage */
const Form = ()=> <StringSelect items={['a','b']} />; |
Note: For whatever reason (not debugging it right now) /** Generic component */
type SelectProps<T> = { items: T[] }
class Select<T> extends React.Component<SelectProps<T>, any> { }
/** Specialization */
interface StringSelect { new (): Select<string> } ;
const StringSelect = Select as StringSelect;
/** Usage */
const Form = ()=> <StringSelect items={['a','b']} />; |
Is there any practical reason why my 1-token lookahead wouldn't be feasible? (JS already requires a whole potential expression of lookahead with async arrow functions, which are implemented already when targeting ES6.) |
@isiahmeadows feel free to log a separate suggestion for that. Might be worth looking in to |
@RyanCavanaugh Done. |
Unfortunately none of these work for me with 1.8.30.0. Using:
With:
Results in:
|
However, a bigger hammer does compile and work:
|
I believe no ReactCtor or type definition is needed. I do it like this: Specialization: Usage: @fzzt resp: |
Well I got it to work with everything defined in one spot with mock classes, so I guess it works in some cases, but not in others. I'll see if I can narrow down what the difference is... |
It appears to be caused by static members on the class. If I delete all the static members, it works... |
Alright here is what I'm seeing. With this [stripped down] code:
And using this line:
I receive this error:
Interestingly, if I remove the constructor, the error message changes a little (it still doesn't work). With this code:
I get this message:
It almost sounds like the inheritance is pointing backwards. It could certainly be that I haven't set something up correctly. It all works (compiles and runs) fine with the In this particular case, I can refactor the statics out of there easily, however I would suspect that not being able to specify default props could mess up other components that really depend on them or classes that implement those interfaces from React. |
const MyTableB = MyTable as new () => MyTable<any>; This is a correct error, because This line should work: const MyTableB = MyTable as new (props: MyTableProps<any>) => MyTable<any>; There's a bug that was recently fixed that prevented the constructorless version from working. |
this code work for me: render() {
class ProjectTreeGrid extends TreeGrid<ProjectItem> {
}
return (
<ProjectTreeGrid>
<TreeGridColumn caption="title"></TreeGridColumn>
</ProjectTreeGrid>
);
} |
@KostiaSA It's highly inefficient, though. Try lifting that out of the |
Adding another voice to @fzzzt's observation that the generic class having static members causes an error in when typing it. |
Still having trouble with this... If my extending class has an additional protected method, it doesn't like the const line:
Results in:
It seems like the same issue I posted before but specifying the props in the constructor outputs the same error, as does private/protected and arrow/prototype definition, which I tried just for fun... I'm not sure why This is with TypeScript 1.8.36.0. |
what about: const TestSliderField = ContextualSliderField as
new (props?: SliderFieldProps) => ContextualSliderField<any>; |
I tried it with the props and receive the same error. |
The cause seems to be the combination of generics and static properties, which I guess really is the same problem as before... This works:
And this works:
But this doesn't:
I guess this is basically a duplicate post, sorry about that. I didn't realize it was the same core issue until I removed |
Unless I'm missing something, the type aliasing approach does only work outside of generic classes or functions, right? type SelectProps<T> = { items: T[] }
class Select<T> extends React.Component<SelectProps<T>, any> { }
function Form<T>(items: T[]) {
return <Select<T> items={items} />;
} The only solution I found for now is not to use JSX syntax and directly code whatever it would have generated, e.g.: function Form<T>(items: T[]) {
return React.createElement(Select, { items=items });
} Note: If TSC was able to infer the generic types from the parameters, it would not cause any parsing issues while enabling the following code to compile successfully: function Form<T>(items: T[]) {
return <Select items={items} />; // -> Select<T> type inferred through items property
} |
I use this approach: const StringSelect: new() => Select<string> = Select as any;
...
return <StringSelect items={items}/>; And it solves the problem @avonwyss talked about: function Form<T>(items: T[]) {
const SpecificSelect: new() => Select<T> = Select as any;
return <SpecificSelect items={items} />;
} |
@mrThomasTeller This approach is not a good solution IMHO since it needs an |
BTW, if you really want this feature, you might want to look at my suggestion in #6395, and if you're familiar with the internals (or willing to put the effort into it), it can be done. (The main issue is supposedly architectural, from what they said over there.) |
Sorry to revive this, but on 2.x none of the above seems to work if you want an export that works as a type and a value. ex: type FeesUK = new () => Fees<FeesProps, any>;
const FeesUK: FeesUK = Fees as FeesUK; This works for the component, ie you can now use class Something extends React.Component<any, any> {
private _reference: FeesUK;
render() {
return <FeesUK ref={(r) => this._reference = r} />;
}
} You will get an error when doing The only thing that seems to work to alleviate this is the following: type FeesUK = Fees<FeesProps, any>;
type FeesUKCtor = new () => FeesUK;
const FeesUK: FeesUKCtor = Fees as FeesUKCtor;
export { FeesUK }; |
JSX generics will land in 2.9 |
Thanks for adding it. Adding the release notes for future reference: http://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html |
To those of you using IntelliJ IDEA 2018.1.5 (current version) that does not support this syntax, a proxy or derived class will work. eg proxy export const FormValueListPicker = (props: Props<IFormValue<string>>) =>
new ListPicker<IFormValue<string>>(props); eg derived export class FormValueListPicker extends ListPicker<IFormValue<string>> {} |
Hello,
I have a generic component, e.g.
class Select<T> extends React.Component<SelectProps<T>, any>
. Is there any "right" way to use it from JSX?It is impossible to write something like
<Select<string> />
in JSX. The best thing that I've found is to writeAnd use
StringSelect
instead ofSelect<string>
. Is there anything better?The text was updated successfully, but these errors were encountered: