Mobx changing observable values outside of action - javascript

I'm getting the following error:
proxyConsole.js:54 Error: [mobx] Invariant failed: Since strict-mode is enabled, changing observed observable values outside actions is not allowed. Please wrap the code in an `action` if this change is intended. Tried to modify: ObservableObject#1.items
at invariant (mobx.module.js:2326)
at fail (mobx.module.js:2321)
at checkIfStateModificationsAreAllowed (mobx.module.js:2890)
at ObservableValue../node_modules/mobx/lib/mobx.module.js.ObservableValue.prepareNewValue (mobx.module.js:796)
at setPropertyValue (mobx.module.js:1673)
at Object.set [as items] (mobx.module.js:1641)
at Store.js:41
at <anonymous>
But I am wrapping the function in an action so I'm a little confused:
import { observable, useStrict, action } from 'mobx';
import Services from './Services';
// ...
getAllTodos: action(() => {
Services.getAllTodos()
.then((response) => {
state.items = response.data;
}).catch((error) => {
console.error(error);
});
}),
Services.js
// ...
getAllTodos () {
return axios.get(root + '/items/');
}
What am I missing here?

A function that alters the observables needs to be wrapped in action, so use it on the callback as well:
getAllTodos: action(() => {
Services.getAllTodos()
.then(action((response) => {
state.items.replace(response.data);
})).catch((error) => {
console.error(error);
});
})

As stated in the MobX docs here:
The action wrapper / decorator only affects the currently running function, not functions that are scheduled (but not invoked) by the current function! This means that if you have a setTimeout, promise.then or async construction, and in that callback some more state is changed, those callbacks should be wrapped in action as well!
Thus, you'd have to wrap the scheduled promise.then here in an action as well, apart from the parent function. (Note that you'd only be able to use the #action on the class-level function)
There are two ways of doing it:
action(
asyncFunction().then(
action((args) => {
// Your function body here
})
)
)
--or--
Use the #action.bound:
#action
asyncFunction().then(
yourStateModifyingFunction();
)
#action.bound
yourStateModifyingFunction() {
// Your function body here
}

Related

How do I make sure one Subcription finishes before another?

globleVariable: any;
ngOnInit() {
// This doesn't work. methodTwo throws error saying "cannot read someField from null. "
this.methodOne();
this.methodTwo();
}
methodOne() {
this.firstService.subscribe((res) => { this.globleVariable = res });
}
methodTwo() {
this.secondService.subscribe((res) => { console.log(this.globleVariable.someField) });
}
As shown above, methodOne set the value of globleVariable and methodTwo uses it, therefore the former must finish running before the latter.
I am wondering how to achieve that.
Instead of subscribing in the methods, combine them into one stream and subscribe to that in ngInit(). You can use tap to perform the side effect of updating globaleVariable that you were previously performing in subscribe().
In the example below the "methods" are converted into fields since there is no reason for them to be methods anymore (you can keep them as methods if you want). Then the concat operator is used to create a single stream, where methodOne$ will execute and then when it's complete, methodTwo$ will execute.
Because concat executes in order, you are guaranteed that globaleVariable will be set by methodOne$ before methodTwo$ begins.
globleVariable: any;
methodOne$ = this.someService.pipe(tap((res) => this.globleVariable = res));
methodTwo$ = this.someService.pipe(tap((res) => console.log(this.globleVariable.someField));
ngOnInit() {
concat(this.methodOne$, this.methodTwo$).subscribe();
}
You can create a subject for which observable 2 will wait to subscribe like below :-
globalVariable: any;
subject: Subject = new Subject();
methodOne() {
this.someService.subscribe((res) => { this.globleVariable = res; this.subject.next(); });
}
methodTwo() {
this.subject.pipe(take(1), mergeMap(() => this.someService)).subscribe((res) => {
console.log(this.globleVariable.someField) });
}
The only way to guarantee a method call after a subscription yields is to use the subscription callbacks.
Subscriptions have two main callbacks a success and a failure.
So the way to implement a method call after the subscription yeilds is to chain it like this:
globleVariable: any;
ngOnInit() {
this.methodOne();
}
methodOne() {
this.someService.subscribe((res) => {
this.globleVariable = res
this.methodTwo(); // <-- here, in the callback
});
}
methodTwo() {
this.someService.subscribe((res) => { console.log(this.globleVariable.someField) });
}
You might want to chain the calls with some other rxjs operators for a more standard usage.
ngOnInit() {
this.someService.method1.pipe(
take(1),
tap(res1 => this.globleVariable = res1)
switchmap(res1 => this.someService.method2), // <-- when first service call yelds success
catchError(err => { // <-- failure callback
console.log(err);
return throwError(err)
}),
).subscribe(res2 => { // <-- when second service call yelds success
console.log(this.globleVariable.someField) });
});
}
Please remember to complete any subscriptions when the component is destroyed to avoid the common memory leak.
my take,
so it's a bit confusing when you use same service that throws different results, so instead of someService I used firstService and secondService here.
this.firstService.pipe(
switchMap(globalVariable) =>
this.secondService.pipe(
map(fields => Object.assign({}, globalVariable, { someField: fields }))
)
)
).subscribe(result => {
this.globalVariable = result;
})
What I like about this approach is that you have the flexibility on how you want to use the final result as it is decoupled with any of the property in your class.

