I have the following nested class structure:
import React, {Component} from 'react';
import {TextField} from '@material-ui/core';
import './ProfileEditor.css';
export default class ProfileEditor extends Component {
SyncedTextField = class SyncedTextField extends Component {
onChange = event => {
console.log(this);
};
render() {
return (
<TextField
{...this.props}
onChange={this.onChange}/>
);
}
};
render() {
return (
<form className={"ProfileEditor"}>
<this.SyncedTextField/>
</form>
);
}
}
When the code is bundled by Webpack, and run in Firefox, it runs this.onChange
correctly, but the outputted this
refers the the context of the ProfileEditor
class instead.
This is excessively strange because in the JSX, when I refer to "this" it points to SyncedTextField correctly, but in the onChange method, it points to ProfileEditor
.
I did add some properties to ProfileEditor
to sanity check myself, and the properties showed up as declared in ProfileEditor
, even when a conflicting definition was provided in SyncedTextField.
Can someone please tell me how I can avoid this issue, and what may be causing it?
Incorrect behaviour may be specific to browser development tools. But in this case it's caused by how transpiler works. There is a bug in Babel 6 class fields (which are stage 3 proposal) transform implementation.
The example compiled with Babel outputs
ProfileEditor
asthis
inonChange
.Here's
SyncedTextField
constructor from Babel output:Notice that transpilers create
_this
,_this2
, etc. temporary variables to provide lexicalthis
in arrow functions but Babel uses wrong variable.onChange = ...
class field is syntactic sugar for:When the example is changed from class fields to constructor code, it outputs
SyncedTextField
.The same example compiled with TypeScript (used by Stackblitz by default in React template) works as expected and outputs
SyncedTextField
asthis
inonChange
.Since classes are rarely defined this way, Babel bug is usually not applicable.
SyncedTextField = class SyncedTextField extends Component {...}
is an antipattern. There is no reason to nest class expression like that. It is inefficient because it is evaluated on eachProfileEditor
instantiation. It should be class declaration, can be used as<SyncedTextField/> this way.
Even if
SyncedTextField
should be defined as a property ofProfileEditor
component for testability or extensibility reasons, it's preferable to make it prototype property: