If I am rendering a page with Nuxt, Vue, and Axios - is there a way to reuse the asyncData request (or data)?. For example, if I render a response, and the user takes an action on the page to filter, sort, etc. the data, can I reuse the same data to render again - or do I need to make a new call in mounted?
export default {
asyncData ({ env, params, error }) {
return axios.get(`${env.cockpit.apiUrl}/collections/get/cat_ruleset?token=${env.cockpit.apiToken}&simple=1&sort[ruleid]=1`)
.then((res) => {
return { catrules: res.data }
})
.catch((e) => {
error({ statusCode: 404, message: 'Post not found' })
})
},
mounted() {
},
methods: {
}
}
Of course you can reuse it. The simplest way would be to store the result somewhere (anywhere, really, but your store would be a good storage candidate) and change your method to:
asyncData ({ env, params, error }) {
return X ? Promise.resolve(X) : axios.get(...)
}
... where X is the stored result of your previous call.
But you don't have to.
Because, by default, the browser will do it for you. Unless you specifically disable the caching for your call, the browser will assume making the same call to the server will yield the same result if you do it within the number of seconds set in max-age of Cache-control.
Basically, the browser returns the previous result from cache without making a call to the server, so the optimization you're after is already performed by the browser itself unless you specifically disable it.
You can easily spot which calls were served from cache and which from server by looking in the Network tab of DevTools in Chrome. The ones from cache will have (memory cache) in the Size column:
... and will have a value of 0ms in Time column.
If you want control over when to call the server and when to serve a cached result — most browsers have a limit on max-age (see link above) — you could (and should) store the result of your previous call and not rely at all on the browser cache (basically the internal check inside the method, which I suggested at the top).
This would enable you to avoid making a call long time after the cache max-age has passed, because you already have the data, should you choose to do so.
Related
I have weird results displayed in the web console:
fetch() is sending duplicated requests to the same url.
I thought it could be something related to fetch(), but also noticed that on reload of the app (quasar, based on webpack) also the requests to the http://localhost:8080/sockjs-node/info are duplicated.
By contrast, I noticed that requests handled by jQuery are NOT duplicated and works fine.
I cannot say if it is an error due to webpack configuration, fetch or they way I am using it i Vue components.
E.g. This article points out possible causes https://insights.daffodilsw.com/blog/how-to-avoid-duplicate-api-requests but in my case it is not due to user interaction : requests are triggered at time of relaunching the app (webpack), and particularly the stack trace shows that the requests are fired at time of creating the components, just multiple times.
Example of how I am using fetch():
// component -
methods : {
search(input) {
return new Promise(resolve => { // a new promise is request multiple times, in spite in created() it is called just once
var _base = 'myEndpoint/api'
const url = `${_base}fuzzyautocomplete?q=${encodeURI(input)}`
if (input.length < 3) {
return resolve([])
}
fetch(url) // so promises are actually different, and duplicated requests are fired by fetch
.then(response => response.json())
.then(data => {
console.log(data)
// resolve(data.query.search)
resolve(data)
})
})
},
....
// and it should be called just once at time of creation
created() {
this.search('init first query !!');
}
Could you advise?
Although I know this may not be considered as a best practice, but what I want to achieve is to silently delete a record from a database after the same was created throughout UI. In htat way I want to keep our test environment clear as much as possible and reduce the noise of test data.
After my tests create a new record by clicking over the UI, I wait for POST request to finish and then I would like to extract the id from the response (so I can reuse it to silently delete that record by calling the cy.request('DELETE', '/id')).
Here's a sample test I have put on as a showcase. I'm wondering why nothing is logged in this example.
it('GET cypress and log', () => {
cy.server()
.route('**/repos/cypress-io/cypress*')
.as('getSiteInfo');
cy.visit('https://www.cypress.io/dashboard');
cy.get('img[alt="Cypress.io"]')
.click()
.wait('#getSiteInfo')
.then((response) => {
cy.log(response.body)
})
})
As far as I can see from here https://docs.cypress.io/api/commands/wait.html#Alias this should be fine.
your code contains two problems.
First:
The click triggers a new page to be loaded but cypress does not wait until the PageLoad event is raised (because you do not use visit). On my PC the Request takes about 5 seconds until it is triggered after the click. So you should use wait(..., { timeout: 10000 }).
Second:
wait() yields the XHR object, not the response. So your code within then is not correct. Also the body is passed as object. So you should use JSON.stringify() to see the result in the command log.
This code works:
describe("asda", () => {
it('GET cypress and log', () => {
cy.server()
.route('**/repos/cypress-io/cypress*')
.as('getSiteInfo');
cy.visit('https://www.cypress.io/dashboard');
cy
.get('img[alt="Cypress.io"]')
.click()
.wait('#getSiteInfo', { timeout: 20000 })
.then((xhr) => {
cy.log(JSON.stringify(xhr.response.body))
})
})
})
Instead of route and server method, try intercept directly
async No response On the server side, 200ok comes in, but I can't get it.
I've tried the watch method, but don't. I want to solve it asynchronously.
async mounted() {
try{
let response =await axios.get('http://localhost:5000/process')
this.msg = response
}
catch (err) {
// eslint-disable-next-line
console.log(err)
}
}
};
There is no reaction.
An axios response will contain a number of properties what you are likely looking for is response.data try that and you should have your response assuming you are actually getting something. You can always look in the network tab to see exactly what is coming in even if it not showing on your app, just look for the call and see what the response or preview show.
In the event that your data is not updating in the template you would need to create a key on the element that needs to update to force a rerender, this is because vue's reactivity only works (or mostly) with primitive values not nested structures.
At work, we think about using Vuejs with Vuex for our frontend. I have already used it for private projects.
One of our big questions is the traffic. Our platform needs a lot of data, sometimes in large packages. Some of them are really big and it would be more expensive to load them more than once.
I've seen many examples of vue with vuex, but for me it looked like all the samples would request the same (already loaded) data every time they paged.
My real question is: Is there a way in vuejs or general to avoid or solve this problem? The only helpful thing I have found so far was this.
As far as I know, you can use this library https://github.com/kuitos/axios-extensions
An example here
import Axios from 'Axios';
import { throttleAdapterEnhancer } from 'axios-extensions';
const http = axios.create({
baseURL: '/',
headers: { 'Cache-Control': 'no-cache' },
adapter: throttleAdapterEnhancer(axios.defaults.adapter, { threshold: 2 * 1000 })
});
http.get('/users'); // make real http request
http.get('/users'); // responsed from the cache
http.get('/users'); // responsed from the cache
setTimeout(() => {
http.get('/users'); // after 2s, the real request makes again
}, 2 * 1000)
As you can see, you can create an adaptor and custom what you want. For example here, you keep the cache for only 2 seconds. So the first request to /users is a real one. The second and thirst request are cache, and the last one is after the two seconds, so it's a real one.
Can you be more specific about the context of how you will keep the cache. When do you need to reload the cache, after how many times, after which event, ...?
The strategy I use is to store the value on the Vuex state.
I write all my request in Vuex actions. In every action, I check if the data already exists on the Vuex state. If it does, I simply bypass the request and return the data from the state (saving requests from being called multiple times). If it doesn't exist, I'll make the request, then store the result on the Vuex state, and return the value.
Vuex Action:
getLists({ state, commit }) {
return new Promise((resolve, reject) => {
if (state.isSetLists === false) {
getListsFromServer((error, data) => {
if (error) {
reject(error);
} else {
console.log('call to getLists successful:', data);
commit('setLists', data.lists);
resolve(data.lists);
}
});
} else {
resolve(state.lists);
}
});
},
Then, the setLists mutation handles it like so:
setLists(state, lists) {
state.isSetLists = true;
state.lists = lists;
},
This way, the user can page around all they want, and only ever call each request once.
I'm using default nuxt loading bar, it works well on simple pages. But when I use multiple axios requests progress bar loads every time request is sent. I want progress bar to understand all those request as a single page load. I used Promise.all and it kind of worked. But my problem is that I am using asynchronous vuex dispatch methods.
So my code is something like this, with three different asynchronous dispatch and progress bar loads three times. How can I make it so, that it loaded only once. Thanks
async fetch({ store }) {
await store.dispatch('LOAD_DATA_1')
await store.dispatch('LOAD_DATA_2')
await store.dispatch('LOAD_DATA_3')
}
It's loading three separate times because your requests are taking place sequentially, one after another, not all at once. To get around this, you can manually start/stop the loader.
First, you'll want to prevent the nuxt axios plugin from triggering the loading bar. See here.
this.$axios.$get('URL', { progress: false })
Then, you can manually start and stop the loading bar programatically before/after the requests are completed.
this.$nuxt.$loading.start()
this.$nuxt.$loading.stop()
Full example:
async fetch({ store }) {
this.$nuxt.$loading.start()
await store.dispatch('LOAD_DATA_1')
await store.dispatch('LOAD_DATA_2')
await store.dispatch('LOAD_DATA_3')
this.$nuxt.$loading.stop()
}
edit 1 (see comment):
To use in asyncData/fetch you can use the following. I'm not sure you should be accessing the components like this, but I don't see another way to access the $loading module within the context...
async fetch(ctx) {
// access the loading component via the access context
ctx.app.components.NuxtLoading.methods.start()
// example, wait 3 seconds before disabling loader
await new Promise(resolve => setTimeout(resolve, 3000))
ctx.app.components.NuxtLoading.methods.finish()
},