InversifyJS icon indicating copy to clipboard operation
InversifyJS copied to clipboard

@lazyInject doesn't work in CRA project

Open BANOnotIT opened this issue 5 years ago • 6 comments

@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)

Link to broken docs example

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()
    )
}

BANOnotIT avatar May 05 '20 14:05 BANOnotIT

Hi @BANOnotIT, I'll take a look as soon as I can

notaphplover avatar Apr 17 '21 12:04 notaphplover

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 avatar Apr 17 '21 13:04 notaphplover

@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 avatar Apr 24 '21 22:04 BANOnotIT

@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:

notaphplover avatar Apr 24 '21 23:04 notaphplover

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?

TheBudgetMan avatar Jun 30 '22 14:06 TheBudgetMan

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...

Koleja avatar Mar 24 '23 05:03 Koleja