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.
Related
The issue I'm having isn't one that needs fixing so to speak more so I'm curious as to why this functionality changes. This code is in Typescript and I'm using Mobx as well.
I have a state called GlobalPageState with a property selectedTab and a function to set this property called setSelectedTab
export default GlobalPageState extends State {
#observable
public selectedTab = string;
#action
public setSelectedTab(selectedTab: PageTab) {
this.selectedTab = selectedTab;
}
}
in my react component, I've destructured these properties and referenced them as such:
const { selectedTab, setSelectedTab } = GlobalPageState
const onTabSelect = (newTab: string) => {
setSelectedTab(newTab)
}
This gives me the error that I can't set the properties of undefined. However, this works:
const { selectedTab } = GlobalPageState
const onTabSelect = (newTab: string) => {
GlobalPageState.setSelectedTab(newTab)
}
Why would this be the case, assuming everything else remains the same? Does destructuring the function call in this case somehow change its functionality i.e. referencing? Not shown are a lot of other functions and properties on GlobalPageState that is being used, and those all work fine when destructured in this way so I'm unsure why this call would be different. Thanks!
Basically you just need to read how this works in Javascript https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
tl;dr: your function is losing context when you assign it to the variable and then call without dot, like class.method()
Regarding to MobX you can also use #action.bound although I would recommend sticking to arrow functions because it is more "native" JS way of doing thing. Also it is usually better to define all public class methods (actions) as arrow functions, that way you don't need to worry if you destructure them or not, you can call them either way.
To be clear, I am learning TypeScript and React for spfx developments. I have read and taken part in tutorials and other answers on various sources and Stack Overflow and haven't found them enough to help me.
Here is my function in the EvalReqNewForm class (getGrades()):
export default class EvalReqNewForm extends React.Component<IEvalReqNewProps, IEvalReqNewState> {
constructor(props){
super(props);
this.state = {
EvalType: null,
JobTitReportTo: null,
JobTitReportToNum: null,
PropGradeList: [],
SelectedGrade: undefined,
CompPos: null,
ContextNewJobCode: null
};
this._onJobTitReportToChange = this._onJobTitReportToChange.bind(this);
this._onJobTitReportToNumChange = this._onJobTitReportToNumChange.bind(this);
this._onPropGradeChange = this._onPropGradeChange.bind(this);
}
...
public _getGrades() {
pnp.sp.web.lists.getByTitle("Grades").items.get().then((items: any[]) =>{
let returnedGrades:IDropdownOption[]= items.map((item) =>{return {key:item.Title, text:item.Title};});
this.setState({PropGradeList : returnedGrades});
});
}
And I want to use the _getGrades() function from the other class to use in a ComponentDidMount() in order to the 'Grades' from the SP list.
Does it involve using props? Or can the function be simply exported and imported into the class where I want to use it?
Please understand I'm learning the basics here!
TL;DR
Create a function that only depends on it's parameters and export/import it.
Or can the function be simply exported and imported into the class where I want to use it?
What you can do is create a function that depends only on it's parameters.
So it would be something like
export function getGrades(something){
// do what ever you want with something
return somethingYouDid
}
And then you import it to other files and use it like
import { getGrades } from '...'
...
public _getGrades() {
const results = getGrades(someDataFromSomeWhere)
this.setState({PropGradeList : results});
}
I also see that you are using promises, so maybe you will need to use async/await in your case.
Edit:
As said in the comments
I can't seem to use export within a class
You should use it out side of the class, you will have to functions.
One out side of the class that have all the logic and other inside that class that only calls the outside function.
export function getGrades(something){
// do what ever you want with something
return somethingYouDid
}
class ... {
...
public _getGrades() {
const results = getGrades(someDataFromSomeWhere)
this.setState({PropGradeList : results});
}
}
I always use this expression in my components:
class Cart extends Component { }
Recently I have seen a lot of codes using this expression.
class Cart extends React.Component {
constructor(props) {
super(props);
}
}
Why is it? What is the purpose of using constructor and super?
Is React.Component and Component same?
Why do we pass props in constructor and super?
I am not asking what super and constructor are, I am asking difference between 2 codes above and benefits of using each one?
I checked online but did not see any explanation, just code examples.
Constructors and super are not react specific or even javascript specific. They are specific to the inheritance in OOP.
Constructors
Constructors are what can be called as initializing functions in a class. Let's look at an example where a constructor can be used.
class parentClass {
constructor(){
this.foo = foo;
this.bar = bar;
}
function sharedMethod1(){
print(this.foo);
}
function sharedMethod(){
print(this.bar)
}
}
object1 = new ParentClass(foo1, bar1);
object1.sharedMethod1() // this will print foo1;
object1.sharedMethod2() // this will print bar1;
object2 = new ParentClass(foo2, bar2);
object2.sharedMethod1() // this will print foo2;
object2.sharedMethod2() // this will print bar2;
when there is a need to create multiple instances of a class with different values for member variables / functions, we make use of the constructor functions.
Super
The super keyword is used in inheritance as well. In inheritance when extending a child class from a parent class, there is a need to initialise the constructor of the parent class. The super keyword is used for this purpose. let's look at the below example for super.
class ParentClass (){
constructor(){
this.foo = foo;
}
}
class childClass extends ParentClass(){
super(foo1); // super is used here initialize the constructor of the ParentClass
}
The same principle mentioned above is followed in React as well.
Please look into dan abramov's blog post on constructor and super here https://overreacted.io/why-do-we-write-super-props/
Dan Abramov mentions on his blog that:
You can’t use this in a constructor until after you’ve
called the parent constructor.
JavaScript won’t let you.
JavaScript enforces that if you want to use this in a constructor, you
have to call super first.
calling super(props) is needed in order to access this.props.
as mentioned on this thread, there are 2 main reasons to use a constructor on a react component:
Initializing local state by assigning an object to this.state.
Binding
event handler methods to an instance.
If you don’t initialize state and you don’t bind methods, you don’t need to implement a constructor for your React component.
If you don’t initialize state and you don’t bind methods, you don’t
need to implement a constructor for your React component.
Copied from reactJS website.
If you don’t initialize state and you don’t bind methods, you don’t
need to implement a constructor for your React component.
The constructor for a React component is called before it is mounted.
When implementing the constructor for a React.Component subclass, you
should call super(props) before any other statement. Otherwise,
this.props will be undefined in the constructor, which can lead to
bugs.
Typically, in React constructors are only used for two purposes:
Initializing local state by assigning an object to this.state.
Binding event handler methods to an instance.
You should not call setState() in the constructor(). Instead, if your
component needs to use local state, assign the initial state to
this.state directly in the constructor:
Constructor is the only place where you should assign this.state
directly. In all other methods, you need to use this.setState()
instead.
Avoid introducing any side-effects or subscriptions in the
constructor. For those use cases, use componentDidMount() instead.
https://reactjs.org/docs/react-component.html
And here is the purpose of super() ,
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super
I tried with babel, Here's what I get.
With Constructor I get this function,
class App extends React.Component{
constructor(props){
this.state = {}
}
}
Related part in ES5,
var App =
/*#__PURE__*/
function (_React$Component) {
_inherits(App, _React$Component);
function App(props) {
var _this;
_classCallCheck(this, App);
_this.state = {};
return _possibleConstructorReturn(_this);
}
return App;
}(React.Component);
Without Constructor
class App extends React.Component{
state = {}
}
ES5 code
var App =
/*#__PURE__*/
function (_React$Component) {
_inherits(App, _React$Component);
function App() {
var _getPrototypeOf2;
var _temp, _this;
_classCallCheck(this, App);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _possibleConstructorReturn(_this, (_temp = _this = _possibleConstructorReturn(this, (_getPrototypeOf2 = _getPrototypeOf(App)).call.apply(_getPrototypeOf2, [this].concat(args))), _this.state = {}, _temp));
}
return App;
}(React.Component);
Why is it? What is the purpose of using constructor and super?
Constructors and the super-keyword are not react specific, but JavaScript specific. Search for inheritance and "OOP" in JavaScript to better understand it (e.g. this medium article). super and inheritance is also not only bound to JavaScript, many other languages also use it and explain it accordingly, here for example is a good explanation of how the super-keyword should be used in java.
Why do we pass props in constructor and super?
Check Why Do We Write super(props)?.
Is React.Component and Component same?
Yes. In the head-section of the files are a bunch of import ...-statements. For React.Component you will find something like import * as React from "react" or import React from "react". In case of Component you will find import { Component } from "react". Check the import-documentation from MDN for more information.
I am asking difference between 2 codes above and benefits of using each one?
There is no difference in the result. Both versions work the same and there is no (notable) difference in performance. One version is shorter and less noisy. If you have no further instructions in the constructor, I would recommend suppressing it.
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)}
I am coding a simple app on reactjs-flux and everything works fine except I am receiving a warning from reactjs telling me that I am calling setState on unmounted components.
I have figured out this is because changelisteners to which components are hooked are not being removed from the store on componentWillUnmount. I know it because when I print the list of listeners from Eventemitter I see the listener which was supposed to be destroyed still there, and the list grows larger as I mount/unmount the same component several times.
I paste code from my BaseStore:
import Constants from '../core/Constants';
import {EventEmitter} from 'events';
class BaseStore extends EventEmitter {
// Allow Controller-View to register itself with store
addChangeListener(callback) {
this.on(Constants.CHANGE_EVENT, callback);
}
removeChangeListener(callback) {
this.removeListener(Constants.CHANGE_EVENT, callback);
}
// triggers change listener above, firing controller-view callback
emitChange() {
this.emit(Constants.CHANGE_EVENT);
}
}
export default BaseStore;
I paste the relevant code from a component experiencing this bug (it happens with all components, though):
#AuthenticatedComponent
class ProductsPage extends React.Component {
static propTypes = {
accessToken: PropTypes.string
};
constructor() {
super();
this._productBatch;
this._productBatchesNum;
this._activeProductBatch;
this._productBlacklist;
this._searchById;
this._searchingById;
this.state = this._getStateFromStore();
}
componentDidMount() {
ProductsStore.addChangeListener(this._onChange.bind(this));
}
componentWillUnmount() {
ProductsStore.removeChangeListener(this._onChange.bind(this));
}
_onChange() {
this.setState(this._getStateFromStore());
}
}
This is driving me pretty nuts at this point. Any ideas?
Thank you!
Short version: expect(f.bind(this)).not.toBe(f.bind(this));
Longer explanation:
The cause of the issue is that EventEmitter.removeListener requires that you pass a function you have previously registered with EventEmitter.addListener. If you pass a reference to any other function, it is a silent no-op.
In your code, you are passing this._onChange.bind(this) to addListener. bind returns a new function that is bound to this. You are then discarding the reference to that bound function. Then you try to remove another new function created by a bind call, and it's a no op, since that was never added.
React.createClass auto-binds methods. In ES6, you need to manually bind in your constructor:
#AuthenticatedComponent
class ProductsPage extends React.Component {
static propTypes = {
accessToken: PropTypes.string
};
constructor() {
super();
this._productBatch;
this._productBatchesNum;
this._activeProductBatch;
this._productBlacklist;
this._searchById;
this._searchingById;
this.state = this._getStateFromStore();
// Bind listeners (you can write an autoBind(this);
this._onChange = this._onChange.bind(this);
}
componentDidMount() {
// listener pre-bound into a fixed function reference. Add it
ProductsStore.addChangeListener(this._onChange);
}
componentWillUnmount() {
// Remove same function reference that was added
ProductsStore.removeChangeListener(this._onChange);
}
_onChange() {
this.setState(this._getStateFromStore());
}
There are various ways of simplifying binding - you could use an ES7 #autobind method decorator (e.g. autobind-decorator on npm), or write an autoBind function that you call in the constructor with autoBind(this);.
In ES7, you will (hopefully) be able to use class properties for a more convenient syntax. You can enable this in Babel if you like as part of the stage-1 proposal http://babeljs.io/docs/plugins/transform-class-properties/ . Then, you just declare your event listener methods as class properties rather than methods:
_onChange = () => {
this.setState(this._getStateFromStore());
}
Because the initializer for _onChange is invoked in the context of the constructor, the arrow function auto-binds this to the class instance so you can just pass this._onChange as an event handler without needing to manually bind it.
So I have found the solution, it turns out I only had to assign this._onChange.bind(this) to an internal property before passing it as an argument to removechangelistener and addchangelistener. Here is the solution:
componentDidMount() {
this.changeListener = this._onChange.bind(this);
ProductsStore.addChangeListener(this.changeListener);
this._showProducts();
}
componentWillUnmount() {
ProductsStore.removeChangeListener(this.changeListener);
}
I do not know, however, why this solves the issue. Any ideas?
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the exports component.
I am using the exact same implementation across multiple react components. i.e. this is repeated across several .jsx components.
componentDidMount: function() {
console.log('DidMount- Component 1');
ViewStateStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
console.log('DidUnMount- Component 1');
ViewStateStore.removeChangeListener(this._onChange);
},
_onChange:function()
{
console.log('SetState- Component 1');
this.setState(getStateFromStores());
},
Possible Solution
Currently the following is working out for me, but it has been a little temperamental. Wrap the call back in a function/named-function.
ViewStateStore.addChangeListener(function (){this._onChange});
one might also try
ViewStateStore.addChangeListener(function named(){this._onChange});
Theory
EventEmitter is for some reason getting confused identifying the callback to remove. Using a named function is perhaps helping with that.
Try removing the .bind(this) from your addChangeListener and removeChangeListener. They are already bound to your component when they get called.
I decided it so
class Tooltip extends React.Component {
constructor (props) {
super(props);
this.state = {
handleOutsideClick: this.handleOutsideClick.bind(this)
};
}
componentDidMount () {
window.addEventListener('click', this.state.handleOutsideClick);
}
componentWillUnmount () {
window.removeEventListener('click', this.state.handleOutsideClick);
}
}
This is a es6 problem. React.createClass binds 'this' properly for all function defined inside its scope.
For es6, you have to do something yourself to bind the right 'this'. Calling bind(this) however, creates a new function each time, and passing its return value to removeChangeListener won't match the function passed into addChangeListener created by an earlier bind(this) call.
I see one solution here where bind(this) is called once for each function and the return value is saved and re-used later. That'll work fine. A more popular and slightly cleaner solution is using es6's arrow function.
componentDidMount() {
ProductsStore.addChangeListener(() => { this._onChange() });
}
componentWillUnmount() {
ProductsStore.removeChangeListener(() => { this._onChange());
}
Arrow functions capture the 'this' of the enclosing context without creating new functions each time. It's sort of designed for stuff like this.
As you already got to know the solution here, I will try to explain what's happening.
As per ES5 standard, we used to write following code to add and remove listener.
componentWillMount: function() {
BaseStore.addChangeListener("ON_API_SUCCESS", this._updateStore);
},
componentWillUnmount: function() {
BaseStore.removeChangeListener("ON_API_SUCCESS", this._updateStore);
}
In above code, memory reference for the callback function (ie: this._updateStore) is same. So, removeChangeListener will look for reference and will remove it.
Since, ES6 standard lacks autobinding this by default you have to bind this explicitly to the function.
Note: Bind method returns new reference for the callback.
Refer here for more info about bind
This is where problem occurs. When we do this._updateStore.bind(this), bind method returns new reference for that function. So, the reference that you have sent as an argument to addChangeListener is not same as the one in removeChangeListener method.
this._updateStore.bind(this) != this._updateStore.bind(this)
Solution:
There are two ways to solve this problem.
1. Store the event handler (ie: this._updateStore) in constructor as a member variable. (Your solution)
2. Create a custom changeListener function in store that will bind this for you. (Source: here)
Solution 1 explanation:
constructor (props) {
super(props);
/* Here we are binding "this" to _updateStore and storing
that inside _updateStoreHandler member */
this._updateStoreHandler = this._updateStore.bind(this);
/* Now we gonna user _updateStoreHandler's reference for
adding and removing change listener */
this.state = {
data: []
};
}
componentWillMount () {
/* Here we are using member "_updateStoreHandler" to add listener */
BaseStore.addChangeListener("ON_STORE_UPDATE", this._updateStoreHandler);
}
componentWillUnmount () {
/* Here we are using member "_updateStoreHandler" to remove listener */
BaseStore.removeChangeListener("ON_STORE_UPDATE", this._updateStoreHandler);
}
In above code, we are binding this to _updateStore function and assigning that to a member inside constructor. Later we are using that member to add and remove change listener.
Solution 2 explanation:
In this method, we modify BaseStore functionalities. Idea is to modify addChangeListener function in BaseStore to receive second argument this and inside that function we are binding this to the callback and storing that reference, so that while removing change listener we can remove with that reference.
You can find complete code gist here and source here.