react component method binding with arrow function (class properties) - reactjs

I heard that this is the cool way to define function instead of binding them in constructor:
class Comp extends React.Component {
delete = id => {
}
render() {}
}
but when I try to build I get:
Module build failed: SyntaxError: Unexpected token (7:15)
pointing to equal sign after delete
what I am missing?

To define class properties the way you are doing it, you need to activate the experimental babel feature transform-class-properties. This allows you to assign expressions like arrrow functions to class properties:
class Bork {
//Property initializer syntax
instanceProperty = "bork";
boundFunction = () => {
return this.instanceProperty;
}
//Static class properties
static staticProperty = "babelIsCool";
static staticFunction = function() {
return Bork.staticProperty;
}
}
Note that this feature is not yet part of the ECMAScript specification and may change in the future.

You need an additional babel plugin for this feature:
https://www.npmjs.com/package/babel-plugin-transform-class-properties

Related

Typescript ReferenceError: google is not defined, but only in standalone files

I'm building a React app with an embedded Google Map.
I've got a custom menu element that I want to display on the map after a click. Google's docs instruct me to 'implement' (although I think in Typescript terms, they mean extend) the google.maps.OverlayView class in order to render elements over the map.
When I define the class ContextMenu extends google.maps.OverlayView class inline, the code compiles fine and my element shows up on click. I want to define this class in a separate file, using Typescript.
However, when I move ContextMenu to a separate file, React errors out with ReferenceError: google is not defined.
Any idea how to 'import the namespace' such that ContextMenu.ts knows where google is? It seems like I am missing something fundamental about Typescript here, but none of their documentation I've been able to find has discussed the practice of creating classes with external namespaces.
Or is extends the wrong way to do this here? Should I just follow Google's instructions, even in Typescript which exists to avoid messing with prototypes?
Inherit from this class by setting your overlay's prototype: MyOverlay.prototype = new google.maps.OverlayView();.
Working code:
// App.tsx
import React from 'react'
import { Wrapper } from '#googlemaps/react-wrapper'
// cannot define ContextMenu here
const App: React.VFC = () => {
// cannot define ContextMenu here
const onClick = (e: google.maps.MapMouseEvent) => {
// CAN define ContextMenu here
class ContextMenu extends google.maps.OverlayView {
private origin_: google.maps.LatLng
constructor(origin: google.maps.LatLng) {
super()
this.origin_ = origin
}
}
const menu = new ContextMenu(e.latLng)
}
return (
<Wrapper ...>
// Map goes here
</Wrapper>
)
}
Broken code:
// App.tsx as above, without ContextMenu defined.
// ContextMenu.ts
class ContextMenu extends google.maps.OverlayView {
// ...
}
It is not possible to directly extend a google.maps.* class since it actually isn't available (this might depend on tsconfig target, but I haven't tested). You can use the following pattern in TypeScript to delay.
export interface OverlayViewSafe extends google.maps.OverlayView {}
/**
* Extends an object's prototype by another's.
*
* #param type1 The Type to be extended.
* #param type2 The Type to extend with.
* #ignore
*/
// eslint-disable-next-line #typescript-eslint/no-explicit-any
function extend(type1: any, type2: any): void {
// eslint-disable-next-line prefer-const
for (let property in type2.prototype) {
type1.prototype[property] = type2.prototype[property];
}
}
/**
* #ignore
*/
export class OverlayViewSafe {
constructor() {
// We use the extend function for google.maps.OverlayView
// because it might not always be available when the code is defined.
extend(OverlayViewSafe, google.maps.OverlayView);
}
}

mobx react action binding