How to assign response object to state

I could get data from nodejs backend to react frontend using axios. But I can't assign that object to state object in React.
getData=()=>{
let responseToHandle;
axios.get("http://localhost:9000/api/getData")
.then(function (response)
{
console.log(response.data);//This is working
responseToHandle = response.data;
console.log(responseToHandle);//This is working
this.setState({
data: responseToHandle}, () => {
console.log(this.state.data)
})
})
// HERE IS MY STATE OBJECT
state={
data:[]
}
axios call and this.setState are both asynchronous.
You must add your desired code inside a callback:
this.setState({
data: responseToHandle
}, () => {
console.log(this.state.data)
// Now you can use this.state.data
});
Edit:
You also need to change
axios.get("http://localhost:9000/api/getData").then(function (response) { ... })
to
axios.get("http://localhost:9000/api/getData").then(response => { ... })
Without arrow function the scope inside .then() is the function scope, different from component scope, which means using this gives you a different value.
This code won't work properly:
console.log(this.state.data)
You called a console.log function synchronously right after a promise declaration. Promise works asynchronously.
So in order to check a new state, you should call console.log in render() method, or the best option is to use componentDidUpdate() method.
E.g.
componentDidUpdate(prevProps, prevState) {
console.log(`Previous data: ${prevState.data}`);
console.log(`New data: ${this.state.data}`);
}

Nested describe and behaviour of dynamically created "it"s

I have nested describes in my tests and as usual I am using some beforeEach and before in describes. And one of my describe function calls helper function which creates dynamic tests (DRY). And mocha runs description of nested describe before beforeEach method. And my dynamically created it has comp as undefined.
const checkProps = (comp, propName, expectedvalue) => {
it(`${comp} should have ${propName} equal to ${expectedvalue}`, () => {
expect(comp.prop(propName)).to.equal(expectedvalue);
});
};
describe('Component', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<MyComponent />);
});
describe('prop checking', () => {
checkProps(wrapper, 'title', 'SomeTitle');
});
});
What is the best way todo it? Thanks in advance.
What happens
The Mocha Run Cycle runs all describe callback functions first (...which is also true for other testing frameworks such as Jest and Jasmine).
Then it runs the before hooks, then beforeEach hooks, and finally the it callbacks.
So checkProps runs as part of running the initial describe callbacks, and at that point wrapper is undefined, so as you have noticed the test description says undefined should have....
The beforeEach hook runs before the it callback function runs...but it redefines wrapper so when the it callback runs comp is still undefined and the test fails:
1) Component
prop checking
undefined should have title equal to SomeTitle:
TypeError: Cannot read property 'prop' of undefined
at Context.prop (test/code.test.js:15:19)
Solution
A couple of things need to be changed:
The component name needs to be available when it runs and at that point wrapper doesn't exist yet so you'll have to pass the name yourself.
If you pass an object to checkProps then you can set a wrapper property on the object during beforeEach and access that wrapper property within your test since the object is never redefined.
Here is a working test that should get you closer to what you are trying to do:
import * as React from 'react';
import { shallow } from 'enzyme';
const MyComponent = () => (<div title="SomeTitle">some text</div>);
const checkProps = (name, obj, propName, expectedvalue) => {
it(`${name} should have ${propName} equal to ${expectedvalue}`, () => {
expect(obj.wrapper.prop(propName)).to.equal(expectedvalue); // Success!
});
};
describe('Component', () => {
const obj = {};
beforeEach(() => {
obj.wrapper = shallow(<MyComponent />);
});
describe('prop checking', () => {
checkProps('MyComponent', obj, 'title', 'SomeTitle');
});
});

