`data` is undefined in apollo useMutation Promise - apollo

Why is data undefined in Promise even though it appears in html below?
I dont want to use .then((data) =>
const LoginPage: React.SFC<{}> = () => {
const [login, { loading, data, error }] = useMutation<Q, V>(MUTATION)
const onSubmit = event => {
event.preventDefault()
login({
variables: {
email,
password
}
}).then(() => {
console.log(data) // <-- undefined
}).catch(() => {
console.log(error) // <-- OK
})
}
return <div>
<form onSubmit={onSubmit}> ... </form>
{data && <pre>{JSON.stringify(data)}</pre>} // <-- OK
</div>
}

A variation of the original question. Not sure if it works as intended though because the result of the mutation's Promise is undefined. Thanks.
const [login] = useMutation(MUTATION);
const onSubmit = event => {
event.preventDefault()
login({
variables: {
email,
password
}
}).then((result) => {
console.log(result) // <-- undefined
}).catch(() => {
console.log(error) // <-- OK
})
}

This is working as intended. The state update caused by calling the state updater function returned by a useState hook is asynchronous. useState is used under the hood by useMutation to manage the various bit of state returned by the hook (data, loading, error, etc.) so this state will also update asynchronously. This means the Promise returned by login can resolve, and in doing so, the appropriate state will be updated, just not immediately. Once it is updated, the component is rerendered and you see the updated value rendered in your component.
If you need to utilize the data returned by the mutation in some way as soon as the Promise resolves, then you need to do so by extracting it from the resolved value of the Promise instead of relying on the component state.

Related

How to find out why a function passed the test in Jest?

Is there a way to show why tested function can pass?
When I follow Jest test Async Code section
It says:
Be sure to return the promise - if you omit this return statement,
your test will complete before fetchData completes.
And my code is:
function add1(n) {
return new Promise((res, rej)=>{
res(n+1)
})
}
test('should add 1', function() {
expect.assertions(1)
//////////////////////////// I did not use RETURN here
add1(10).then((n11)=>{
expect(n11).toBe(11)
})
});
This still passed, I want to know how this can pass?
The Promise resolves immediately and synchronously so the then gets called immediately and the expect has run before the test finishes. (then callbacks run immediately if the Promise has already resolved)
If you use setTimeout to keep the Promise from resolving immediately and synchronously then the test fails unless you return the Promise:
function add1(n) {
return new Promise((res, rej) => {
setTimeout(() => { res(n + 1) }, 0); // use setTimeout
})
}
test('should add 1', function () {
expect.assertions(1)
// PASSES only if Promise is returned
return add1(10).then((n11) => {
expect(n11).toBe(11);
})
});

ember: promise for a controller action that calls a service method

I have an action on my controller that calls a service method. The service method is an ember-data query. I need to return a message contained in that ember-data payload back to the controller (print it to the screen).
I am having a hard time figuring out how to get the controller action (function) to "wait" for the service method to finish.
The controller action:
// controller action
processCoupon() {
// the service method I want to wait for the response for
let messageObject = DS.PromiseObject.create({
promise: this.get('cart').processCoupon()
});
// the message
messageObject.then(response => {
let promo_message = messageObject.get('promo_message');
if (promo_message.message && promo_message.alert) {
if (!promo_message.success) {
// show error message in alert with ember-cli-notifcations
} else {
// show success message in alert with ember-cli-notifcations
}
}
});
},
Method in the service I want to wait for the response for:
// service method syncs cart info (containing promo) with the backend
// promo_message is in the response payload
processCoupon() {
return this.get('store').findRecord('cart', get(this, 'cartObj.id')).then(cart => {
cart.save().then(newCart => {
set(this, 'cartObj', newCart); // sets response to property on service
return newCart.get('promo_message');
});
});
},
the 'response' in the promise is empty, and the MessageObject itself has no content. So I'm doing something wrong here (and it's likely misunderstanding promises).
I messed around with RSVP promises and didn't do well there either. What am I missing, OR is there a better way to do this?
Your service method should return a promise. Then you can use it like this: this.get('cart').process().then((response) => {/*your code working with service's response*/});
You also should be aware that if you use ember data, it will return a model instance, not an original response from your back-end.
And, in order to return promise, you need to wrap your service's method in new Promise((resolve, reject) => {/*asynchronous code here*/});
I've done something similar with Ember.RSVP.Promise() is that what you want?
// controller
myMessage = null,
actions:
cartCoupon() {
let msg = this.get('cart').processCoupon();
msg.then(myDataMessage => this.set('myMessage', myDataMessage);
}
//service
processCoupon() {
return new Ember.RSVP.Promise( resolve => {
let data = this.get('store').findRecord('cart', get(this, 'cartObj.id')).then(cart => {
cart.save().then(newCart => {
set(this, 'cartObj', newCart); // sets response to property on service
return newCart.get('promo_message');
});
});
resolve(data);
});
}

Test state value after promise resolved

Given an component that make a login ...
onSubmit(e) {
e.preventDefault();
const { userStore, data } = this.props;
this.setState({isLogin: true});
const promise = userStore.login(data);
promise.then(() => {
this.setState({isLogin: false});
})
}
How can I create a test that validate if isLogin state is false after the promise is resolved?
So far, I have ..
it('resolved submit should set isLogin to false', () => {
mockApi.onPost('/authenticate').reply(200);
const userStore = new UserStore();
let fromPromiseLogin;
userStore.login = jest.fn()
.mockImplementationOnce((data) => {
fromPromiseLogin = userStore.login(data);
return fromPromiseLogin;
});
const wrapper = mount(
<FormLogin userStore={userStore} />,
);
wrapper.find('form').simulate('submit');
// HOW CAN I WAIT/OR CALLBACK FOR THE PROMISE ? I TRIED >
whenWithTimeout(
() => fromPromiseLogin && fromPromiseLogin.state !== PENDING,
() => expect(wrapper.state().isLogin).toBeFalsy(),
);
});
But a exeption
[mobx] Encountered an uncaught exception that was thrown by a reaction or observer component, in: 'Reaction[When#21] TypeError: Cannot read property 'state' of undefined
EDIT:
This answer is no longer relevant after the question has been edited.
Have you tried to test the result of the promise with .resolves function as described here?
expect(myPromise).resolves.toEqual('SomeValue');
Or using async/await:
const result = await myPromise;
expect(result).toEqual('SomeValue');

Test async middleware in redux with thunk

I have a middleware that waits for a ARTICLE_REQUEST action, performs a fetch and dispatches either an ARTICLE_SUCCESS or an ARTICLE_FAILURE action when fetch is done. Like so
import { articleApiUrl, articleApiKey } from '../../environment.json';
import { ARTICLE_REQUEST, ARTICLE_SUCCESS, ARTICLE_FAILURE } from '../actions/article';
export default store => next => action => {
// Prepare variables for fetch()
const articleApiListUrl = `${articleApiUrl}list`;
const headers = new Headers({ 'Content-Type': 'application/json', 'x-api-key': articleApiKey });
const body = JSON.stringify({ ids: [action.articleId] });
const method = 'POST';
// Quit when action is not to be handled by this middleware
if (action.type !== ARTICLE_REQUEST) {
return next(action)
}
// Pass on current action
next(action);
// Call fetch, dispatch followup actions and return Promise
return fetch(articleApiListUrl, { headers, method, body })
.then(response => response.json());
.then(response => {
if (response.error) {
next({ type: ARTICLE_FAILURE, error: response.error });
} else {
next({ type: ARTICLE_SUCCESS, article: response.articles[0] });
}
});
}
I really wonder how to test this async code. I want to see if the follow-up actions will be dispatched properly and maybe if the fetch call gets invoked with the proper URL and params. Can anyone help me out?
PS: I am using thunk although I am not absolutely sure of its function as I just followed another code example
You can mock the fetch() function like so:
window.fetch = function () {
return Promise.resolve({
json: function () {
return Prommise.resolve({ … your mock data object here … })
}
})
}
Or you wrap the entire middleware in a Function like so:
function middlewareCreator (fetch) {
return store => next => action => { … }
}
and then create the middleware with the actual fetch method as parameter, so you can exchange it for tests or production.

Ember promise isPending not firing if thenable

I want to show a loading spinner in an ember component when I load some data from the store. If there is an error from the store, I want to handle it appropriately.
Here is a snippet of my current code:
cachedCampaignDataIsPending: computed.readOnly('fetchCachedCampaignData.isPending'),
fetchCachedCampaignData: computed('campaign', function() {
return this.get('store').findRecord('cachedCampaignMetric').then( (cachedMetrics) => {
this.set('cachedCampaignData', cachedMetrics);
}, () => {
this.set('errors', true);
});
}),
This will properly set the errors to true if the server responds with an error, however, the cachedCampaignDataIsPending is not acting properly. It does not get set to true.
However, if I rewrite my code to make the computed property not be thenable, then it does fire propertly.
fetchCachedCampaignData: computed('campaign', function() {
return this.get('store').findRecord('cachedCampaignMetric');
}),
Anyone know the reason why, or how to make it fire properly, using the then/catch logic?
Thanks.
Update
I found that this works for me and gives me what I need.
cachedCampaignDataIsPending: computed.readOnly('fetchCachedCampaignData.isPending'),
fetchCachedCampaignData: computed('campaign', function() {
let promise = this.get('store').findRecord('cachedCampaignMetric');
promise.then( (cachedMetrics) => {
this.set('cachedCampaignData', cachedMetrics);
}, () => {
this.set('errors', true);
});
return promise;
}),