ES7 introduces the concept of static property and method definitions. Along with an ES7-capable transpiler, these can be used in React to specify validators and defaults for props, like so:
export default class ComponentOne extends React.Component {
static propTypes = {
foo: React.PropTypes.string
}
static defaultProps = {
foo: 'bar'
}
// ...
}
This is super handy, but gets tricky when subclasses come into play. For example, say the following module is added to the same codebase as ComponentOne above:
export default class ComponentTwo extends ComponentOne {
static propTypes = {
baz: React.PropTypes.number
}
static defaultProps = {
baz: 42
}
// ...
}
I'd like ComponentTwo to "inherit" the property validators and defaults of its superclass, ComponentOne. Instead, propTypes and defaultProps on ComponentTwo shadow those on ComponentOne, and React tosses out those defined on ComponentOne.
Since super is a reference to the current class's prototype, and static is supposed to reference values hung directly off the prototype, I thought this might work:
import _ from 'lodash';
export default class ComponentTwo extends ComponentOne {
static propTypes = _.merge(super.propTypes, {
baz: React.PropTypes.number
});
}
However, this generates an error, presumably from Babel: Parsing error: 'super' outside of function or class.
This works, but is not very portable:
export default class ComponentTwo extends ComponentOne {
static propTypes = Object.assign({
baz: React.PropTypes.number
}, ComponentOne.propTypes);
}
Are there any other ways to do this more cleanly/reusably?
I stumbled upon this question, and it's been almost 3 years, but who know, someone might need it. (And it's still relevant)
Given that when you extend a class it automatically inherits of its parent class, you would not need to overwrite the static propTypes property.
Given a parent class:
class Parent {
static propTypes = {
parentProp: PropTypes.string
}
}
If you don't want to add other propTypes/defaultProps, you can simply:
class Children extends Parent {
// Do not declare the propTypes, it will extends by itself.
}
console.log(Children.propTypes); // Will output an object with parentProp in it
If you want to explicitly tell that you extends Parent propTypes, or add new propTypes:
class Children extends Parent {
static propTypes = {
...Parent.propTypes, // Yes, you can spread static properties like everything else
childProp: Proptypes.number,
}
}
Small note, for this to work properly with Babel, you might need to include the transform-es2015-classes babel plugin in your plugins or preset. My .babelrc:
"presets": [
["env", {
"include": ["transform-es2015-classes"]
}],
"stage-0",
"react"
],
Hope this helps!
Curiously enough, using super works for static methods. I'd think it should work for static properties too. To me, then, it feels more natural to use the super class name directly:
export default class ComponentTwo extends ComponentOne {
static propTypes = _.merge({}, ComponentOne.propTypes, {
baz: React.PropTypes.number
});
}
But, to use super, one workaround I can think of is using a static method to initialize the property, which unfortunately would have to be called manually:
class ComponentTwo extends ComponentOne {
static _init() {
this.propTypes = _.merge({}, super.propTypes, {
baz: React.PropTypes.number
});
}
}
ComponentTwo._init();
Related
In a library that I wish to extend without modifying its code, several classes inherit from the same imported one. That is in this BaseClass I would need to overwrite a specific method.
In the library (written in TypeScript) :
import { BaseClass } from './base_class';
export class ClassA extends BaseClass {}
import { BaseClass } from './base_class';
export class ClassB extends BaseClass {}
…
In the external extension I wish to write :
import { BaseClass } from 'library';
export class ExtendedBaseClass extends BaseClass {
oneMethod() {
const data = BaseClass.prototype.oneMethod.call(this);
// make additional things with data
return data;
}
}
Is there a way for this new ExtendedBaseClass to become the parent of all ClassXs ? At least in a new extended and re-exported version of them without the need to copy their internal code.
Is there a way for this new ExtendedBaseClass to become the parent of all ClassXs?
No.
An alternative might be to replace the one method directly on the base class:
import { BaseClass } from 'library';
const oneMethod = BaseClass.prototype.oneMethod;
Object.defineProperty(BaseClass.prototype, 'oneMethod', {
value() {
const data = oneMethod.call(this);
// make additional things with data
return data;
},
});
There's no way to do exactly what you're asking, but you could achieve the same result by extending each class individually.
ExtendedClassA extends ClassA {
oneMethod() {
// call a shared method if you need to reuse
}
}
// ExtendedClassB, etc
I understand sub classing with extends for example
class Car extends Vehicle {}
class Dog extends Animal {}
But with React, you may see
class HelloMessage extends React.Component {}
What does the dot between React and Component mean? How does it work in React and in vanilla Javascript?
Classes do not have to be standalone variable names - they may be properties of objects as well. So extends React.Component, absent any other context of what React is, just means that React is an object with has a Component property which is a class.
For an example of how to emulate this in vanilla JS:
const obj = {
Foo: class Foo {
doThing() {
console.log('doing thing');
}
}
};
class MySubClass extends obj.Foo {
subMethod() {
console.log('submethod');
}
}
const s = new MySubClass();
s.doThing();
s.subMethod();
React is doing the same sort of thing. It's just a way to organize data as properties of objects.
Lets assume that I have
// Foo.js
type PropsType = { cool: boolean };
class Foo extends React.Component<PropsType> {}
// Bar.js
import Foo from './Foo';
type PropsBar = { temp: string };
class Bar extends Foo {
test() {
this.props.cool; // there is no error
this.props.temp;
^^^^ Property not found in object type
}
}
My question is, how to pass additional Props to Bar component?
You need to make your super class generic. Just as React.Component is generic, so can your classes and functions be as well.
You can make a declaration such as a class or function generic by introducing a type parameter.
Let us make Foo generic
export default class Foo<T> extends React.Component<FooProps & T> {}
Note the intersection type, written FooProps & T, that is passed to the generic super class React.Component. This means that Foo.prototype.props will have the properties declared in FooProps and also any properties declared by T.
Now when we use Foo, such as in an extends clause, we need to specify a type for T.
type BarProps = { temp: string };
export default class Bar extends Foo<BarProps> {
constructor(props, context) {
super(props, context);
console.log(this.props.temp);
}
}
If you want to maintain simplicity for consumers of Foo that do not add additional props, you can specify a default type for T as in
export default class Foo<T = {}> extends React.Component<FooProps & T> {}
export class Bar extends Foo {}
Note: All of the syntax above is valid in both Flow and TypeScript.
I have a problem introducing TypeScript to our JavaScript project.
First I want to use TypeScript only in my part of the code, leaving the JavaScript untouched.
Now I try to use a JavaScript class in my TypeScript code, but I don't find a solution in the last days.
The head of my TypeScript class with import of the JavaScript:
import { BaseLogic } from "../baseLogic";
export class ClaimLogic extends BaseLogic {
...
The JavaScript class ("baseLogic.js"):
module.exports = class BaseLogic {
constructor(meta, logger) {
...
My *.d.ts file ("baseLogic.d.ts"):
export class BaseLogic {
meta: any;
log: any;
constructor(meta: any, logger: any)
}
The head of the compiled JavaScript:
const baseLogic_1 = require("../baseLogic");
class ClaimLogic extends baseLogic_1.BaseLogic {
...
As you see in the compiled JavaScript baseLogic_1.BaseLogic is used.
This results in following error:
TypeError: Class extends value undefined is not a constructor or null
With only baseLogic_1 after the extends keyword in the JavaScript file all is fine.
I have no idea about a solution and hope you can help me!
Your import suppose to be import * as BaseLogic from "../baseLogic";.
In that way you will get the Class that you put on module.exports.
The codesnipet in baseLogic.js exports the class.
module.exports = class BaseLogic {
constructor(meta, logger) {
...
}
You try to access with class ClaimLogic extends baseLogic_1.BaseLogic an object that includes the class BaseLogic
Solution
import BaseLogic from '../baseLogic'
// or: const BaseLogic = require("../baseLogic");
class ClaimLogic extends BaseLogic {
...
}
Suppose I have a class in one big file like this:
export default class {
constructor () {}
methodA () {}
methodB () {}
methodC () {}
}
And I want to break up the class definition so that methodA, methodB, and methodC are each defined in their own separate files. Is this possible?
You should be able to, as class is supposed to just be syntax sugar for the usual prototype workflow:
import methodOne from 'methodOne'
import methodTwo from 'methodTwo'
class MyClass {
constructor() {
}
}
Object.assign(MyClass.prototype, {methodOne, methodTwo})
export default MyClass
#elclanrs gave a correct answer, but I would modify it to allow for the use of this. I also think this is more readable.
import methodOne from 'methodOne'
import methodTwo from 'methodTwo'
class MyClass {
constructor() {
this.methodOne = methodOne.bind(this)
this.methodTwo = methodTwo.bind(this)
}
}
export default MyClass
Tip: although if your class is so large that it warrants being split into multiple files, a better solution might be to split up the class into multiple classes.