An element descriptor's .kind property must be either "method" or "field" - mobx

I'm following the documentation for mobx-react-router but upon attempting to run my application I get the following error in the browser:
Uncaught TypeError: An element descriptor's .kind property must be either "method" or "field", but a decorator created an element descriptor with .kind "undefined"
at _toElementDescriptor (app.js:49988)
at _toElementFinisherExtras (app.js:49990)
at _decorateElement (app.js:49980)
at app.js:49976
at Array.forEach (<anonymous>)
at _decorateClass (app.js:49976)
at _decorate (app.js:49958)
at Module../src/App/UserStore.js (app.js:50012)
at __webpack_require__ (bootstrap:19)
at Module../src/index.js (index.js:1)
Here is how I intitialize:
const appContainer = document.getElementById('app');
if(appContainer) {
const browserHistory = createBrowserHistory()
const routingStore = new RouterStore();
const stores = {
users: userStore,
routing: routingStore
}
const history = syncHistoryWithStore(browserHistory, routingStore);
ReactDOM.render(
(
<Provider {...stores}>
<Router history={history}>
< App />
</Router>
</Provider>
),
appContainer);
}
And this is how I use:
#inject('routing')
#inject('users')
#observer
class App extends Component { ...
My UserStore:
import { observable, action, computed } from "mobx"
class UserStore {
#observable users = [];
#action addUser = (user) => {
this.users.push(user)
}
#computed get userCount () {
return this.users.length
}
}
const store = new UserStore();
export default store;
I've tried to Google for this error but it's returning no useful results. Any ideas what I am doing wrong?

If you're using Babel 7 install support for decorators:
npm i -D\
#babel/plugin-proposal-class-properties\
#babel/plugin-proposal-decorators
Then enable it in your .babelrc or webpack.config.js file:
{
"plugins": [
["#babel/plugin-proposal-decorators", { "legacy": true}],
["#babel/plugin-proposal-class-properties", { "loose": true}]
]
}
Note that the legacy mode is important (as is putting the decorators proposal first). Non-legacy mode is WIP.
Reference: https://mobx.js.org/best/decorators.html

While the accepted answer works, for anyone coming here from the same error message but a different context, this is likely because the decorator's signature changed as the proposal progressed.
Using { "legacy": true } uses one method signature, while {"decoratorsBeforeExport": true } uses another. The two flags are incompatible.
You can inspect what's happening with the given example
function log() {
console.log(arguments)
}
class Foo
#log
bar() {
console.log('hello')
}
}
new Foo().bar()
Using {"decoratorsBeforeExport": true } will yield
[Arguments] {
'0': Object [Descriptor] {
kind: 'method',
key: 'bar',
placement: 'prototype',
descriptor: {
value: [Function: bar],
writable: true,
configurable: true,
enumerable: false
}
}
}
Where {"legacy": true } would give you
[Arguments] {
'0': Foo {},
'1': 'bar',
'2': {
value: [Function: bar],
writable: true,
enumerable: false,
configurable: true
}
}
By not using the legacy semantics, writing a decorator is fairly simple. A decorator which returns its 0th argument is a no-op. You can also mutate before returning. Using the new semantics, you can describe a decorator as follows:
function log(obj) {
console.log('I am called once, when the decorator is set')
let fn = obj.descriptor.value
obj.descriptor.value = function() {
console.log('before invoke')
fn()
console.log('after invoke')
}
return obj
}

I changed the observable like so and it works (although not sure why):
import { observable, action, computed } from "mobx"
class UserStore {
#observable users;
constructor() {
this.users = []
}
#action addUser = (user) => {
this.users.push(user)
}
}
const store = new UserStore();
export default store;

Related

How Do I Resolve this "An error was captured in current module: TypeError: e.parse is not a function"

