0

This question should be an easy riddle for TypeScript/React hackers.

I have a React component that passes a class-object to a child-component. Within the child-component, I call a method on the class-object. Those two components look as follows:

class ParentComponent extends React.Component<{}, Foo> {
  constructor(props: any) {
    super(props);
    this.state = new Foo();
  }
  render() {
    return (<ChildComponent {...this.state} />);
  }
}

class ChildComponent extends React.Component<Foo, {}> {
  render() {
    this.props.fooMethod(); // TypeError or not? fooMethod is not a function?
    return (<div/>);
  }
}

Furthermore, I have two different implementations of Foo. One of them works, whereas the other one throws a TypeError in the child-component. Can you explain why only one of those Foo implementations works?

First Foo implementation:

class Foo {
  constructor() {
    this.fooMethod = function(): void {};
  }
  fooMethod: () => void;
}

Second Foo implementation:

class Foo {
  fooMethod(): void {};
}
Mike76
  • 899
  • 1
  • 9
  • 31

1 Answers1

3

Actually, the problem has nothing to do with React.

What is happening is that those two implementations behave slightly different one from each other.

The following code:

class Foo {
  constructor() {
    this.instanceMethod = function(): void {};
  }
  fooMethod: () => void;
}

const fooInstance = new Foo();

Declares a class with an instance method instanceMethod.

The following:

class Foo {
  prototypeMethod(): void {};
}

const fooInstance = new Foo();

Declares a class with a prototype method prototypeMethod.

When you use object destructuring syntax {...this.state} only own properties and methods (non prototype) are assigned.

So that is the reason why the first implementation works while the second throws an error.

Ernesto Stifano
  • 3,027
  • 1
  • 10
  • 20
  • 2
    Just a minor nitpick that was brought up as a topic of discussion between me and the asker of this question [in another post](https://stackoverflow.com/q/61133292/1541563). The problem _is_ related to [React](https://github.com/facebook/react/blob/22cab1cbd6ad52925828643c8c6d0d4fd7890c54/packages/react/src/ReactElement.js#L373-L380), and not to the [JSX spread element](https://babeljs.io/repl#?code_lz=DwMQ9mAEDeB08CMCGAnAvpA9APiA). The spread element does not perform the copy operation on the object that gets passed to `React.createElement()`, it's performed internally by React. – Patrick Roberts Apr 10 '20 at 03:48
  • Besides that detail, this is a good answer and correctly explains the distinction between the two implementations of `Foo`. – Patrick Roberts Apr 10 '20 at 03:49
  • @PatrickRoberts you are right that in this case it is React that will internally perform the copy operation. However, I do not see the "problem" or "issue" in the first place. Code behaves as expected. We cannot make any conclusion from the transpiled code (in fact, spread operator is removed), we can only hope that it respects the original code behaviour and related ECMAScript specifications. – Ernesto Stifano Apr 13 '20 at 21:36
  • JSX is not, and never will be in the ECMAScript specification. `{...this.state}` is not destructuring syntax, it's a JSX spread attribute. In React documentation, they make reference to ECMAScript 2015 spread syntax for reasons of familiarity, but spread attributes are not bound to the semantics of spread syntax. – Patrick Roberts Apr 13 '20 at 21:39
  • @PatrickRoberts Indeed, I'm not talking about JSX but about the spreed operator, specifically "Spread in object literals" which is part of the "[Rest/Spread Properties for ECMAScript proposal (ES2018)](https://github.com/tc39/proposal-object-rest-spread)". Standard or not, its behaviour is well defined and it is expected to be matched by implementors (Babel, JSX, React, etc.) as it is in this case. – Ernesto Stifano Apr 13 '20 at 21:50
  • And again, JSX spread attributes are _not_ an implementation of ECMAScript spread syntax! It resembles spread syntax for reasons of familiarity, but the semantics of the syntax are not bound by the ECMAScript specification! – Patrick Roberts Apr 13 '20 at 21:51
  • Anyway, the statement that "the problem has nothing to do with React" is categorically wrong, as the copy operation is performed as an internal implementation detail in `React.createElement()`, regardless of what semantics an implementation of JSX such as Babel chooses to use for a JSX spread attribute. – Patrick Roberts Apr 13 '20 at 22:11