For those who has written apps with mobx + react, I'm wondering if there's a better way to handle context issue (eg. this. returns undefined in mobx store) when using onClick event handler inside a react component w/ inject & observer.
I have been writing the handler like onClick={actionFromStore.bind(this.props.theStore)} to resolve that issue, but it seems like there should be more concise way to do this that I'm not aware of.
I'm not a mobx expert, any advice would be appreciated!
The actions here are async fetch requests
You can either use #action.bound decorator:
#action.bound
doSomething(){
// logic
}
or use labmda function which will preserve the context:
#action
doSomething = ()=> {
// logic
}
Since there is 2018, the best practice in React apps development is to use lambda functions as class properties instead of class methods.
The lambda function as class property resolves all issues that can happen with context. You don't have to bind methods to the specific context, if using it.
For example, you working with this in some class method:
export default class SomeClass {
myProp = "kappa"
myMethod() {
console.log(this.myProp)
}
}
In this case, if you will use it, e.g., like some event listener, this will be unexpectedly (actually, more than expected) change from SomeClass instance to other value. So, if you using class methods, you should modify you code like this:
export default class SomeClass {
constructor() {
this.myMethod = this.myMethod.bind(this)
}
myProp = "kappa"
myMethod() {
console.log(this.myProp)
}
}
In constructor you are binding your class method to context of SomeClass instance.
The best way to avoid this kind of unnecessary code (imagine, that you have 10+ of this type of methods - and you should bind each of them), is to simply use lambda functions:
export default class SomeClass {
myProp = "kappa"
myMethod = () => {
console.log(this.myProp)
}
}
That's it! Lambda functions have no context, so this will always point to the SomeClass instance. So, now you can decorate you class property as you wish:
export default class SomeClass {
myProp = "kappa"
#action
myMethod = () => {
console.log(this.myProp)
}
}
Note, that if you are using Babel, you have to use transform-class-properties plugin.
This question is more related to the core of JavaScript, so I advise you to read this MDN article for more information about this behavior.
Hope, this was helpful!
With Mobx 6, decorators are becoming more discouraged and cumbersome to use (requiring makeObservable(this) to be called carefully in the constructor, even in subclasses.)
I therefore now find it cleaner to use
doStuff = action(() => {
// stuff logic
})
rather than
#action.bound
doStuff() { ...
or
#action
doStuff = () => { ...
This pattern with no decorators also works in older Mobx versions.

React binding through constructor - possible to automate?

As per recommendations from others, I have been binding class methods in the constructor in React, for example:
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
I have components with many methods, and I am binding all of these methods to this. Argh, what a pain! To avoid repetitively maintaining this pattern, I built a function that would be called in the constructor in place of all the individual calls; it binds all the methods specific to that class, while a parent class would take care of its own methods moving up the classes. For example:
function bindClassMethodsToThis(classPrototype, obj) {
Object.getOwnPropertyNames(classPrototype).forEach(prop => {
if (obj[prop] instanceof Function && prop !== 'constructor') {
obj[prop] = obj[prop].bind(obj);
console.log(`${classPrototype.constructor.name} class binding ${prop} to object`);
}
});
}
class A {
constructor() {
bindClassMethodsToThis(A.prototype, this);
}
cat() {
console.log('cat method');
}
}
class B extends A {
constructor() {
super();
bindClassMethodsToThis(B.prototype, this);
}
dog() {
console.log('dog method');
}
}
let b = new B();
So, React and ES6 gurus, is this a reasonable approach, or I am doing something wrong here? Should I stick to the individual bindings to this?
Your strategy seems sound, though there are some edge cases that you may end up wanting to tackle. A library like react-autobind, which Alexander mentioned, takes care of some of these things for you, and if I were to use this strategy I would probably use a library like this one (or take a look into the source code to get an idea for what it does).
For completeness, some alternative approaches are:
Use class properties and arrow functions (along with any necessary Babel transforms) to create pre-bound methods:
class MyComponent extends React.Component {
handleChange = () => { /* ... */ }
}
Use a decorator, like the autobind decorator from core-decorators, along with any necessary Babel transforms (this was the strategy I used previously):
import { autobind } from 'core-decorators'
class MyComponent extends React.Component {
#autobind
handleChange() { /* ... */ }
}
Explore the use of Hooks (currently in alpha) to avoid the problem of binding all together (since state values and setters exists as local variables to be closed over). This is the strategy I've been preferring very recently, but please note it is still in the proposal state and may change. :)
Assuming you have Babel setup for it, you can also use arrow functions instead, avoiding the need to bind this:
class Foo extends React.Component {
handleClick = (event) => {
event.preventDefault()
}
render() {
return <div onClick={this.handleClick}>Click me</div>
}
}
One way to resolve this is to call bind in render:
onChange={this.handleChange.bind(this)}

TypeScript 2: Calling method of class, not object

I have found a great service for recording audio in Ionic.
But there is at least one thing that I do not understand:
For example, in line 25:
this.MediaPlugin.startRecord();
Question: Why does it call on this.MediaPlugin.startRecord() and not this.mediaPlugin.startRecord() where this.mediaPlugin is an object and MediaPlugin a class?
If the class uses this.MediaPlugin to do the actions why does it return in the get method an object?
complete code:
import { Injectable } from '#angular/core';
import { MediaPlugin } from 'ionic-native';
export enum AudioRecorderState {
Ready,
Recording,
Recorded,
Playing
}
#Injectable()
export class AudioRecorder {
mediaPlugin: MediaPlugin = null;
state: AudioRecorderState = AudioRecorderState.Ready;
get MediaPlugin(): MediaPlugin {
if (this.mediaPlugin == null) {
this.mediaPlugin = new MediaPlugin('../Library/NoCloud/recording.wav');
}
return this.mediaPlugin;
}
startRecording() {
this.MediaPlugin.startRecord();
this.state = AudioRecorderState.Recording;
}
stopRecording() {
this.MediaPlugin.stopRecord();
this.state = AudioRecorderState.Recorded;
}
startPlayback() {
this.MediaPlugin.play();
this.state = AudioRecorderState.Playing;
}
stopPlayback() {
this.MediaPlugin.stop();
this.state = AudioRecorderState.Ready;
}
}
this.MediaPlugin is the reference to the get. Using this syntax, it'll construct the MediaPlugin on the first call, but use the constructed on following calls.
(This solutions seems a little weird to me too, since it'd make more sense to just use the constructor in the AudioRecorder class to init this.mediaPlugin, and then use this.mediaPlugin elsewhere)
First of all following method is a getter and I guess it's an attempt to implement readonly property with lazy initialization.
get MediaPlugin(): MediaPlugin {
if (this.mediaPlugin == null) {
this.mediaPlugin = new MediaPlugin('../Library/NoCloud/recording.wav');
}
return this.mediaPlugin;
}
So when you try to access this.MediaPlugin for the first time, new instance of MediaPlugin is being created. And when you instantiate AudioRecorder and do not use it (for some reasons) it helps you to save memory not creating instance of MediaPlugin immediately. (see Lazy loading pattern)
Answering you question:
Why does it call on this.MediaPlugin.startRecord() and not
this.mediaPlugin.startRecord() where this.mediaPlugin is an object and
MediaPlugin a class?
Typescript does not provide any common mechanism of lazy loading and the way to implement it is to create private property with a getter method (as in your AudioRecorder class).
Calling this.MediaPlugin.startRecord() you kind of encapsulating creation and manipulation logic of MediaPlugin instance.
Naming conventions
Someone messed up with regards to naming conventions. The name of the property should be mediaPlugin, not MediaPlugin.
The reason that the property accessor is called MediaPlugin is that there is already a backing field called mediaPlugin.
Some would argue that the backing field should be called _mediaPlugin. Others would argue that this would go against naming conventions. If the latter is the case, you could call it mediaPluginField.
class AudioRecorder {
mediaPluginField: MediaPlugin = null;
get mediaPlugin(): MediaPlugin {
if (this.mediaPluginField === null) {
this.mediaPluginField = new MediaPlugin('../Library/NoCloud/recording.wav');
}
return this.mediaPluginField;
}
}
Dependency inversion
However, as #Vladyslav Yefremov points out, an arguably better option would be to inject the MediaPlugin dependency through the constructor or pull it from some kind of service locator.
class AudioRecorder {
constructor(private mediaPlugin: MediaPlugin) { }
}
or
class AudioRecorder {
private mediaPlugin: MediaPlugin;
constructor(mediaPlugin: MediaPlugin) {
this.mediaPlugin = mediaPlugin;
}
}
Lazy instantiation
I do not immediately see the need to instantiate the media plugin lazily, as the audio recorder needs a media plugin for all actions.
However, it could be because the media plugin has the side effect of loading the media file when instantiated. If this is the case, the media plugin property is instantiated lazily to delay the opening of the resource until it is actually needed, i.e. when the AudioRecorder is needed.

Typescript object extends the Array<BoxModel> can not call the inside methods anymore

After upgrading to Ionic3 which is using the newest Typescript version (2.2.1) I am facing the big issue. I have one normal Typescript class BoxList which extends the Array. So it is normal array with some additional custom methods that I use. I have a method called "allHit" which loops through the array and returns the boolean value. The thing is, that in Ionic2 everything was working fine, but after the upgrade, I can not call the this.boxList.allHit method anymore because it throws me an exception:
-> main.js:1 ERROR TypeError: this.boxList.allHit is not a function(…)
Code:
import {BoxModel} from "./BoxModel";
export class BoxList extends Array<BoxModel> {
constructor() {
super();
}
public allHit(boxTarget: BoxModel) : boolean {
return this.findIndex(box => box.doesMatch(boxTarget) && !box.isHit) === -1;
}
public findUntouchedBox() : BoxModel {
return this.find(box => !box.isHit);
}
}
And the call of the allHit method from other object:
public allBoxesAreHit() : boolean {
return this.boxList.allHit(this.targetBox);
}
Does someone have a clue what is going on here? Thanks!
According to Typescript 2.1 breaking changes:
As part of substituting the value of this with the value returned by a super(...) call, subclassing Error, Array, and others may no longer work as expected. This is due to the fact that constructor functions for Error, Array, and the like use ECMAScript 6's new.target to adjust the prototype chain; however, there is no way to ensure a value for new.target when invoking a constructor in ECMAScript 5.
Try their recommendation and set:
constructor() {
super();
Object.setPrototypeOf(this, BoxList.prototype);
}
in the constructor.

Resources