How do I solve this Vue Js error on Shopware 6 Administration. The module is suppose to select a column in the database table.
PS. This is the complete code. I'm trying to read data from the database and view it in the twig template.
const { Component, Mixin } = Shopware;
const { Criteria } = Shopware.Data;
import template from './store-settings-page.html.twig'
Component.register('store-settings-page', {
template,
inject: [
'repositoryFactory'
],
metaInfo() {
return {
title: this.$createTitle()
};
},
data: function () {
return {
entity: undefined,
storeData: null,
entityId: '4e2891496c4e4587a3a7efe587fc8c80',
secret_key: 'hdkkjjsmk538dncbjmns',
public_key: '1destinoDet2123lefmoddfk##$$%O',
}
},
computed: {
storeKeysRepository() {
return this.repositoryFactory.create('store_keys');
},
},
created() {
this.storeKeysRepository
.get(this.entityId, Shopware.Context.api)
.then(entity => {
this.entity = entity;
});
console.log(entity);
},
});
Apologies if my knowledge of Vue & JS is a bit off, based on how I see Shopware codes it, I recommend data to be written like this:
data() {
return {
...
};
}
I would also try to strip your file to the bear minimum to see when the error disappears.
Another thing to check is if you are running a JS file or TS file. Maybe it's having a hard time parsing your file because you are extending store-settings-page and it assumes it should be TypeScript?
this.storeKeysRepository
.get(this.entityId, Shopware.Context.api)
.then(entity => {
this.entity = entity;
console.log(this.entity);
});
This will do the trick

extending of observable subclassing: [MobX] Options can't be provided for already observable objects

When I create a deep structure with few extends I got this kind of error:
Uncaught Error: [MobX] Options can't be provided for already observable objects.
"mobx": "^6.4.2",
"mobx-react-lite": "^3.3.0",
All of code is just dirty example. real structure more complex.
Code example:




import { makeObservable, observable, action } from 'mobx';
class DateMobx {
date ;
constructor(data) {
this.date = new Date(data.date);
makeObservable(this, { date: observable }, { autoBind: true });
}
}
class Todo extends DateMobx {
id = 0;
title = 'title';
text = 'text';
constructor(todo) {
super(todo);
this.id = todo.id;
this.title = todo.title;
makeObservable(this, { id: observable, title: observable, changeTitle: action }, { autoBind: true });
}
changeTitle= (e) => {
const { value } = e.target;
this.title = value;
}
}
class TodoList {
todo = [];
constructor() {
const todoData = [{ id: 1, title: 'title', date: '123' }];
this.todo = todoData.map(item => new Todo(item)); // ERROR happen here
makeObservable(this, { todo: observable }, { autoBind: true });
}
}


Error happen in constructor of TodoList class.

if remove makeObservable from Todo class, error is not reproduced but I need reactivity in that class.

If remove extends DateMobx from Todo class error also is not reproduces (but I have a lot of general classes with basic logic where I need reactivity too).



Why its happen and what should I do if I really need such kind of deep structure ?
Guys on github bring me the right solution
Don't pass the third param ({ autoBind: true }) to makeObservable in subclasses. All options are "inherited" and can't be changed by subsequent makeObservable calls on the same object (this).
options argument can be provided only once. Passed options are
"sticky" and can NOT be changed later (eg. in subclass).
https://mobx.js.org/observable-state.html#limitations

Vue3: how to pass an object from provide to index

I've been using vue.js for a few weeks and I would like to understand how to globally inject to child components an object coming from the server.
When I try to inject the object using inject:['user'] to a child component it returns an empty object.
data() {
return {
user: []
}
},
methods: {
getLoggedUserData() {
axios.get('/api/get-user/' + window.auth.id
).then(response => {
this.user = response.data.user;
});
}
},
provide: {
return {
user: this.user
}
},
created() {
this.getLoggedUserData();
}
The provide option should be a function in this case to get access to this.user property:
export default {
provide() {
return {
user: this.user
}
}
}
For descendants to observe any changes to the provided user, the parent must only update user by subproperty assignment (e.g., this.user.foo = true) or by Object.assign() (e.g., Object.assign(this.user, newUserObject)):
export default {
methods: {
async getLoggedUserData() {
const { data: user } = await axios.get('https://jsonplaceholder.typicode.com/users/1')
// ❌ Don't do direct assignment, which would overwrite the provided `user` reference that descendants currently have a hold of:
//this.user = user
Object.assign(this.user, user) ✅
}
}
}
demo

