redux-orm: Getting error "Class constructor Record cannot be invoked without 'new'"

Hello,

Just trying out redux orm and I am getting the following error after a basic Model.create

TypeError: Class constructor Record cannot be invoked without 'new'

  at new SessionBoundModel (../node_modules/redux-orm/lib/Session.js:77:124) <-- redux-orm internal
  at Function.create (../node_modules/redux-orm/lib/Model.js:663:28) <-- session.Record.create
  at Object.RECORD_ADDED (state/orm/models.js:39:16) <-- event inside Record.reducer
  at Function.reducerForRecord [as reducer] (state/orm/models.js:63:28)  <-- Record.reducer

Here is my model:

// My domain is a database; record is a row which has many fields; fields can
// be of many types but they only exist in the context of a record; if a record is deleted
// fields must also be deleted.

export class Record extends Model {}

Record.modelName = 'Record'

Record.fields = {
  fields: many('Field')
}

// Please note that makeReducer() isn't doing anything fancy; just matches event.type
// to each specific function and passes session as third argument instead of the model class   
// Error is same if reducer is written with a switch statement
Record.reducer = makeReducer(Record, {

  [events.RECORD_ADDED] (state, event, session) {
    session.Record.create({ id: event.id })    // <--- this is where the error occurs
  },

  [events.FIELD_ADDED] (state, event, session) {
    session.Record.withId(event.record).fields.add(event.id)
  },

  [events.FIELD_REMOVED] (state, event, session) {
    const field = session.Field.withId(event.id)
    const record = session.Record.withId(field.record)

    record.fields.remove(event.id)
  }

})

This also happens with other models (for instance the Field model referenced above). I also tried installing redux-orm v0.8.3 version to see if this was a regression.

Since all the examples show the create static method being called to initialize models, I have not tried attempted to call new directly.

Hope that is clear, let me know if you need more info.

Thanks for the package; I hope it will be useful.

About this issue

  • Original URL
  • State: open
  • Created 8 years ago
  • Reactions: 1
  • Comments: 37 (8 by maintainers)

Commits related to this issue

Most upvoted comments

Update: I found a working solution for my own case (i.e. a non-transpiled Electron app).

Basically, redux-orm’s transpiled ES5 code clashes with vanilla ES6 code here — ES6 classes can’t be invoked without new, but that’s what the library is trying to do. So using a Proxy object is a good workaround. (See the MDN docs.)

Here’s a helper function that’ll overwrite klass.apply() so when it is called, it’ll correctly invoke the class:

function proxyClassForORM(klass) {
  return new Proxy(klass, {
    apply(target, thisArg, rest) {
      return new target(...rest);
    },
  });
}

In your models.js, wrap your original class objects before registering them:

// Before
const orm = new ORM();
orm.register(Record);

// After
const orm = new ORM();
orm.register(
  proxyClassForORM(Record)
);

Works for me, YMMV. @tommikaikkonen, this might be something for the docs (if you approve of the workaround and explanation).

To give higher perspective, this issue prevents redux-orm code from being tested in create-react-app environment (which uses jest with this babel configuration).

I would also like to link relevant discussion from babel issues: https://github.com/babel/babel/issues/4269. As far as I can tell from the resolution, it is not possible for transpiled classes to extend from native classes.

With that in mind, I think we should discuss a viable long term solution. Perhaps someone with more knowledge about the codebase than myself can weight in with a conceptual outline of the required changes?

This is the line which causes an error: https://github.com/tommikaikkonen/redux-orm/blob/1b867835f85e542d6d8b64079b7d3f870acdde71/src/Session.js#L31

Huh. So, I actually ran into this myself recently, but didn’t think to search for it exactly. I was able to resolve this by tweaking my Webpack config to have it pull in the redux--orm/src folder so that I used the uncompiled source myself, rather than the precompiled source. In my Webpack config, I added:

    alias : {
        "redux-orm" : "redux-orm/src"
    }

This somewhat worked for me, but moved the problem somewhere else.

