Refresh my Vue Apollo query when params change - vuejs2

I am new to vue, but I don't understand why no one seems to be able to answer this question. I would have thought it was simple, but I can't find anywhere talking about it.
I have this method getCategory that uses Apollo to query our graphQL API.
The logic looks like this:
import { useQuery, useResult } from "#vue/apollo-composable";
import * as getCategoryBySlug from "#graphql/api/query.category.gql";
export function useGetCategory(slug: string) {
const { result, loading, error } = useQuery(getCategoryBySlug, { slug });
const category = useResult(result, null, (data) => data.categoryBySlug);
return { category, loading, error };
}
When I want to use this in a component, I can simply do this:
import { computed, defineComponent } from "#vue/composition-api";
import { useGetCategory } from "#logic/get-category";
import CategoryTitle from "#components/category-title/category-title.component.vue";
import Products from "#components/products/products.component.vue";
import { defineComponent } from "#vue/composition-api";
import { useGetCategory } from "#logic/get-category";
import CategoryTitle from "#components/category-title/category-title.component.vue";
import Products from "#components/products/products.component.vue";
export default defineComponent({
name: "Categories",
components: { CategoryTitle, Products },
setup(_, context) {
const { category, loading, error } = useGetCategory(
context.root.$route.params.slug
);
return { category, loading, error };
},
});
And that's fine. Then in my template, I can do what I need to do like this:
<template>
<div>
<category-title v-if="category" :category="category"> </category-title>
<base-loader :loading="loading"> </base-loader>
<products :category="category" v-if="category"></products>
</div>
</template>
<script src="./category.component.ts" lang="ts"></script>
<style src="./category.component.scss" lang="scss" scoped></style>
Now comes the issue (which in my mind, should be dead easy). I need to handle route changes, specifically the slug.
So I have changed my code to this:
import { computed, defineComponent } from "#vue/composition-api";
import { useGetCategory } from "#logic/get-category";
import CategoryTitle from "#components/category-title/category-title.component.vue";
import Products from "#components/products/products.component.vue";
export default defineComponent({
name: "Categories",
components: { CategoryTitle, Products },
setup(_, context) {
const result = computed(() => {
return useGetCategory(context.root.$route.params.slug);
});
return { result };
},
});
which means I have to update my template to this:
<template>
<div v-if="result">
<category-title
v-if="result.category.value"
:category="result.category.value"
>
</category-title>
<base-loader :loading="result.loading.value"> </base-loader>
<products
:category="result.category.value"
v-if="result.category.value"
></products>
</div>
</template>
<script src="./category.component.ts" lang="ts"></script>
<style src="./category.component.scss" lang="scss" scoped></style>
Which is just ugly.
My question is this, can I destructure the computed property or something so my template can stay the same as it was?

You can destructure object returned by your computed but you will lose reactivity (category, loading, error variables created by destructuring computed value will not be updated when computed re-evaluates)
What you want is to use Vue Apollo ability to refresh query when it's variables change
import { useQuery, useResult } from "#vue/apollo-composable";
import * as getCategoryBySlug from "#graphql/api/query.category.gql";
export function useGetCategory(params) {
const { result, loading, error } = useQuery(getCategoryBySlug, params);
const category = useResult(result, null, (data) => data.categoryBySlug);
return { category, loading, error };
}
in component...
import { computed, defineComponent } from "#vue/composition-api";
import { useGetCategory } from "#logic/get-category";
import CategoryTitle from "#components/category-title/category-title.component.vue";
import Products from "#components/products/products.component.vue";
export default defineComponent({
name: "Categories",
components: { CategoryTitle, Products },
setup(_, context) {
const params = computed(() =>
return {
slug: context.root.$route.params.slug
}
)
const { category, loading, error } = useGetCategory(params);
},
});

Related

How to use $store.commit in Nuxt with #vue/composition-api

<template>
<div>
<h1>Vuex Typescript Test</h1>
<button #click="handleLogin">click</button>
</div>
</template>
<script lang="ts">
import { defineComponent } from '#vue/composition-api'
export default defineComponent({
setup() {
return {
handleLogin() {
// something....
},
}
},
})
</script>
#vue/composition-api do not apply useStore
I want to use store in setup function.
You should be able to access the useStore composable in the setup function according to the documentation of Vuex.
Your script section will look like this:
import { defineComponent } from '#vue/composition-api';
import { useStore } from 'vuex';
export default defineComponent({
setup() {
return {
const store = useStore();
return {
handleLogin {
store.dispatch('auth/login');
},
};
}
},
});
The proper way to structure the content of setup would be to move the handleLogin as a separate const and expose the constant in the return, in order to keep the return section more readable like this:
setup() {
const store = useStore();
const handleLogin = () => {
store.dispatch('auth/login');
};
return {
handleLogin,
}
}

How to show return data from vue apollo within a <script setup> syntax

