It seems like render is called before componentWillMount.
looking in the code it looks like things are happening in this order:
Its not as in react spec, but more importantly calling the hook before everything makes a lot of sense to me.
otherwise there is no hook that allows touching the component before the first render.
I tried to play with the code a bit, but I couldn't figure out how to move the hook to the right place.
mainly this line
rendered = hook(component, 'render', props, state, context);
is positioned before this
if (component.base) deepHook(inst, 'componentWillMount');
setComponentProps(inst, childProps, NO_RENDER, childContext);
renderComponent(inst, DOM_RENDER);
if (component.base) deepHook(inst, 'componentDidMount');
Looks like a valid bug, yup. I don't tend to rely on componentWillMount() being fired prior to render, since technically it only needs to fire before mounting (render and mount are separate stages), but if React always invokes componentWillMount prior to the first render, it'd be nice to have the same behavior.
In terms of working within the current behavior though, you could just use a constructor as a hook. It's called when the component is instantiated, prior to any other lifecycle methods or rendering.
Still doesn't work in 5.0.0-beta.1. Unfortunately, constructor isn't a real replacement. Many react components/mixins/decorators rely on componentWillMount to be called before render. It's a lot of trouble to go through each and every one of them and rewrite them from using mixins to extending the component class instead, because there is no other way. You can't override constructor on the prototype via decorator, or create a mixin that does it. I've stumbled upon this bug multiple times. componentWillMount is essentially useless if it's not called before render.
Good to know. I agree about the current utility of willmount, just need to find some time to implement the mount lifecycle using a queue.
mobx-react depends on componentWillMount being called before render. Following code might help someone to use mobx-react with current preact and preact-compat.
It's a module which re-exports mobx-react API, but its' observer() moves mobx-react's componentWillMount logic to the object constructing moment.
Some notes:
mobx-react depends on React-specific API, so use preact-compat and setup aliases as recommended by preact-compat README.mobx-react counts on shouldComponentUpdate() not being called in forceUpdate()'ing and doesn't work in other case. preact provides required behavior only in versions with fixed developit/preact#158, but stable preact-compat currently depends on preact version without that fix.console.log'ged instantiated object of that class.import mobxReact from 'mobx-react';
export default {
...mobxReact,
observer,
}
export * from 'mobx-react';
const observerMixinFunctionNames = [
'componentWillMount',
'componentWillUnmount',
'componentDidMount',
'componentDidUpdate',
'shouldComponentUpdate',
];
const mobxReactObserverMixin = (() => {
// mobx-react's observer() works both as a component class decorator and a
// higher-order function for wrapping statless component function. It
// differentiates them by several checks, but setting isReactClass
// property is enough to present function as a component class.
//
// It matters because observer() creates new class for component function
// with some additional methods beside mixin methods, but for component
// class without any instance methods it will just mix in mixin functions.
const blankClass = class { static isReactClass = true; },
protoWithMixin = mobxReact.observer(blankClass).prototype;
return observerMixinFunctionNames.reduce((reconstructedMixin, fnName) => {
reconstructedMixin[fnName] = protoWithMixin[fnName];
return reconstructedMixin;
}, {});
})();
export function observer(componentClass) {
const mixinTarget = (componentClass.prototype || componentClass),
originalComponentWillMount = mixinTarget.componentWillMount,
decoratedClass = mobxReact.observer(componentClass);
// Restore original componentWillMount (or its' absence).
if (originalComponentWillMount) {
mixinTarget.componentWillMount = originalComponentWillMount;
} else {
delete mixinTarget.componentWillMount;
}
// Setup MobX reaction in object constructing.
const observableClass = class extends decoratedClass {
constructor() {
super();
mobxReactObserverMixin.componentWillMount.apply(this);
}
};
// Return class with class name set to the original component class name.
return Object.defineProperty(observableClass, 'name', {
...Object.getOwnPropertyDescriptor(observableClass, 'name'),
value: componentClass.name,
});
}
That is some great work @leonidborisenko. Someone submitted a patch to add isReactClass to preact-compat that was merged but needs a release (can do that tomorrow).
I'm really hoping to wrap up 5.0.0 in the next few days, which will include fixing the lifecycle event timing. We've already fixed a few other similar issues (relating to unmount invocation for nested components) so things are set up to be solved relatively easily. Once that's merged, I'll be using your shim as a guide for what is left to cover to get MobX working natively (directly) with preact-compat. Then we can do something like what preact-redux does and drop the -compat dependency entirely (yay!).
@developit, to be clear, when stable preact-compat will depend on preact version containing #96 (this bug) and #158 fixes, mobx-react should work with preact-compat out of the box (given that react and react-dom aliases are setup) for both:
Component from preact-compatThen this shim will be obsolete.
mobx-react just provides convenient decorator for wrapping component's render() function with MobX Reaction, so in some sense MobX will already work directly with preact-compat. Nevertheless I'm looking forward to any of your enhancements of MobX integration.
I'm happy to say I believe I have a fix locally for the timing of componentWillMount and componentDidMount. In my testing, this fix produces identical event timing to React! 馃槉
Very nice! :+1:
I think this being fixed probably warrants a party of some kind. Cheers, all! 馃嵒
Released in 5.2.0-beta.0.
Most helpful comment
I think this being fixed probably warrants a party of some kind. Cheers, all! 馃嵒