nuxt watchQuery not working on update route with new query [duplicate] - vue.js

This question already has answers here:
How to access current route name reactively in Vue Composition API in TypeScript?
(3 answers)
Closed last month.
when i add a new query using $router.push to route Nuxt watchQuery not working and asyncData not fetching api and remount children components. please attention to "New query" and not exist any query by default.(after created new query and then exists query every things are correct and watchQuery works correctly.)
example.com/some-param ----> example.com/some-param?brand=x (not working watchQuery)
example.com/some-param?brand=x ----> example.com/some-param (correct watchQuery)
change_brand: function () {
const vm = this;
/*** selected_brands = [] is an array defined in data ***/
let q = { ...vm.$route.query };
if (vm.selected_brands.length > 0) {
q.brands = vm.selected_brands.join("-");
} else {
delete q.brands;
}
vm.$router.push({
name: "search-slug",
params: vm.$route.params,
query: q,
});
},

As Nuxt documentation says here:
Warning: The new fetch hook introduced in 2.12 is not affected by watchQuery. For more information see listening to query string changes.
I used the following watcher to make fetch hook listen to route query changes:
export default {
watch: {
'$route.query': '$fetch'
},
async fetch() {
// Called also on query changes
}
}
Reference: https://nuxtjs.org/docs/2.x/features/data-fetching#listening-to-query-string-changes

Related

useInfiniteScroll utility of Vueuse is fetching same items again

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&param2=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;
};

Stuck with $ref pointing to Proxy object with vue-router

