Type of generic abstract react component - reactjs

This is my first question, please be gentle ;)
I have several components which share behavior, so I would like to have them all extend the same class, so I don't have to duplicate my functionality:
export abstract class FooComponent<P extends {}> extends React.Component<P, {}> {
foobar: Foobar = new Foobar();
}
Other classes inherit this component like:
export class BarComponent extends FooComponent<{baz?: boolean}> {
//dostuff
}
I try to pass these classes as a type into a function, like so:
setFoo (foo: typeof FooComponent) {
let obj = { foo: foo };
this.foo = <obj.foo />;
}
this.setFoo(BarComponent);
but my compiler throws the following error:
Type 'BarComponent' is not assignable to type 'FooComponent<any>'.
Property 'foobar' is missing in type 'BarComponent'.
It works if I don't extend the props, making FooComponent non-generic, but the FooComponents all have different props, which would not be type-safe if I were to set them as any.
Is there a way I can pass the type of my derived classes, and use them as my base class?
Edit
I found a way to remove all the different props in my FooComponents, so I could make it a base class without a generic type as shown below.
export abstract class FooComponent extends React.Component<{baz?: boolean}, {}> {
foobar: Foobar = new Foobar();
}
This way, the setFoo function as shown in the original question works, and no longer throws an error.
Thanks for the provided answers though, They gave me some new insights.

You can specify the constructor signature instead of using typeof
setFoo <P, T extends FooComponent<P>>(foo: new (... p: any[]) => T)

You're almost there :
React.createElement(foo, whateverProps);
should do the trick. whateverProps is an object that contains, well, props for the foo class. Be aware that by doing this, you lose some of the TypeScript advantages because the compiler won't be able to check the props against the class' props interface.

IF THE IDEAL IS TO ABSTRACT METHODS AND APPLY STATES ON THE BASIS OF ABSTRACT METHODS WITHOUT THE NEED TO RECONSTRUCT, I THINK THAT IT WILL SERVE THEM (AND IT MAY IMPLEMENT TO HIS NEED)
1 - ABSTRACT COMPONENT
import React from 'react';
/**
      * AbstractComponent
      *
      * Abstract mapper component, ie when extending this abstract component its component class
      * will have properties and auxiliary methods in order to streamline and simplify the deve
* #author Nathan C. do Nascimento <nathannascimento#nodesistemas.com.br>
*/
abstract class AbstractComponent extends React.Component<any, any>
{
protected init(states:any) {
this.changeInputVal = this.changeInputVal.bind(this);
this.state = states;
};
protected changeInputVal(e:any) {
this.setStateByKey(e.target.id, e.target.value);
};
private setStateByKey(key:any, value:any) {
this.setState({
[key] : value
});
console.log(value);
}
}
export default AbstractComponent;
1 - SO IN MY EXTENDED CLASS:
import React from 'react';
import AbstractComponent from './AbstractComponent';
export default class Teste extends AbstractComponent
{
protected constructor(props:any) {
super(props);
this.init({
inputVal : 'INITIAL STATE OF MY INPUT VAL'
});
}
render() {
return (
<div id='node-notify-app2'>
<input value={this.state.inputVal} id='inputVal' name='inputVal' onChange={this.changeInputVal} />
</div>
);
}
}
3 - AND TO FINISH IMPORT THE COMPONENT IN YOU APP.ts/tsx
File: App.tsx
import React from 'react';
import Teste from "../../temp";
/**
* Component APP
*
* #author Nathan C. do Nascimento <nathannascimento#nodesistemas.com.br>
*/
class App extends React.Component {
render() {
return (
<div id='node-notify-app'>
<Teste />
</div>
);
}
}
export default App;
4 - THIS RESULT
Now all features that added in the abstract element will be accessible to those who extend it, and through the setStateByKey method the abstract (parent) can control the state of the child, since configured in method init!

Related

Class Component with new kotlin-react without legacy

The kotlin-wrappers for React was split into kotlin-react and kotlin-react-legacy in version pre.282.
In kotlin-react-legacy it is possible to create a class based component by using RComponent.
This is missing in the new kotlin-react, however both kotlin-react and kotlin-react-legacy import kotlin-react-core which contains Component.
In kotlin-react-legacy the RComponent is defined by using RBuilder, but that doesn't exist in kotlin-react which instead has ChildrenBuilder. It would be possible to create something analogous to the legacy RComponent with ChildrenBuilder, however it cannot be accessed because it is internal.
Is there any way to create a class-based React Component, similar to what is possible in kotlin-react-legacy with RComponent, in the new kotlin-react?
There is a related discussion: https://github.com/JetBrains/kotlin-wrappers/issues/1266
Which mentions a working example: https://github.com/studoverse/campus-qr/blob/master/moderatorFrontend/src/main/kotlin/webcore/ReactHelper.kt
The RComponent can be defined as follows:
abstract class RComponent<P : Props, S : State> : Component<P, S> {
constructor() : super() {
state = jso { init() }
}
constructor(props: P) : super(props) {
state = jso { init(props) }
}
open fun S.init() {}
// if you use this method, don't forget to pass props to the constructor first
open fun S.init(props: P) {}
abstract fun ChildrenBuilder.render()
override fun render(): ReactNode = Fragment.create { render() }
}
fun <S : State> Component<*, S>.setState(buildState: S.() -> Unit) {
setState({ assign(it, buildState) })
}
Then a component can be created as:
class TestComponent : RComponent<Props, State>() {
override fun ChildrenBuilder.render() {
div {
+"Hello, world!"
}
}
}
And instantiated:
TestComponent::class.react {}