I followed this tutorial in Vue Apollo for retrieving data with fake api https://www.apollographql.com/blog/frontend/getting-started-with-vue-apollo/.
I however have a code where I use <script setup></script> instead of the usual setup() method where everything there is placed.
How should I return data on my elements in this instance?
So this is currently my vue file:
<script setup>
import { storeToRefs } from 'pinia';
import { useAuthStore, useUsersStore } from '#/stores';
import gql from 'graphql-tag'
import { useQuery } from '#vue/apollo-composable'
const authStore = useAuthStore();
const { user: authUser } = storeToRefs(authStore);
const usersStore = useUsersStore();
const { users } = storeToRefs(usersStore);
usersStore.getAll();
apollo: gql`
query Characters {
characters {
results {
id
name
image
}
}
}
`
const { result, loading, error } = useQuery(CHARACTERS_QUERY);
}
</script>
<template>
<div>
<h1>Hi {{authUser?.firstName}}!</h1>
<div v-if="users.loading" class="spinner-border spinner-border-sm"></div>
<div v-if="users.error" class="text-danger">Error loading users: {{users.error}}</div>
<p v-if="error">Something went wrong...</p>
<p v-if="loading">Loading...</p>
<p v-else v-for="character in result.characters.results" :key="character.id">
{{ character.name }}
</p>
<div></div>
</div>
</template>
Then my main.js is:
import { createApp, provide, h } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import { router } from './helpers';
// setup fake backend
import { fakeBackend } from './helpers';
fakeBackend();
import { ApolloClient, InMemoryCache } from '#apollo/client/core'
import { DefaultApolloClient } from '#vue/apollo-composable'
import { createApolloProvider } from '#vue/apollo-option'
const cache = new InMemoryCache()
const apolloClient = new ApolloClient({
cache,
uri: 'https://rickandmortyapi.com/graphql',
})
const apolloProvider = createApolloProvider({
defaultClient: apolloClient,
})
const app = createApp({
setup () {
provide(DefaultApolloClient, apolloClient)
},
render: () => h(App),
})
app.use(apolloProvider)
app.use(createPinia());
app.use(router);
app.mount('#app');
With the <script setup></script> it is showing this error?
screenshot of error
Seems like you made mistake during copypasting.
Replace this:
apollo: gql`
query Characters {
characters {
results {
id
name
image
}
}
}
`
With this:
const CHARACTERS_QUERY = gql`
query Characters {
characters {
results {
id
name
image
}
}
}
`

Pinia getter does not filter state (options API)

I have a pinia store I am using with a vue component, with the options API. I have a getter in my pinia store that is supposed doing some basic filtering of items. However, the getter just returns what is in state without any of the filtering applied.
My component:
<template>
<DetailsWrapper :filteredDetails="filteredDetails"
</template>
<script>
import {mapState, } from 'pinia';
export default {
components: DetailsWrapper,
computed: {
...mapState(useDetailsStore, {
filteredDetails: store => store.filteredDetails,
},
};
</script>
In my pinia store I have:
import axios from 'axios';
import { defineStore } from 'pinia';
const useDetailsStore = defineStore('details', {
getters: {
filteredDetails: state => {
const productDetails = state.product && state.product.details;
productDetails.forEach(detail => {
detail.values.filter(detail => detail.isOnline && detail.isDisplayable
});
return productDetails
},
});
export default useDetailsStore
The end result is just that everything in productDetails is returned -- nothing is filtered out, even though there are definitely values to be filtered.
If anyone could provide any guidance it would be much appreciated!
You can try like this:
import axios from 'axios';
import { defineStore } from 'pinia';
const useDetailsStore = defineStore('details', {
getters:
{
filteredDetails(state)
{
return (state.product?.details || []).map(product => ({
...product,
values: product.values.filter(val => val.isOnline && val.isDisplayable),
});
},
});
export default useDetailsStore;

Vue warn]: Property "isMobileTerminal" was accessed during render but is not defined on instance

I'm working on a project that's both mobile and PC,I need to estimate the mobile terminal or PC terminal。
flexible.js
import { computed } from 'vue'
import { PC_DEVICE_WIDTH } from '../constants'
import { useWindowSize } from '#vueuse/core/index'
const { width } = useWindowSize()
// 判断当前是否为移动设备,判断依据屏幕宽度是否小于一个指定宽度(1280)
export const isMobileTerminal = computed(() => {
return width.value < PC_DEVICE_WIDTH
})
and the navigation/index.vue code is
<template>
<mobile-navigation v-if="isMobileTerminal"></mobile-navigation>
</template>
<script>
import { isMobileTerminal } from '../../../../utils/flexible'
import mobileNavigation from './mobile/index.vue'
export default {
name: 'index',
components: {
mobileNavigation
}
}
</script>
<style lang="scss" scoped></style>
My project catalog is shown below
isMobileTerminal is only imported in your component. It also needs to be made available to the template by declaring it in your component definition.
Returning it from the setup() hook is one way to do that:
<script>
import { isMobileTerminal } from '../../../../utils/flexible'
export default {
setup() {
return {
isMobileTerminal
}
}
}
</script>

I built a simple vuejs app with vuex but I would like to use mapGetters, how can I implement that function on it?

I built a simple vuejs app with vuex but I would like to use mapGetters, how can I implement that function on it?
this is my index.js:
import { mapGetters } from 'vuex'
import { createStore } from 'vuex'
import axios from 'axios'
export default createStore({
state: {
counter: 0,
colourCode: 'blue'
},
getters: {
counterSquared(state){
return state.counter * state.counter
}
},
and currently this how the vue component looks like:
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<div
:style="{color: $store.state.colourCode}"
class="counter">
{{$store.state.counter}}
</div>
<div class="counter-squared">
{{$store.state.counter}}
<sup>2</sup> =
{{$store
.getters.counterSquared}}
</div>
How can I change it to using mapGetters?
Here's what you can do, in VUE 3
<script>
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
setup () {
const store = useStore()
return {
counterSquared: computed(() => store.getters.counterSquared)
}
}
}
</script>
Using setup template :
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
const counterSquared: computed(() => store.getters.counterSquared)
}
}
</script>
Vue 2
<script>
import { mapGetters } from 'vuex'
export default {
// you can call it like this.counterSquad, counterSquad(in template)
computed: {
...mapGetters([
'counterSquared',
// ...
])
}
// OR
// you can call it like this.cS, cS(in template) base on defined name.
computed: {
...mapGetters({
cS: 'counterSquared'
})
])
}
}
</script>