Class Component with new kotlin-react without legacy - reactjs

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 {}

Related

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 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.

Decorator for children of abstract generic Container class in Typescript

I'm having trouble with Typescript generics. I'm using Typescript 2.6.
The basic idea is that I want to create a MobX store that exposes a class decorator adding basic authentication check, and that decorator takes a class type derived from the abstract generic Container class (a React Router route container, for example).
Here is the code leading to the issue:
interface Newable<T, U = any> {
new (...args: U[]): T;
}
function withAuth<TContainer extends Container<TProps, TState>, TProps extends Container.Props, TState>(ctor: Newable<TContainer>): Newable<TContainer> {
return class WithAuth extends ctor {
public async componentWillMount() {
await this.props.stores.authentication.tryLoggingIn();
super.componentWillMount();
}
public async componentWillUpdate() {
await this.props.stores.authentication.tryLoggingIn();
super.componentWillUpdate();
}
public render(): React.ReactNode {
return super.render();
}
};
}
Both the TProps and TState generic parameters can be redefined by the derived class, as they may have more React props or a complex state.
The error messages I get from the Typescript compiler:
Type 'typeof WithAuth' is not assignable to type 'Newable<TContainer, any>'. Type 'WithAuth' is not assignable to type 'TContainer'. (line 6)
Base constructor return type 'TContainer' is not a class or interface type. (line 6)
Property 'props' does not exist on type 'WithAuth'. (line 8)
Property 'props' does not exist on type 'WithAuth'. (line 14)
Removing the return type of the withAuth function leads to this error:
Base constructor return type 'TContainer' is not a class or interface type.
Here is the relevant code for the Container class (simplified, it contains a little bit more stuff):
abstract class Container<T extends Container.Props = Container.Props, U extends React.ComponentState = {}> extends React.Component<T, U> {
public abstract render(): React.ReactNode;
}
namespace Container {
export type Props = React.Props<any>; // TODO: typings
}
What I don't get is, even using a non-generic class instead of Container leads to the
Base constructor return type 'TContainer' is not a class or interface type.
error. However, as you can see in the generic parameter declaration, TContainer IS DERIVED from a class. From what I get, this shouldn't be an issue.
So my question is, how can I create a class decorator to the Container<TProps, TState> that would add authentication checks to componentWillMount and componentWillUpdate?
Thanks a bunch!
You are trying to use mixins, which are described here. The way it works is pretty particular, the class that will be augmented must be passed in as a type parameter, which can be constrained to extend the abstract class. Note that since we mandate the class will have a callable constructor, the class passed to withAuth can't be Container but rather a class that is derived from Container and implements the abstract methods already.
abstract class Container<T extends Container.Props = Container.Props, U extends React.ComponentState = {}> extends React.Component<T, U> {
public abstract render(): React.ReactNode;
}
namespace Container {
export type Props = {
stores: any
};
}
interface Newable<T, U = any> {
new(...args: U[]): T;
}
function withAuth<TCtor extends Newable<Container<Container.Props, any>>>(ctor: TCtor) {
return class WithAuth extends ctor {
public async componentWillMount() {
await this.props.stores.authentication.tryLoggingIn();
this.componentWillMount();
}
public async componentWillUpdate() {
await this.props.stores.authentication.tryLoggingIn();
this.componentWillUpdate();
}
public render(): React.ReactNode {
return this.render();
}
};
}
class NewComponent extends Container< Container.Props & { otherProp: string}, any> {
public render(): React.ReactNode {
// Actual implementation
throw new Error("Method not implemented.");
}
}
const NewComponentWitAuth = withAuth(NewComponent);
let witAuth = <NewComponentWitAuth otherProp="" stores={null} /> // props type is preserved

Type of generic abstract react component

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!

The this keyword is undefined in React base class

I have a basic React app and I'd like to put some commonly used functionality into a base component class and have all my other components inherit from that class to get access to those features. I have this:
export class BaseComponent extends React.Component {
constructor() {
super();
this.commonlyUsedMethod = this.commonlyUsedMethod.bind(this);
}
commonlyUsedMethod() {
let x = this.someValue; // <--- 'this' is undefined here
}
}
export class SomeComponent extends BaseComponent {
onButtonClick() {
super.commonlyUsedMethod();
}
render() {
return whatever;
}
}
The problem is that when I call super.commonlyUsedMethod() from the derived class, this.someValue blows up inside BaseComponent.commonlyUsedMethod() because this is undefined. I'm calling this.commonlyUsedMethod.bind(this); in the BaseComponent constructor, so I'm not sure what's going on.
First of all I (and most of the React dev community) don't recommend you to use inheritance. https://facebook.github.io/react/docs/composition-vs-inheritance.html
Most of the use cases you have you can solve it using Higher Order Components or writing functions in a JS file and importing it.
If you still want to go ahead and do this.
You need to bind the this when you attach the buttonClick listener
export class SomeComponent extends BaseComponent {
onButtonClick() {
super.commonlyUsedMethod();
}
render() {
return <div onClick={this.onButtonClick.bind(this)}>Hello</div>;
}
}
Here is the working example for it. https://www.webpackbin.com/bins/-Knp4X-n1RrHY1TIaBN-
Update: Problem was not with calling super with proper this, problem was with not binding proper this when attaching the onClick listener. Thanks #Mayank for pointing it out.
So I'm not sure if this a Good Practice™, but I can get it to work by calling this.someCommonMethod() instead of super.someCommonMethod(), like this:
export class SomeComponent extends BaseComponent {
constructor() {
super();
this.onButtonClick = this.onButtonClick.bind(this);
}
onButtonClick() {
this.commonlyUsedMethod(); <--- changed 'super' to 'this'
}
render() {
return whatever;
}
}
I'm new enough to React and ES6 not to know if this is how this should work. Any thoughts would be appreciated.

Resources