First of all, I didnt seem to run into this problem in my regular code. I only had this occur when writing a test. Once I added the fix above the Model.create() did work but then I go this calling the reducer of the model:

TypeError: Cannot read property 'applyUpdate' of undefined

      at Form.update (node_modules\redux-orm\lib\Model.js:493:26)
      at Function.reducer (src\models\index.js:43:48)

Architecturally, I think the issue is that Model is full of static methods that reference a static this.session field.

I guess there’s a couple aspects that need to be solved:

  • What use cases are there for having multiple Sessions active at once?
  • Assuming we still want to allow multiple simultaneous Sessions, is there a way to have a static method / class definition determine what the correct Session instance is as it’s running?

There’s gotta be something that can be done with, I dunno, unique IDs and lookup tables somehow.

Clearly this is still a pain point.

Is there an alternative way we can implement the overall behavior of Redux-ORM and sessions, without relying on this dynamic subclassing approach?

Alright, v0.10.2 should not require transpilation for ES2017-compatible environments anymore. @Amareis @markerikson @sheppard Would you mind trying the new release? The resolve.mainFields field can be removed now for anyone using babel-preset-env.

no working idea yet, let’s leave this issue open to remember “why”.

Sent from iPhone

On Jul 1, 2018, at 06:39, haveyaseen notifications@github.com wrote:

Reopened #53.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

It would be great if we could solve this on an architectural basis as ES classes are downright backwards-incompatible. However, for now I think the following may suffice (transpiling and testing with ES6 works).

src/Session.js#L29:

this.sessionBoundModels = this.models.map((modelClass) => {
    function SessionBoundModel() {
        return Reflect.construct(modelClass, arguments, SessionBoundModel);
    }
    Reflect.setPrototypeOf(SessionBoundModel.prototype, modelClass.prototype);
    Reflect.setPrototypeOf(SessionBoundModel, modelClass);

    Object.defineProperty(this, modelClass.modelName, {
        get: () => SessionBoundModel,
    });

    SessionBoundModel.connect(this);
    return SessionBoundModel;
});

Source: https://esdiscuss.org/topic/extending-an-es6-class-using-es5-syntax#content-2

The solution with Proxy doesn’t work on our side because it cannot be transpiled using Babel.

Still reproducible here with babel@6 & redux-orm@0.10.2:

  "presets": [
    [
      "env",
      {
        "modules": false,
        "targets": {
          "browsers": [
            "last 2 Chrome versions",
            "last 2 Firefox versions",
            "last 1 Safari version"
          ]
        },
        "useBuiltIns": true
      }
    ],
    "react"
  ],

@haveyaseen yeah, it’s working now without mainFields, big thanks!

Heh, one does not simply add preset-stage-2 to babelrc because babelrc from redux-orm take precedence (also, I already have stage-0). But mainFields thing did the trick, thank you mate!

That is very unfortunate to hear so I have looked into it some more. If it’s just the spread operator causing the issue, then you can probably fix it by

  1. requiring babel-preset-stage-2 using npm install babel-preset-stage-2 or yarn add babel-preset-stage-2 and then
  2. adding 'stage-2' to babel-loader’s presets array in webpack.config.js (or .babelrc).

And luckily there is also an alternative to revert to the old behavior! You can apparently ignore the new pkg.module key and use the transpiled files in lib/ instead by adding a resolve.mainFields key to your webpack.config.js like this:

module.exports = {
  // ...
  resolve: {
    // by default this includes 'module' which is what we want to avoid
    // see the Webpack docs if you really need 'browser'
    mainFields: ['browser', 'main'],
  },
};

If you choose the latter option, make sure to undo the addition that removes, redux-orm from babel-loader’s excludes.

Hope this helps you so you don’t have to miss out on our new performance improvements!

I tried adding "module": "src/index.js" to package.json (see #192). This fixed the issue for me with the latest create-react-app and babel. Does the fix work for anyone else?

@carlo Thanks for sharing your solution! Update: It unfortunately does not work properly. I’m using redux-orm in a server-only (node 8) environment.

@nealoke yes, I noticed you had a different issue which caused the same error message.