unable to assign data from response data in Angular

I have a function that should return an array of objects when called. The function looks like this
loadTodo(): Todo[]{
var data
this.http.get(`${this.API_URL}todos`).toPromise().then(res => {
data = res.json()
}, error => {
console.log(error)
})
return data}
This results in unexpected behavior where data variable gets assigned correctly inside the success response block but is undefined when accessed outside the response block.
The function is assigned to a variable with type Todo[] and is invoked immediately when the variable is declared. I am quite new to TypeScript and Angular but not to JavaScript. Am I missing something with scope/closure of the function or is this issue related to TypeScript/Angular?
Whole class looks like this:
export class TodoDataService {
API_URL: String = 'http://localhost:3000/'
todos: Todo[] = this.loadTodo();
constructor(private http: Http) {
}
loadTodo(): Todo[]{
this.http.get(`${this.API_URL}todos`).toPromise().then(res => {
this.parcedTodos = res.json()
console.log('inside function')
console.log(this.parcedTodos)
}, error => {
console.log(error)
})
console.log('outside function')
console.log(this.parcedTodos)
return this.parcedTodos
}
}
http.get() is asynchronous, which means that when you try to print parcedTodos outside the then callback, it will still be undefined.
Asynchronous programming in JS
It is happening because http calls are asynchronous.
You need to make sure you are accessing data only after call is completed.
export class TodoDataService {
API_URL: String = 'http://localhost:3000/'
todos: Todo[] = this.loadTodo();
constructor(private http: Http) {
}
loadTodo(): Todo[]{
this.http.get(`${this.API_URL}todos`).toPromise().then(res => {
this.parcedTodos = res.json()
console.log('inside function')
console.log(this.parcedTodos)
}, error => {
console.log(error)
},
{
console.log(this.parcedTodos);
// This is where your call gets completed. Here you can access assigned data or call another function where you can access data.
})
console.log('outside function')
console.log(this.parcedTodos) // This is called before asynchronous call is completed. Thats why it is undefined yet.
return this.parcedTodos
}
}
Hope this helps.
this.http.get(whatever) is an async call.
Your data is undefined because you're accessing it before it is actually initialized. i.e. you're initializing it inside the success handler (the first argument to then), and probably are accessing it before initialization.
All you need to do is make sure you're doing so after the success or error handler. use Observable
I think that using res.json() not is neccesary because angular pipes already doing this works. Do you try to assign to variable res directly?
As others friends says, you are doing bad some things.
First: you must read about asynchronous methods
Second: use Observables importing rxjs/Observable; and follow its callbacks flow
Example
export class TodoDataService {
API_URL: String = 'http://localhost:3000/'
todos: Todo[] = this.loadTodo();
constructor(private http: Http) {
}
loadTodo() : Observable<Todo[]>{
return this.http.get(`${this.API_URL}todos`);
}
}
And other class consummes this method
todoDataService.loadTodo().subscribe(
(response) => {
console.log("Future response", response);
}
);

Why isn't the following expect ... toThrowError logging anything in my terminal?

I have a helper that just throws an error:
export const checkPanoramaFormat = (panorama) => {
if (!panorama.objectId) {
throw new Error('panorama id is required')
}
}
This is my test:
import {
checkPanoramaFormat
} from '#/common/helpers'
describe('checkPanoramaFormat', () => {
const panorama = { anotherProp: '1' }
it('creates clone', () => {
expect(checkPanoramaFormat(panorama)).toThrowError('hshshs')
})
})
I expected the terminal to show me what was expected and what I got. But I got nothing. In fact, Jest isn't telling me anything:
Why is this and how to fix it?
Try instead:
expect(() => {
checkPanoramaFormat(panorama)
}).toThrow('hshshs')
If you call the function immediately as the expect argument, it will throw the exception before the assertion. It is not inside a try/catch block. You should instead pass a function handler to expect to be called only on assertion. I enclosed it in an arrow function, but it can be called other forms such as:
expect(myFunc) // no arguments
expect(myFunc.bind(this, arg1, arg2)) // using .bind to set arguments for later calling

Categories

Resources