How to get the this instance in vue 3?

In vue 2+ I can easily get the instance of this as a result I can write something like this,
// main.js
app.use(ElMessage)
// home.vue
this.$message({
showClose: true,
message: 'Success Message',
type: 'success',
})
What should I do for vue 3 as,
Inside setup(), this won't be a reference to the current active
instance Since setup() is called before other component options are
resolved, this inside setup() will behave quite differently from this
in other options. This might cause confusions when using setup() along
other Options API. - vue 3 doc.
Using ElMessage directly
ElementPlus supports using ElMessage the same way as $message(), as seen in this example:
import { ElMessage } from 'element-plus'
export default {
setup() {
const open1 = () => {
ElMessage('this is a message.')
}
const open2 = () => {
ElMessage({
message: 'Congrats, this is a success message.',
type: 'success',
})
}
return {
open1,
open2,
}
}
}
Using $message()
Vue 3 provides getCurrentInstance() (an internal API) inside the setup() hook. That instance allows access to global properties (installed from plugins) via appContext.config.globalProperties:
import { getCurrentInstance } from "vue";
export default {
setup() {
const globals = getCurrentInstance().appContext.config.globalProperties;
return {
sayHi() {
globals.$message({ message: "hello world" });
},
};
},
};
demo
Note: Being an internal API, getCurrentInstance() could potentially be removed/renamed in a future release. Use with caution.
Providing a different method where the idea is to set a globally scoped variable to the _component property of the viewmodel/app or component:
pageVM = Vue.createApp({
data: function () {
return {
renderComponent: true,
envInfo: [],
dependencies: [],
userGroups: []
}
},
mounted: function () {
//Vue version 3 made it harder to access the viewmodel's properties.
pageVM_props = pageVM._component;
this.init();
},

How to correctly test effects in ngrx 4?

There are plenty of tutorials how to test effects in ngrx 3.
However, I've found only 1 or 2 for ngrx4 (where they removed the classical approach via EffectsTestingModule ), e.g. the official tutorial
However, in my case their approach doesn't work.
effects.spec.ts (under src/modules/list/store/list in the link below)
describe('addItem$', () => {
it('should return LoadItemsSuccess action for each item', async() => {
const item = makeItem(Faker.random.word);
actions = hot('--a-', { a: new AddItem({ item })});
const expected = cold('--b', { b: new AddUpdateItemSuccess({ item }) });
// comparing marbles
expect(effects.addItem$).toBeObservable(expected);
});
})
effects.ts (under src/modules/list/store/list in the link below)
...
#Effect() addItem$ = this._actions$
.ofType(ADD_ITEM)
.map<AddItem, {item: Item}>(action => {
return action.payload
})
.mergeMap<{item: Item}, Observable<Item>>(payload => {
return Observable.fromPromise(this._listService.add(payload.item))
})
.map<any, AddUpdateItemSuccess>(item => {
return new AddUpdateItemSuccess({
item,
})
});
...
Error
should return LoadItemsSuccess action for each item
Expected $.length = 0 to equal 1.
Expected $[0] = undefined to equal Object({ frame: 20, notification: Notification({ kind: 'N', value: AddUpdateItemSuccess({ payload: Object({ item: Object({ title: Function }) }), type: 'ADD_UPDATE_ITEM_SUCCESS' }), error: undefined, hasValue: true }) }).
at compare (webpack:///node_modules/jasmine-marbles/index.js:82:0 <- karma-test-shim.js:159059:33)
at Object.<anonymous> (webpack:///src/modules/list/store/list/effects.spec.ts:58:31 <- karma-test-shim.js:131230:42)
at step (karma-test-shim.js:131170:23)
NOTE: the effects use a service which involves writing to PouchDB. However, the issue doesn't seem related to that
and also the effects work in the running app.
The full code is a Ionic 3 app and be found here (just clone, npm i and npm run test)
UPDATE:
With ReplaySubject it works, but not with hot/cold marbles
const item = makeItem(Faker.random.word);
actions = new ReplaySubject(1) // = Observable + Observer, 1 = buffer size
actions.next(new AddItem({ item }));
effects.addItem$.subscribe(result => {
expect(result).toEqual(new AddUpdateItemSuccess({ item }));
});
My question was answered by #phillipzada at the Github issue I posted.
For anyone checking this out later, I report here the answer:
Looks like this is a RxJS issue when using promises using marbles. https://stackoverflow.com/a/46313743/4148561
I did manage to do a bit of a hack which should work, however, you will need to put a separate test the service is being called unless you can update the service to return an observable instead of a promise.
Essentially what I did was extract the Observable.fromPromise call into its own "internal function" which we can mock to simulate a call to the service, then it looks from there.
This way you can test the internal function _addItem without using marbles.
Effect
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import { Injectable } from '#angular/core';
import { Actions, Effect } from '#ngrx/effects';
import { Action } from '#ngrx/store';
import { Observable } from 'rxjs/Observable';
export const ADD_ITEM = 'Add Item';
export const ADD_UPDATE_ITEM_SUCCESS = 'Add Item Success';
export class AddItem implements Action {
type: string = ADD_ITEM;
constructor(public payload: { item: any }) { }
}
export class AddUpdateItemSuccess implements Action {
type: string = ADD_UPDATE_ITEM_SUCCESS;
constructor(public payload: { item: any }) { }
}
export class Item {
}
export class ListingService {
add(item: Item) {
return new Promise((resolve, reject) => { resolve(item); });
}
}
#Injectable()
export class SutEffect {
_addItem(payload: { item: Item }) {
return Observable.fromPromise(this._listService.add(payload.item));
}
#Effect() addItem$ = this._actions$
.ofType<AddItem>(ADD_ITEM)
.map(action => action.payload)
.mergeMap<{ item: Item }, Observable<Item>>(payload => {
return this._addItem(payload).map(item => new AddUpdateItemSuccess({
item,
}));
});
constructor(
private _actions$: Actions,
private _listService: ListingService) {
}
}
Spec
import { cold, hot, getTestScheduler } from 'jasmine-marbles';
import { async, TestBed } from '#angular/core/testing';
import { Actions } from '#ngrx/effects';
import { Store, StoreModule } from '#ngrx/store';
import { getTestActions, TestActions } from 'app/tests/sut.helpers';
import { AddItem, AddUpdateItemSuccess, ListingService, SutEffect } from './sut.effect';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
describe('Effect Tests', () => {
let store: Store<any>;
let storeSpy: jasmine.Spy;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({})
],
providers: [
SutEffect,
{
provide: ListingService,
useValue: jasmine.createSpyObj('ListingService', ['add'])
},
{
provide: Actions,
useFactory: getTestActions
}
]
});
store = TestBed.get(Store);
storeSpy = spyOn(store, 'dispatch').and.callThrough();
storeSpy = spyOn(store, 'select').and.callThrough();
}));
function setup() {
return {
effects: TestBed.get(SutEffect) as SutEffect,
listingService: TestBed.get(ListingService) as jasmine.SpyObj<ListingService>,
actions$: TestBed.get(Actions) as TestActions
};
}
fdescribe('addItem$', () => {
it('should return LoadItemsSuccess action for each item', async () => {
const { effects, listingService, actions$ } = setup();
const action = new AddItem({ item: 'test' });
const completion = new AddUpdateItemSuccess({ item: 'test' });
// mock this function which we can test later on, due to the promise issue
spyOn(effects, '_addItem').and.returnValue(Observable.of('test'));
actions$.stream = hot('-a|', { a: action });
const expected = cold('-b|', { b: completion });
expect(effects.addItem$).toBeObservable(expected);
expect(effects._addItem).toHaveBeenCalled();
});
})
})
Helpers
import { Actions } from '#ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { empty } from 'rxjs/observable/empty';
export class TestActions extends Actions {
constructor() {
super(empty());
}
set stream(source: Observable<any>) {
this.source = source;
}
}
export function getTestActions() {
return new TestActions();
}