11

I have this fiddle

let m = new Mine();
this.setState(m, () => {
    console.log('1:', m instanceof Mine, m.x, m.meth);
    // => 1: true 123 function meth() {}
    console.log('2:', this.state instanceof Mine, this.state.x, this.state.meth);
    // => 2: false 123 undefined
});

As you can see I create an instance of the Mine class and then set state in a react component with that instance.

I would expect this.state to contain exactly that instance but while the instance properties that are set in the constructor are available I can't access any of the class methods on that instance.

The test in the fiddle shows that this.state is not an instance of the class Mine.

Does anybody understand what is going on or is this unintended behavior?

Vlad
  • 385
  • 2
  • 13

3 Answers3

5

After more investigation I found out the reason why that happens.

The function _processPendingState from react uses Object.assign to set the new state, so since the target object is a new object (different than what is passed to setState) the new state loses the quality of being an instance of the "Mine" class.

And because Object.assign only copies own enumerable properties from the sources to the target the new state also won't have the class methods.

If in the fiddle we replace the line...

let m = new Mine();

with...

let m = {x: 123};
Object.defineProperty(m, 'meth', {
    enumerable: false,
    get() { return function() {}; }
});

we still don't have the "meth" property on the resulting state. Even if "m" owns the "meth" property it is not enumerable.

Vlad
  • 385
  • 2
  • 13
  • this Github issue seems to talk (briefly) about the same issue: https://github.com/facebook/react/issues/5425 – manroe Aug 28 '18 at 21:55
3

The best solution is to surface the method as an arrow function:

class Blah {
  constructor() {
    // no definition here for surfacedMethod!
  }

  surfacedMethod = () => {
    // do something here
  }

}

Then you can set instances of this class in setState and use their methods as if they were attributes set on the instance.

// other component innards
this.setState(state => ({blah: new Blah()}))

// later
this.state.blah.surfacedMethod();  // this will now work
jorf.brunning
  • 452
  • 4
  • 9
0

In such case use replaceState, it should work.

Igorsvee
  • 4,101
  • 1
  • 25
  • 21
  • This method is not available on ES6 class components that extend React.Component. It may be removed entirely in a future version of React. – Kokovin Vladislav Jul 20 '16 at 09:58
  • With replaceState it does work but as Ultro mentioned it may be removed in the future, so I am reluctant to use it. – Vlad Jul 20 '16 at 12:26
  • @KokovinVladislav I need exactly this method. What is the alternativ in 2017? I can't seem to find any good way to keep the instance of a class... Every good function react had is removed one by one – E Sturzenegger Mar 22 '17 at 12:42