I have read that nextTick allows codes to be executed at the next action. But this does not work in my code, can someone helps me on this? Please correct me. Thanks.
.vue
.....
methods:{
getUserInfo(){
var vm = this
vm.$http.get('/getAuthUser').then((response)=>{
vm.user = response.data
})
Vue.nextTick(()=>{
vm.$http.get('/getShop/'+vm.user.id).then((response)=>{
vm.shop = response.data.data.shop
})
})
},
}
.....
{{user.id}} does work. where this gives me the following error:
GET http://localhost:8000/getShop/undefined 404 (Not Found)
EDIT#1
if i do something like this it works but this should not be the right way to do in my opinion.
.....
methods:{
getUserInfo(){
var vm = this
vm.$http.get('/getAuthUser').then((response)=>{
vm.user = response.data
vm.$http.get('/getShop/'+vm.user.id).then((response)=>{
vm.shop = response.data.data.shop
})
})
},
}
.....
EDIT#2
If I do something like this it wont work coz vm.user.id is not set.
.....
methods:{
getUserInfo(){
var vm = this
vm.$http.get('/getAuthUser').then((response)=>{
vm.user = response.data
})
vm.$http.get('/getShop/'+vm.user.id).then((response)=>{
vm.shop = response.data.data.shop
})
},
}
.....
I think your understanding of what nextTick does is incorrect. If you read the documentation, it says that the callback you pass to the nextTick function will be executed after the next DOM update.
Let's say you have a property that determines whether an element exists or not in the DOM with a v-if directive. If you change the value of the property so that the element exists in the DOM, you might have to wait for Vue to process the change and update the DOM before you can grab a reference of that element, for example. In that case, you should use Vue.nextTick to make sure by the time you want to query the DOM to get that element, it actually exists.
Your scenario doesn't have anything to do with the DOM.
You have 2 asynchronous HTTP calls that you want to execute one after another, because the second relies on the result of the first. Your original implementation and third one (EDIT#2) are flaky because you don't make sure the first HTTP request is complete before firing the second one, which explains why you get errors about vm.user.id not being set.
Your second implementation (EDIT#1) is more correct because the second HTTP request is fired after the first one completes. Still, I'd suggest a minor modification:
getUserInfo() {
vm.$http.get('/getAuthUser')
.then(response => {
vm.user = response.data;
return vm.$http.get('/getShop/' + vm.user.id);
}).then(response => {
vm.shop = response.data.data.shop;
});
}
The first callback returns a Promise which result is fed into the second then call. I like this approach because it avois having nested thens. I would also suggest you to read the MDN docs on Promises.
Related
Here is a reproducable stackblitz -
https://stackblitz.com/edit/nuxt-starter-jlzzah?file=components/users.vue
What's wrong? -
My code fetches 15 items, and with the bottom scroll event it should fetch another 15 different items but it just fetches same items again.
I've followed this bottom video for this implementation, it's okay in the video but not okay in my stackblitz code:
https://www.youtube.com/watch?v=WRnoQdIU-uE&t=3s&ab_channel=JohnKomarnicki
The only difference with this video is that he's using axios while i use useFetch of nuxt 3.
It's not really a cache issue. useFetch is "freezing" the API URL, the changes you make to the string directly will not be reliably reflected. If you want to add parameters to your API URL, use the query option of useFetch. This option is reactive, so you can use refs and the query will update with the refs. Alternatively, you can use the provided refresh() method
const limit = ref(10)
const skip = ref(20)
const { data: users, refresh: refreshUsers } = await useFetch(
'https://dummyjson.com/users',
{
query:{
limit,
skip
}
}
);
//use the data object directly to access the result
console.log(users.value)
//if you want to update users with different params later, simply change the ref and the query will update
limit.value = 23
//use refresh to manually refresh the query
refreshUsers()
This results in a first API call http://127.0.0.1:8000/api/tasks?limit=10&skip=20 and then a second with the updated values http://127.0.0.1:8000/api/tasks?limit=23&skip=20
You can leave the cache alone, as it is just a workaround, and will not work reliably.
[Updated] The useFetch() documentation is now updated as described below.
The query option is not well documented yet, as discussed in this nuxt issue. I've created a pull request on nuxt/framework to have it reflected in the documentation. Please see a full explanation below:
Using the query option, you can add search parameters to your query. This option is extended from unjs/ohmyfetch and is using ufo to create the URL. Objects are automatically stringified.
const param1 = ref('value1')
const { data, pending, error, refresh } = await useFetch('https://api.nuxtjs.dev/mountains',{
query: { param1, param2: 'value2' }
})
This results in https://api.nuxtjs.dev/mountains?param1=value1¶m2=value2
Nuxt3's useFetch uses caching by default. Use initialCache: false option to disable it:
const getUsers = async (limit, skip) => {
const { data: users } = await useFetch(
`https://dummyjson.com/users?limit=${limit}&skip=${skip}`,
{
initialCache: false,
}
);
//returning fetched value
return users.value.users;
};
But you probably should use plain $fetch instead of useFetch in this scenario to avoid caching:
const getUsers = async (limit, skip) => {
const { users } = await $fetch(
`https://dummyjson.com/users?limit=${limit}&skip=${skip}`
);
//returning fetched value
return users;
};
So I am writing a test that will add a card to a container(payment-card-container) and I want to confirm an element was added later by seeing if the children have increased by 1. But I am having issues when we try to count the children length when there isnt any. I am currently using the below:
cy.get('[data-test-id="payment-card-container"]')
.children()
.its('length')
.then(length => {
const childrenLength = length;
})
But Cypress seems to get an error because it cant find the children (Error below).
Timed out retrying: Expected to find element: ``, but never found it.
Is there a way this can work when there isnt any children and it returns the value of 0?
The problem with using a jQuery expression like
Cypress.$('[data-test-id="payment-card-container"]').children().length
is you don't get the Cypress retry for async updates.
If adding a payment card calls an API, the above expression will falsely report 0 children instead of waiting for the DOM to update.
There's really no good way to handle the no-cards situation,
Except
set up your test scenario such that there are no cards initially
add a card
confirm that there is now exactly one card
If you must test for zero children, a trailing .should() will remove the error message.
cy.get('[data-test-id="payment-card-container"]')
.children()
.should('have.length', 0); // no error when should expression passes
// Add card here
cy.get('[data-test-id="payment-card-container"]')
.children()
.should('have.length', 1); // waits for async add-card operation
Tested with
<body>
<div data-test-id="payment-card-container"></div>
<script>
setTimeout(() => {
const div = document.querySelector('[data-test-id="payment-card-container"]');
const p = document.createElement('p')
div.appendChild(p)
}, 2000)
</script>
</body>
One hacky way that I could think of is this. You can use the jQuery length and children() property to check the length:
cy.get('body').then(() = > {
if (Cypress.$('[data-test-id="payment-card-container"]').children().length == 0) {
//Do Something
}
else {
//Do Something
}
})
I am reading through the documentation in Cypress and I think I have an idea as to what then() does. It works like promises, where a promise returns another promise, but with then(), we are returning a new subject.
If we look at the code example below, we are using then() because we are returning a new variable, which in this case is called target.
Am I understanding this correctly? If not, can someone correct me?
it.only('Marks an incomplete item complete', () => {
//we'll need a route to stub the api call that updates our item
cy.fixture('todos')
.then(todos => {
//target is a single todo, taken from the head of the array. We can use this to define our route
const target = Cypress._.head(todos)
cy.route(
"PUT",
`api/todos/${target.id}`,
//Here we are mergin original item with an object literal
Cypress._.merge(target, {isComplete: true})
)
})
.then is used to receive the results from cy.fixture('todos'). The variable target is not significant in this code.
In your code sample, the variable that is returned from cy.fixture is named todos - the spacing of the code may be throwing you off here? The .then call is attached to the cy.fixture() call
// These 2 code blocks are the same - just different spacing
cy.fixture('todos')
.then(todos => {});
cy.fixture('todos').then(todos => {});
https://docs.cypress.io/api/commands/fixture.html#Usage
cy.fixture('logo.png').then((logo) => {
// load data from logo.png
})
Using .then() allows you to use the yielded subject in a callback function and should be used when you need to manipulate some values or do some actions.
To put it simply, it is used to play around with the yield of the previous command and work around with it in that case. THEN() command is handy and helpful in debugging the yield of the previous command.
const baseURL = "https://jsonplaceholder.typicode.com";
describe("Get Call-Expect+ normal req", () => {
it("GetPostById-Expect", () => {
cy.request(baseURL + "/posts/1").as("GetPostById");
cy.get("#GetPostById").then((response) => {
//response: status
expect(response.status).to.equal(200);
expect(response.status).to.eq(200);
});
});
Refer: https://docs.cypress.io/api/commands/then#Promises
I'm having a little trouble setting one of my this.values within my Vue application. I believe I'm either not understanding async axios calls correctly, or how async works within axios.
I have the following axios post method within my Vue application:
postRequest: async function(event) {
event.preventDefault();
let config = {
headers: {
"X-CSRFToken": this.csrfToken,
"Content-Type": 'application/x-www-form-urlencoded',
}
}
let formData = new FormData();
formData.append('data', someData);
await axios.post('{{ request_absolute_uri }}', formData, config).then(function(response) {
this.responseMessage = response.data.message;
}).catch(function(error) {
console.log(error);
});
}
console.log(this.responseMessage)
return this.responseMessage;
}
My response.data.message is being passed back from my framework as True/true but it seems I'm not returning anything from the postRequest function? The post definitely hits the server as logging shows everything I want - then returns back message = true in json response context (using console.log(this.responseMessage) directly before returning the same value. However, nothing on the template changes or updates from this....
I'm assuming at this point that I have missed something incredibly simple!
Hmmm. I think I know what is going on here: because you're using a standard function, your this inside the .then() callback is not refering to the instantiated object...instead, this. is the calling the context of that function - with your original code, i.e., the Promise returned by the axios .post method.
Instead, I would advise using an arrow function instead so that the this is inherited from the outer scope, something along these lines:
await axios.post('{{ request_absolute_uri }}', formData, config).then( (response) => {
this.responseMessage = response.data.message;
})
N.B. With arrow functions, the this. is always inherited from the outer scope, rather than depending on the calling context of the arrow function - so you can reference this.message or this.someFunction() with ease.
I believe the "this" is out of scope inside .then statement. If you add the line:
var self = this;
at the top of your postRequest function, and then when assigning the response message use:
self.responseMessage = response.data.message;
I think that should work.
I have a view that contains a model. The view listens for an event from the model and will perform an action once the event is triggered. Below is my code
window.Category = Backbone.Model.extend({})
window.notesDialog = Backbone.View.extend({
initialize: function() {
this.model.bind("notesFetched", this.showNotes, this);
},
showNotes: function(notes) {
//do stuffs here
}
})
I want to test this using Jasmine and below is my test (which doesn't work)
it("should show notes", function() {
var category = new Category;
var notes_dialog = new NotesDialog({model: category})
spyOn(notes_dialog, "showNotes");
category.trigger("notesFetched", "[]");
expect(notes_dialog.showNotes).toHaveBeenCalledWith("[]");
})
Does anyone know why the above test doesn't work? The error I get is "Expected spy showNotes to have been called with [ '[]' ] but it was never called."
I was doing something similar where I had a view, but I couldn't get the spy to work properly unless I added it to the prototype, and before I created the instance of the view.
Here's what eventually worked for me:
view.js
view = Backbone.View.extend({
initialize: function(){
this.collection.bind("change", this.onChange, this);
},
...
onChange: function(){
console.log("Called...");
}
});
jasmine_spec.js
describe("Test Event", function(){
it("Should spy on change event", function(){
var spy = spyOn(view.prototype, 'onChange').andCallThrough()
var v = new view( {collection: some_collection });
// Trigger the change event
some_collection.set();
expect(spy).toHaveBeenCalled()
});
});
I would test initially with the toHaveBeenCalled() expectation and change to the toHaveBeenCalledWith() after you get that working...
Update 5/6/2013: Changed update() to set()
Try to amend your existing test code as follows:
it("should show notes", function() {
var category = new Category;
spyOn(NotesDialog.prototype, "showNotes");
var notes_dialog = new NotesDialog({model: category})
category.trigger("notesFetched", "[]");
expect(notes_dialog.showNotes).toHaveBeenCalledWith("[]");
})
In your original code, the instance of the method you are calling is one defined in the bind closure, whereas the one you are spying on is in the notes_dialog instance. By moving the spy to the prototype, you are replacing it before the bind takes place, and therefore the bind closure encapsulates the spy, not the original method.
Using a spy means to replace the function you spying on. So in your case you replace the bind function with the spy, so the internal logic of the original spy will not call anymore. And thats the right way to go cause you dont wanna test that Backbones bind is work but that you have called bind with the specific paramaters "notesFetched", this.showNotes, this.
So how to test this. As you know every spy has the toHaveBeenCalledWith(arguments) method. In your case it should looks like this:
expect(category.bind).toHaveBeenCalledWith("notesFetched", category. showNotes, showNotes)
So how to test that trigger the "notesFetched" on the model will call your showNotes function.
Every spy saves the all parameters he was called with. You can access the last one with mostRecentCall.args.
category.bind.mostRecentCall.args[1].call(category.bind.mostRecentCall.args[2], "[]");
expect(notes_dialog.showNotes).toHaveBeenCalledWith("[]");
mostRecentCall.args[1] is the the second argument in your bind call (this.showNotes). mostRecentCall.args[2] is the the third argument in your bind call (this).
As we have test that bind was called with your public method showNotes, you can also call the your public method showNotes directly, but sometimes the passed arguments can access from outside so you will use the shown way.
Your code looks fine, except do you have the test wrapped in a describe function, as well as an it function?
describe("show notes", function(){
it("should show notes", function(){
// ... everything you already have here
});
});
Total guess at this point, but since you're not showing the describe function that's all I can think it would be. You must have a describe block for the tests to work, if you don't have one.
You are pretty close ;)
spyOn replaces the function with your spy and returns you the spy.
So if you do:
var dialog_spy = spyOn(notes_dialog, "showNotes");
category.trigger("notesFetched", "[]");
expect(dialog_spy).toHaveBeenCalledWith("[]");
should work just fine!