what is class extends React.component in React

In this link https://reactjs.org/docs/higher-order-components.html
where explanation is of higher order component.The code is below has class extends React.component. What is this class keyword here?
function logProps(WrappedComponent) {
return class extends React.Component {
componentDidUpdate(prevProps) {
console.log('Current props: ', this.props);
console.log('Previous props: ', prevProps);
}
render() {
// Wraps the input component in a container, without mutating it. Good!
return <WrappedComponent {...this.props} />;
}
}
}
It is an unnamed class expression.
return class extends React.Component {
The above code is creating an unnamed / anonymous class by extending React.Component class, hence, creating a new React Component which wraps (returns) the WrappedComponent passed to the function logProps.
The syntax of class expression is:
const MyClass = class [className] [extends otherClassName] {
// class body
};
where the name className (and also, extends otherClassName) is optional.
And, in your code in question, it is just returning the result instead of assigning it to a variable:
return class [className] [extends otherClassName] {
// class body
};
Note that, there are two ways to create a React Component, one is by writing a function and the other is by writing a class.
And, in JavaScript, classes was introduced in ECMAScript 2015 (also knows as ES6).

How to create a base observer class in typescript/react?

I have a common pattern my react app where I have many components that observe the same model. The code for each component looks like this:
export interface IAppModel {
someLocation: { x: number, y: number } ;
doSomething: () => void;
};
#inject("appModel")
#observer
export default class MyComponent
extends React.Component<{appModel?: IAppModel}> { ... }
I would like to simplify this by declaring a base class like this:
#inject("appModel") #observer class AppComponentBase extends React.Component<{appModel?: IAppModel}> {}
export default class MyComponent extends AppComponentBase { ... }
This gives me a runtime error: Class extends value #<Object> is not a constructor or null
Not sure if/how this can work. Any ideas?
I think your problem comes from IAppModel
Please share it

How to extend React Component prop typing without changing all usages of React.Component

I want to make testID a prop available for all React.Component instances for native testing. Currently, I am adding it to prop type of all the components that are using it. Is there any way where, for example I can define react/index.d.ts and override the Component prop type to include {testID?: string}?
EDIT:
// types/react/index.d.ts
import 'react'
import { Attributes, ClassAttributes } from 'react'
declare namespace react {
interface IntrinsicAttributes extends Attributes {
testID?: string
}
interface IntrinsicClassAttributes<T> extends ClassAttributes<T> {
testID?: string
}
}
I tried the above override, but it's not working, but if I copy the whole react typing file in and then make above changes, it works fine. So I just need proper overriding technique. Can someone please help me in that?
// src/types/react/index.d.ts
import * as React from 'react'
declare global {
namespace JSX {
interface IntrinsicAttributes extends React.Attributes {
testID?: string
}
interface IntrinsicClassAttributes<T> extends React.ClassAttributes<T> {
testID?: string
}
}
}
Above override worked for me. Thanks all!
For class components, create a proxy class and extend it as even static properties are inherited :
class Proxy extends React.Component {
static propTypes = {
testId: PropTypes.string
};
}
class SomeComponent extends Proxy {
render() {
}
}
For functional components, you cannot do this. An alternative would be to create a utility which will add it for you :
function withPropTypes(Component) {
Component.propTypes = {
testId : PropTypes.string
};
return Component;
}
function SomeComponent() {
}
const SomeComponentWithPropTypes = withProps(SomeComponent);
Note: You can use the above util for class components also.

Passing Typescript generics on React component through connect from react-redux

I'm trying to use generics in a component that gets passed through react-redux's connect. Here's a stripped down version:
export default class ItemBar<T> extends React.PureComponent{
// ...
}
When I use ItemBar with no connect, it looks like this:
export default class Component extends React.PureComponent {
render() {
return <ItemBar<number> />;
}
}
This works properly. When I "connect" the ItemBar class as such:
class ItemBar<T> extends React.PureComponent {
// ...
}
export default connect()(ItemBar);
I now get Expected 0 type arguments, but got 1. from Typescript. I think this is because the connect (and probably any other higher component) doesn't pass through the generics. Is there any way I can get this to work?
class ItemBar<T> extends React.PureComponent {
// ...
}
const connected = connect()(ItemBar);
export class ItemBar<T> extends connected {}
You may have to disable tslint at last line if you have one class rule per file.

Resources