Fix: observer should not modify original componentClass #523
Conversation
Do not alert origin componentClass, Which may cause "Maximum call stack size exceeded" error. Error sample code: https://github.com/zhangciwu/mobx-react-stack-exceeded
@zhangciwu thanks for this PR. Few questions
So instead of writing:
You could simply write:
There is no need to define a new component here every time you are rendering the display name |
For Q1, tested with mobx-react-boilerplate, it's all OK when I edited components render or store functions. For Q2, the below code is like react pure function component, and can be more dynamic, like adding some dynamic options when generates this component. And I use this function for (nearly) root node, should be fine cause when store changes, root would not be re-rendered, only children nodes can be re-rendered |
@@ -367,16 +367,17 @@ export function observer(arg1, arg2) { | |||
throw new Error("Please pass a valid component to 'observer'") | |||
} | |||
|
|||
const target = componentClass.prototype || componentClass | |||
const targetClass = class targetClassName extends componentClass {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering, will targetClassName
now be the reported name in exceptions and React devtools and such?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will, so it's worth implementing .displayName
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, just added displayName
for targetClass
src/observer.js
Outdated
@@ -367,16 +367,17 @@ export function observer(arg1, arg2) { | |||
throw new Error("Please pass a valid component to 'observer'") | |||
} | |||
|
|||
const target = componentClass.prototype || componentClass | |||
const targetClass = class targetClassName extends componentClass {} | |||
const target = targetClass.prototype || targetClass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the right branch ever taken?
Ok, thanks for the clarification! Left some more questions. Might be nice to split the code flow a bit to make explicitly clear which code is taken for a component class / function component / class react.createClass component, etc? |
Actually there is code to tell if it's And after Line 369, For |
Ok, thanks! Will test this extensively and if everything works out fine
make part of the next release
Op ma 20 aug. 2018 om 12:47 schreef zhangciwu <notifications@github.com>:
… Actually there is code to tell if it's Stateless function component at
line 342 and above, and handling the branch after judging.
For component class / class react.createClass, just extend should be
fine, cause extend is actually dealing with prototype of parent class or
class-like-function.
This extend just put componentClass.prototype into
targetClass.prototype.prototype (to prevent componentClass.prototype
being modified), should works fine for both component class / class
react.createClass
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#523 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABvGhCjIb5ULN1RykH7juxMgS0Ne2CLtks5uSpO4gaJpZM4VlFPS>
.
|
This will introduce huge BC break, because of missing statics and methods on new component. As for now, |
@Strate I think that should keep working as is because the new component inherits from the old one? Probably we should make sure we have some unit tests that make sure we don't have regressions here |
Inheritance wouldn't drop class methods, including static ones, just seek one more step on prototype chain. (So it's called inheritance) |
@zhangciwu @mweststrate oh, sorry, I've missed thing that newely created class extends original. |
No problem! I'm happy you critically reviewed the code :) So feel free to
leave any concerns!
Op di 18 sep. 2018 om 08:56 schreef Artur Eshenbrener <
notifications@github.com>:
… @zhangciwu <https://github.com/zhangciwu> @mweststrate
<https://github.com/mweststrate> oh, sorry, I've missed thing that newely
created class extends original.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#523 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABvGhFm1uUIMjqH2iJhg34mdGqGRisdZks5ucJkUgaJpZM4VlFPS>
.
|
When trying to use the changes locally, I get the following error on any class component: Uncaught TypeError: Class constructor Router cannot be invoked without 'new'
at new targetClassName (:8001/static/builds/7.15.1/main.js:1183)
at constructClassInstance (:8001/static/builds/7.15.1/vendor.js:221916)
at updateClassComponent (:8001/static/builds/7.15.1/vendor.js:223400)
at beginWork (:8001/static/builds/7.15.1/vendor.js:223786)
at performUnitOfWork (:8001/static/builds/7.15.1/vendor.js:225785)
at workLoop (:8001/static/builds/7.15.1/vendor.js:225849)
at HTMLUnknownElement.callCallback (:8001/static/builds/7.15.1/vendor.js:216103)
at Object.invokeGuardedCallbackDev (:8001/static/builds/7.15.1/vendor.js:216142)
at invokeGuardedCallback (:8001/static/builds/7.15.1/vendor.js:215999)
at renderRoot (:8001/static/builds/7.15.1/vendor.js:225927) |
Happens when use ES6 targeting typescript, any idea @zhangciwu? Upgrading to this package to babel7 (see #554) doesn't seem to fix the problem |
@mweststrate
So, do you use |
Hey @zhangciwu, sorry, I probably won't be able to create a small repro until Monday, but here are the most interesting details:
Router is declared as: @stateObserver
export class Router extends React.Component<IModelerViewProps> { where export function stateObserver<P extends IReactComponent>(clazz: P): P {
return inject("modelerState")(observer(clazz as any));
} Compiled down to:
|
How do you import If you import from '..somepath/mobx-react/src', |
|
Seems
|
sure var targetClass =
/*#__PURE__*/
function (_componentClass) {
_inherits(targetClassName, _componentClass);
function targetClassName() {
_classCallCheck(this, targetClassName);
return _possibleConstructorReturn(this, _getPrototypeOf(targetClassName).apply(this, arguments));
}
return targetClassName;
}(componentClass);
targetClass.displayName = componentClass.displayName || componentClass.name;
var target = targetClass.prototype || targetClass;
mixinLifecycleEvents(target);
targetClass.isMobXReactObserver = true;
makeObservableProp(target, "props");
makeObservableProp(target, "state");
var baseRender = target.render; Edit: this is the babel 7 compiled version of mobx-react, as it wat supposed to fix several class check issues |
This shall be the root cause Thus, you still need to compile source code to es5 or compile mobxreact into es6 |
Both are not options imho:
|
If need to publish es2015 (preserve class) https://babeljs.io/blog/2018/06/26/on-consuming-and-publishing-es2015+-packages
So much targets make npm community ecology fragile, hope there will be a good solution for this |
As I noticed, mobx 5.x is ES6 only, 4.x is ES5 compatible |
Here's a related issue in babel: babel/babel#9367. |
When use as function, observer modifies original componentClass
It could cause
Maximum call stack size exceeded
exception after componentClass's render method got too many wrappingIt happens to me, and I create a repo to reproduce it: https://github.com/zhangciwu/mobx-react-stack-exceeded
This PR is to fix it by extending (clone) componentClass before modifying, and keeping original class out of altering.
Also appended some test cases.