@lazyInject doesn't work in CRA project
@lazyInject is not reached
Expected Behavior
I mark some property with @lazyInject. Then I use it like this.somproperty and get an instance of required class from container.
Current Behavior
I mark some property with @lazyInject. Then I use it like this.somproperty and get undefined allways. In any react compoenent or even in example from documentation.
Problem
I mentioned that probpem in #1212 with TS, but in CRA the property bubling broken by implementation of babel-plugin-transform-class-properties.
Temproray solution
Call delete this.something at the end of constructor call
Steps to Reproduce (for bugs)
Your Environment
dependencies: {
"reflect-metadata": "^0.1.13",
"typescript": "~3.7.2",
"inversify": "^5.0.1",
"inversify-inject-decorators": "^3.1.0",
"customize-cra": "^0.9.1",
"react-app-rewired": "^2.1.5",
"react-scripts": "3.4.1"
}
config-overrides.js:
const {
override,
addDecoratorsLegacy,
disableEsLint
} = require('customize-cra')
module.exports = {
webpack: override(
disableEsLint(),
addDecoratorsLegacy()
)
}
Hi @BANOnotIT, I'll take a look as soon as I can
It is kind of tricky, I let you my conclussions:
How does the compiled code looks like:
var _initializerDefineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/initializerDefineProperty"));
...
var Book = (_dec2 = lazyInject("PrintService"), (_class2 = (_temp = /*#__PURE__*/function () {
function Book(author, summary) {
(0, _classCallCheck2.default)(this, Book);
(0, _initializerDefineProperty2.default)(this, "_printService", _descriptor, this);
this._author = author;
this._summary = summary;
}
(0, _createClass2.default)(Book, [{
key: "print",
value: function print() {
this._printService.print(this);
}
}]);
return Book;
}(), _temp), (_descriptor = (0, _applyDecoratedDescriptor2.default)(_class2.prototype, "_printService", [_dec2], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
})), _class2));
...
It seems the compilation process ends adding a call to _initializerDefineProperty2.default, whose source file is:
function _initializerDefineProperty(target, property, descriptor, context) {
if (!descriptor) return;
Object.defineProperty(target, property, {
enumerable: descriptor.enumerable,
configurable: descriptor.configurable,
writable: descriptor.writable,
value: descriptor.initializer ? descriptor.initializer.call(context) : void 0
});
}
This completelly breaks @lazyInject, which had already established a proxy to the property's getter through _proxyGetter:
function _proxyGetter(
proto: any,
key: string,
resolve: () => any,
doCache: boolean
) {
function getter() {
if (doCache && !Reflect.hasMetadata(INJECTION, this, key)) {
Reflect.defineMetadata(INJECTION, resolve(), this, key);
}
if (Reflect.hasMetadata(INJECTION, this, key)) {
return Reflect.getMetadata(INJECTION, this, key);
} else {
return resolve();
}
}
function setter(newVal: any) {
Reflect.defineMetadata(INJECTION, newVal, this, key);
}
Object.defineProperty(proto, key, {
configurable: true,
enumerable: true,
get: getter,
set: setter
});
}
So, what happens?
The compile process (unrelated to inversify) calls Object.defineProperty overriding inversify getter proxy used to lazy inject the dependency.
What to do?
No good fix comes to my mind, I would suggest changing your project's compilation / bundling process in order to stop overriding the inversify's getter proxy. I wouldn't use lazyInject in this scenario, if I can figure out a solution I'll write you.
Having said that, it seems it's not an issue related to inversify, we can not deal with injected code from a non related process. Maybe I'm missing something
@notaphplover I think it's a good idea to warn people not to use it in React class components in CRA. For example in documentation there's a phrase that can mislead people.
@BANOnotIT I also think it would be a good idea to warn people in order to not use @lazyInject on these cases. I'll try to submit a PR updating docs in the next week, of course feel free to go for it if you want :smiley:
This can be reproduced with setting useDefineForClassFields which is getting more wildly used. For example, MobX 6.x requires this setting to support decorators, making it incompatible with @lazyInject. It will be a default flag when targeting es2022 or higher. Are there any plans to update @lazyInject to work with this new flag?
Anyone found workaround for this issue? I need to upgrade library that uses heavily @lazyInject and I'm getting to the point of refactoring it...