I was using VueJS in browser mode and am now trying to switch my code to a VueJS SPA and vue-router. I've been stuck for hours with a $refs not working anymore.
To interact with my Google Charts, I was using an absolute reference to the graph (this.$refs.villesChart) to get selected data like that:
computed: {
eventsprox() {
let eventsprox = {
select: () => {
var selection = "";
if (this.$refs.villesChart) selection = this.$refs.villesChart1.chartObject.getSelection();
if (selection.length) {
var row = selection0[0].row + 1;
this.code_commune = this.dataprox[row][4];
this.changerville(this.code_commune, this.dataprox[row][0]);
}
return false;
},
};
return eventsprox;
}
HTML code for graph:
<GChart type="BarChart" id="villesChart" ref="villesChart" :data="dataprox" :options="optionsprox" :events="eventsprox"/>
I don't know why, but in browser mode, this.$refs.villesChart is a component:
[1]: https://i.stack.imgur.com/xJ8pV.png
but now it is a proxy object, and lost its chartObject attribute:
[2]: https://i.stack.imgur.com/JyXrL.png
I'm really confused. Do you have an idea why?
And if I use the proxy object, then I get a Vue warning "Avoid app logic that relies on enumerating keys on a component instance" and it is not working in production environment.
Thanks a lot for your help!!
After hours of testing different solutions, I finally found a solution working with Vue3 and Vue-google-chart 1.1.0.
I got rid of "refs" and put the events definition and code in the data section of my Vue 3 app (instead of computed) and accessed the chart data through a component variable I used to populate it.
Here is my event code where this.dataprox is my data table for the chart:
eventsprox: {
'click': (e) => {
const barselect = parseInt(e.targetID.split('#')[2]) + 1;
this.code_commune = this.dataprox[barselect][4];
this.nom_commune = this.dataprox[barselect][0];
this.changerville(this.code_commune, this.nom_commune);
}
},
My Gchart html code:
<GChart type="AreaChart" :data="datag" :options="optionsg" :events="eventsprox"/>
I hope it can help!

Vuejs - update array of an object which is in an array

I'm developing a helpdesk tool in which I have a kanban view.
I previously used nested serializers in my backend and I managed to have everything working with a single query but it's not scalable (and it was ugly) so I switched to another schema :
I query my helpdesk team ('test' in the screenshot)
I query the stages of that team ('new', 'in progress')
I query tickets for each stage in stages
So when I mount my component, I do the following :
async mounted () {
if (this.helpdeskTeamId) {
await this.getTeam(this.helpdeskTeamId)
if (this.team) {
await this.getTeamStages(this.helpdeskTeamId)
if (this.stages) {
for (let stage of this.stages) {
await this.getStageTickets(stage)
}
}
}
}
},
where getTeam, getTeamStages and getStageTickets are :
async getTeam (teamId) {
this.team = await HelpdeskTeamService.getTeam(teamId)
},
async getTeamStages (teamId) {
this.stages = await HelpdeskTeamService.getTeamStages(teamId)
for (let stage of this.stages) {
this.$set(stage, 'tickets', [])
}
},
async getStageTickets (stage) {
const tickets = await HelpdeskTeamService.getTeamStageTickets(this.helpdeskTeamId, stage.id)
// tried many things here below but nothing worked.
// stage.tickets = stage.tickets.splice(0, 0, tickets)
// Even if I try to only put one :
// this.$set(this.stages[this.stages.indexOf(stage)].tickets, 0, tickets[0])
// I see it in the data but It doesn't appear in the view...
// Even replacing the whole stage with its tickets :
// stage.tickets = tickets
// this.stages.splice(this.stages.indexOf(stage), 1, stage)
},
In getTeamStages I add an attribute 'tickets' to every stage to an empty list. The problem is when I query all the tickets for every stage. I know how to insert a single object in an array with splice or how to delete one object from an array but I don't know how to assign a whole array to an attribute of an object that is in an array while triggering the Vue reactivity. Here I'd like to put all the tickets (which is a list), to stage.tickets.
Is it possible to achieve this ?
If not, what is the correct design to achieve something similar ?
Thanks in advance !
EDIT:
It turns out that there was an error generated by the template part. I didn't think it was the root cause since a part of the view was rendered. I thought that it would have prevent the whole view from being rendered if it was the case. But finally, in my template I had a part doing stage.tickets.length which was working when using a single query to populate my view. When making my API more granular and querying tickets independently from stages, there is a moment when stage has no tickets attribute until I set it manually with this.$set(stage, 'tickets', []). Because of that, the template stops rendering and raises an issue. But the ways of updating my stage.tickets would have worked without that template issue.
I could update the stages reactively. Here is my full code; I used the push method of an array object and it works:
<template>
<div>
<li v-for="item in stages" :key="item.stageId">
{{ item }}
</li>
</div>
</template>
<script>
export default {
data() {
return {
stages: [],
};
},
methods: {
async getTeamStages() {
this.stages = [{ stageId: 1 }, { stageId: 2 }];
for (let stage of this.stages) {
this.$set(stage, "tickets", []);
}
for (let stage of this.stages) {
await this.getStageTickets(stage);
}
},
async getStageTickets(stage) {
const tickets = ["a", "b", "c"];
for (let ticket of tickets) {
this.stages[this.stages.indexOf(stage)].tickets.push(ticket);
}
},
},
mounted() {
this.getTeamStages();
},
};
</script>
It should be noted that I used the concat method of an array object and also works:
this.stages[this.stages.indexOf(stage)].tickets = this.stages[this.stages.indexOf(stage)].tickets.concat(tickets);
I tried your approaches some of them work correctly:
NOT WORKED
this.$set(this.stages[this.stages.indexOf(stage)].tickets, tickets)
WORKED
this.$set(this.stages[this.stages.indexOf(stage)].tickets, 0, tickets[0]);
WORKED
stage.tickets = tickets
this.stages.splice(this.stages.indexOf(stage), 1, stage)
I'm sure it is XY problem..
A possible solution would be to watch the selected team and load the values from there. You seem to be loading everything from the mounted() hook, and I suspect this won't actually load all the content on demand as you'd expect.
I managed to make it work here without needing to resort to $set magic, just the pure old traditional vue magic. Vue will notice the properties of new objects and automatically make then reactive, so if you assign to them later, everything will respond accordingly.
My setup was something like this (showing just the relevant parts) -- typing from memory here, beware of typos:
data(){
teams: [],
teamId: null,
team: null
},
watch:{
teamId(v){
this.refreshTeam(v)
}
},
methods: {
async refreshTeam(id){
let team = await fetchTeam(id)
if(!team) return
//here, vue will auomaticlly make this.team.stages reactive
this.team = {stages:[], ...team}
let stages = await fetchStages(team.id)
if(!stages) return
//since this.team.stages is reactive, vue will update reactivelly
//turning the {tickets} property of each stage reactive also
this.team.stages = stages.map(v => ({tickets:[], ...v}))
for(let stage of this.team.stages){
let tickets = await fetchTickets(stage.id)
if(!tickets) continue
//since tickets is reactive, vue will update it accordingly
stage.tickets = tickets
}
}
},
async mounted(){
this.teams = fetchTeams()
}
Notice that my 'fetchXXX' methods would just return the data retrieved from the server, without trying to actually set the component data
Edit: typos

How to grab apollo client cache content and use it in a query as an input type?

I am developing a react native application with apollo client and I need to have 2 screens:
Screen A: displays a list of profiles
Screen B: displays some filters which have to be applied to Screen A
So my idea was to use apollo client cache to save the state of the filters in screen B, and then come back to screen A and somehow refetch with the new filters applied.
Since there are a couple of filters that I need to send on each request to the server, I also was thinking about using an input type so I can send my filters in the form of an object instead of a list of comma-separated parameters.
So looking at the docs from apollo client, there is a section to manage the local stage. In there, I found a sub-section called Using #client fields as variables. Basically, this part tells you how to grab whatever filters were stored in the cache and send it as part of the query.
However, I always get the following error:
Invariant Violation: Missing selection set for an object of type ProfileParameters returned for query field profileParameters
some code:
This is how I initialized the cache-store, for now, it only contains page and pageSize but it will contain more parameters.
const cache = new InMemoryCache({
cacheRedirects: {
Query: {
profile: (_, {id}, {getCacheKey}) =>
getCacheKey({__typename: 'Profile', id: id}),
},
},
});
cache.writeData({
data: {
profileParameters: {
__typename: 'ProfileParameters',
page: 0,
pageSize: 25,
},
},
});
const client = new ApolloClient({
uri: 'http://192.168.1.102:3000/graphql',
cache: cache,
resolvers: {},
});
And this is the query component and the query:
const PROFILES_QUERY = gql`
query getFilteredProfiles($type: String, $parameters: ProfileParameters) {
profileParameters #client #export(as: "parameters")
profiles(type: $type, parameters: $parameters) {
id
name
termOfEntry
sport
sportPosition
gpa
avatarUrl
imageUrl
nationality
countryOfResidence
dateOfBirth
annualBudget
career
satScore
toeflScore
graduationDate
}
}
`;
<Query
query={PROFILES_QUERY}
variables={{
type: this.PROFILE_TYPE,
}}>
...
</Query>
There is a type variable that is being passed in the variables object. That comes from a local variable in the class.
Also, screen B does not exist yet, but the cache is being initialized and I want to read whatever is in there so I can send those filters to the server.

Vue Method works on update but not on mounted

I have a Vue method that is called at mounted lifecycle hook. For some reason, it does not work if I refresh but does if the browser is hot reladed in development. I expect an object to be returned. Instead, it's undefined.
data () {
return {
randomAd: '',
startingIndex: 0,
businesses: [...]
}
},
mounted () {
this.getRandomAd()
},
methods: {
getRandomAd () {
console.log(this.businesses.length)
let randomNum = Math.floor(Math.random() * this.businesses.length)
console.log(randomNum)
this.startingIndex = randomNum
let randomAd = this.businesses[randomNum]
this.randomAd = randomAd
console.log(this.businesses[randomNum])
},
The output of the three console logs are: [__ob__: Observer], 0, and undefined. The first object contains all the data but it seems to break at the randomNum.
When the browser is hot reloaded I get what I expect with [__ob__: Observer], 17, 11.
Update
I just realized that the data comes from firebase. If I remove the firebase businesses array and create a dummy one this code works. Do I need to add an async/await somewhere? The thing I find weird is that the first console.log of the array shows the data already there.