Related
how can i update my local apollo cache after mutation in vue apollo composable please help here is my add recipe code and I want to update local apollo cache and also make changes to take effect in the UI ?
const people = [
{ id: 1, name: "category" },
{ id: 2, name: "Breakfast" },
{ id: 3, name: "Dinner" },
{ id: 4, name: "Lunch" },
];
const selectedPerson = ref(people[0]);
const user = userLoginStore();
const router = useRouter();
const { mutate: insertRecipe, result, onDone } = useMutation(INSERT_RECIPE);
onDone((result) => {
});
const onSubmit = (values) => {
insertRecipe(
{
title: values.title,
description: values.description,
category: selectedPerson.value.name,
image: values.image,
ingredients: ingredient.value.ingredients.toString(),
preparation_time: values.preparation_time,
steps: ingredient.value.steps.toString(),
},
cache.writeDate({})
);
};
</script>```
here is the update cache code after mutation
{
update: (cache, { data: { insert_recipe_one } }) => {
let data = cache.readQuery({ query: ALL_RECIPE });
// data.recipe = [...data.recipe, insert_recipe_one];
console.log(data);
data = {
...data,
recipe: [...data.recipe, insert_recipe_one],
};
console.log(data);
cache.writeQuery({ query: ALL_RECIPE, data });
},
}
It might be my lack of knowledge, but I seem to run into this issue no matter what I do.
I have this component that I want to do a query whenever the route slug changes.
Origianlly it was set up like this:
import {
computed,
defineComponent,
getCurrentInstance,
ref,
} from "#vue/composition-api";
import Brands from "#components/brands/brands.component.vue";
import Chooser from "#components/chooser/chooser.component.vue";
import Products from "#components/products/products.component.vue";
import { useListProducts } from "./list-products";
import { useSearchBrands } from "#logic/search-brands";
import { useGetCategory } from "#logic/get-category";
export default defineComponent({
name: "ProductList",
components: { Brands, Chooser, Products },
setup() {
const instance = getCurrentInstance();
const request = computed(() => {
return {
searchTerm: "*",
itemsToShow: 12,
filters: [
{
key: "Categories[]/Slug",
value: `'${instance.proxy.$route.params.categorySlug}'`,
},
],
includePartialMatches: false,
};
});
const orderBy = ref([
{ key: "InVenue", value: "desc" },
{ key: "Promoted", value: "desc" },
]);
const { category, categoryError, categoryLoading } =
useGetCategory(instance);
const {
brands,
brandError,
brandFacets,
brandsLoading,
brandsHasMoreResults,
brandsItemsToShow,
brandsTotal,
brandsFetchMore,
brandsRefetch,
} = useSearchBrands(request);
const {
products,
productError,
productFacets,
productsLoading,
productsHasMoreResults,
productsItemsToShow,
productsTotal,
productsFetchMore,
productsRefetch,
} = useListProducts(instance, orderBy);
return {
brands,
brandError,
brandFacets,
brandsLoading,
brandsHasMoreResults,
brandsItemsToShow,
brandsTotal,
category,
categoryError,
categoryLoading,
products,
productError,
productFacets,
productsLoading,
productsHasMoreResults,
productsItemsToShow,
productsTotal,
brandsFetchMore,
productsFetchMore,
brandsRefetch,
productsRefetch,
};
},
});
This looks like it works, when I change routes, everything changes as expected. But I have tracking enabled and what I am seeing is that when I navigate to the parent page (in this case, products) the code fires again, but with a null slug, which is not good.
So, I changed my code to this:
import {
computed,
defineComponent,
getCurrentInstance,
reactive,
ref,
toRefs,
watch,
} from "#vue/composition-api";
import Brands from "#components/brands/brands.component.vue";
import Chooser from "#components/chooser/chooser.component.vue";
import Products from "#components/products/products.component.vue";
import { useListProducts } from "./list-products";
import { useSearchBrands } from "#logic/search-brands";
import { useGetCategory } from "#logic/get-category";
export default defineComponent({
name: "ProductList",
components: { Brands, Chooser, Products },
setup() {
const instance = getCurrentInstance();
const categoryResult = reactive({
category: null,
categoryError: null,
categoryLoading: null,
});
const brandsResult = reactive({
brands: [],
brandError: null,
brandFacets: [],
brandsLoading: false,
brandsHasMoreResults: false,
brandsItemsToShow: null,
brandsTotal: 0,
brandsFetchMore: null,
brandsRefetch: null,
});
const productsResult = reactive({
products: [],
productError: null,
productFacets: [],
productsLoading: false,
productsHasMoreResults: false,
productsItemsToShow: null,
productsTotal: 0,
productsFetchMore: null,
productsRefetch: null,
});
const list = (slug) => {
if (!slug) return;
const request = ref({
searchTerm: "*",
itemsToShow: 12,
filters: [
{
key: "Categories[]/Slug",
value: `'${slug}'`,
},
{
key: "InnovationGuide",
value: "true",
},
],
includePartialMatches: false,
});
const orderBy = ref([
{ key: "InVenue", value: "desc" },
{ key: "Promoted", value: "desc" },
]);
Object.assign(brandsResult, useSearchBrands(request));
Object.assign(categoryResult, useGetCategory(instance));
Object.assign(productsResult, useListProducts(instance, orderBy));
};
watch(
() => instance.proxy.$route.params.categorySlug,
(slug) => list(slug)
);
list(instance.proxy.$route.params.categorySlug);
return {
...toRefs(brandsResult),
...toRefs(categoryResult),
...toRefs(productsResult),
};
},
});
Again, this looks like it works. I don't have the issue between child and parent anymore, but the problem is, if I navigate from /products/speakers to /products/cameras I get this error:
[![enter image description here][1]][1]
I have tried many many many times to fix this and I have never been able to. It seems that vue apollo is not great and the documentation is far from perfect.
Currently, I have it setup like this:
import { useGetUser } from "#logic/get-user";
import {
ApolloClient,
InMemoryCache,
createHttpLink,
} from "#apollo/client/core";
import { setContext } from "#apollo/client/link/context";
const uri = `${process.env.VUE_APP_API_URL}/graphql`;
const link = createHttpLink({
uri,
});
const cache = new InMemoryCache();
const authLink = setContext(async (_, { headers }) => {
const user = useGetUser();
const token = user ? `Bearer ${user.token}` : "";
if (!token) return { headers: { ...headers } };
return {
headers: {
...headers,
authorization: token || "",
},
};
});
const apiClient = new ApolloClient({
link: authLink.concat(link),
cache,
});
export default apiClient;
which in my main.ts I register like this:
import apiClient from "./_core/plugins/vue-apollo-api";
new Vue({
router,
store,
vuetify,
setup() {
provide(ApolloClients, {
default: apiClient,
apiClient,
contentfulClient,
});
},
render: (h) => h(App),
}).$mount("#app");
If I try to add:
import VueApollo from "vue-apollo";
const apolloProvider = new VueApollo({
clients: {
apiClient,
contentfulClient
},
defaultClient: apiClient
});
I get a whole load of errors stating:
1: Type ApolloClient is missing the following properties from type 'ApolloClient': store, writeData, initQueryManager
2: Type 'ApolloClient' is not assignable to type 'ApolloClient'
But no matter how many pages I search and use, I can never get this to work.
Can someone please help as it's stopping production now and tbh, I wish I had never bothered with graphql because of it.
[1]: https://i.stack.imgur.com/oqq59.png
This is probably just a basic vue question, but I am using Vue Apollo.
I have this component that is setup like this:
export default defineComponent({
name: "Products",
components: { Product },
props: {
categorySlug: {
type: String,
required: true,
},
},
setup(props) {
const { categorySlug } = toRefs(props);
const page = ref(1);
const skip = ref(0);
const result = reactive({ search: null, laoding: null, error: null });
Object.assign(result, useListProducts(categorySlug.value, page.value));
const more = () => {
skip.value += 12;
page.value += 1;
Object.assign(result, useListProducts(categorySlug.value, page.value));
};
return { ...toRefs(result), ...{ skip, more } };
},
});
As you can see I have a reactive object setup which is assigned to when the component setup method is invoked:
const result = reactive({ search: null, laoding: null, error: null });
Object.assign(result, useListProducts(categorySlug.value, page.value));
In my template, I iterate over the results like this:
<base-fade-up class="row" :duration="0.1" tag="div">
<v-col
cols="12"
xl="2"
v-for="(product, index) in products"
:key="product.id"
:data-index="index"
:data-skip="skip"
>
<product class="product" :product="product"></product>
</v-col>
</base-fade-up>
The problem I have, is that I want to add a more button:
<v-btn color="primary" #click="more()" v-if="search.hasMoreResults"
>Show more</v-btn
>
When pressed, it executes the more() method:
const more = () => {
skip.value += 12;
page.value += 1;
Object.assign(result, useListProducts(categorySlug.value, page.value));
};
As you can see, I am now getting page 2, so page one products are no longer in the search object.
What I would like to do is get a copy of the products from the first query and then add to them every time the more button is pressed.
I tried this:
setup(props) {
const { categorySlug } = toRefs(props);
const page = ref(1);
const skip = ref(0);
const products = ref([]);
const result = reactive({ search: null, laoding: null, error: null });
Object.assign(result, useListProducts(categorySlug.value, page.value));
const more = () => {
skip.value += 12;
page.value += 1;
Object.assign(result, useListProducts(categorySlug.value, page.value));
};
products.value.push(result.search.value.items);
return { ...toRefs(result), ...{ products, skip, more } };
},
but when I run this code, I get an error:
[Vue warn]: Error in data(): "TypeError: Cannot read property 'value' of null"
which is complaining about result.search.value in this line:
products.value.push(result.search.value.items);
I believe it's because the promise has not been resolved.
I am using Apollo's useResult method inside my useListProducts which looks like this:
import { useQuery, useResult } from "#vue/apollo-composable";
import * as listProducts from "#/graphql/api/query.products.gql";
export function useListProducts(slug: string, page = 1, itemsToShow = 12) {
const request: {
identifier?: string;
searchTerm: string;
itemsToShow: number;
page: number;
filters: any;
facets: string[];
} = {
searchTerm: "*",
itemsToShow,
page,
filters: [
{
key: "CategorySlug",
value: `'${slug}'`,
},
],
facets: ["Criteria/Attribute,count:100"],
};
const { result, loading, error } = useQuery(listProducts, {
search: request,
});
const search = useResult(result, null, (data) => data.search);
return { search, loading, error };
}
Can anyone tell me what I am doing wrong?
After some digging around, I found something in the documentation about pagination (which is essentially what I am doing), so because of that I managed to adjust my code to this:
export default defineComponent({
name: "Products",
components: { Product },
props: {
categorySlug: {
type: String,
required: true,
},
},
setup(props) {
const { categorySlug } = toRefs(props);
const page = ref(1);
const skip = ref(0);
const { search, loading, error, fetchMore } = useListProducts(
categorySlug.value,
page.value
);
const more = () => {
skip.value += 12;
page.value += 1;
const request = {
...params,
...{
page: page.value,
filters: [
{
key: "CategorySlug",
value: `'${categorySlug.value}'`,
},
],
},
};
fetchMore({
variables: { search: request },
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
//return fetchMoreResult;
console.log(fetchMoreResult);
return Object.assign({}, prev, {
search: {
hasMoreResults: fetchMoreResult.search.hasMoreResults,
total: fetchMoreResult.search.total,
facets: [...prev.search.facets, ...fetchMoreResult.search.facets],
items: [...prev.search.items, ...fetchMoreResult.search.items],
__typename: prev["__typename"],
},
});
},
});
};
return { search, loading, error, skip, more };
},
});
Which satisfies everything Estus mentioned in the comments, but it does throw an error, which I will create a new post about :D
I'm trying to integrate PayPal buttons with my Vuejs3 project using Composition API (setup ) but all what i get is errors i try to integrate it without using setup and its working fine i leave the working script down
the esseu is i couldent pass data from data to methodes
<script>
import { inject, onMounted, ref } from "vue";
export default {
data() {
return {
loaded: false,
paidFor: false,
product: {
price: 15.22,
description: "leg lamp from that one movie",
img: "./assets/lamp.jpg",
},
};
},
setup() {
const store = inject("store");
console.log(store.state.prodects_in_cart);
return { store };
},methods:{
setLoaded: function() {
this.loaded = true;
paypal_sdk
.Buttons({
createOrder: (data, actions) => {
return actions.order.create({
purchase_units: [
{
description: this.product.description,
amount: {
currency_code: "USD",
value: this.product.price
}
}
]
});
},
onApprove: async (data, actions) => {
const order = await actions.order.capture();
this.data;
this.paidFor = true;
console.log(order);
},
onError: err => {
console.log(err);
}
})
.render(this.$refs.paypal);
}
},
mounted: function() {
const script = document.createElement("script");
script.setAttribute('data-namespace',"paypal_sdk");
script.src ="https://www.paypal.com/sdk/js?client-id=Here i pute my Client Id";
script.addEventListener("load", this.setLoaded);
document.body.appendChild(script);
},
};
</script>
the error i get when i use setup() is
The error image
my script using setup()
setup() {
const store = inject("store");
const paypal = ref(null);
let loaded = ref(false);
let paidFor = ref(false);
const product = {
price: 15.22,
description: "leg lamp from that one movie",
img: "./assets/lamp.jpg",
};
onMounted: {
const script = document.createElement("script");
script.setAttribute("data-namespace", "paypal_sdk");
script.src =
"https://www.paypal.com/sdk/js?client-id=AXDJPmFjXpXm9HMXK4uZcW3l9XrCL36AxEeWBa4rhV2-xFcVYJrGKvNowY-xf2PitTSkStVNjabZaihe";
script.addEventListener("load", ()=>{
loaded = true;
console.log('hello adil');
paypal_sdk
.Buttons({
createOrder: (data, actions) => {
return actions.order.create({
purchase_units: [
{
description: 'this is product description',
amount: {
currency_code: "USD",
value: 120.00,
},
},
],
});
},
onApprove: async (data, actions) => {
const order = await actions.order.capture();
this.data;
this.paidFor = true;
console.log(order);
},
onError: (err) => {
console.log(err);
},
})
.render(paypal);
});
document.body.appendChild(script);
}
return { store ,paypal};
}
paypal is a ref. You're currently passing to paypal_sdk the ref itself and not the inner value, which would be the template ref's element. To fix this, pass the ref's .value.
Your onMounted code is not properly invoked, as it must be passed a callback.
import { onMounted, ref } from 'vue'
export default {
setup() {
const paypal = ref(null)
onMounted(/* 2 */ () => {
const script = document.createElement('script')
//...
script.addEventListener('load', () => {
paypal_sdk
.Buttons(/*...*/)
.render(paypal.value) /* 1 */
})
})
return {
paypal
}
}
}
The reason why you are getting that error is because you are using option Api onMounted life cycle hook, instead of doing that use the vue 3 life cycle hooks for onMounted.
First you will have to import it from vue like this.
<script>
import {onMounted} from 'vue'
then you are going to use it like this.
return it as a call back function
onMounted(() => {
//all your code should placed inside here and it will work
})
</script>
Here is my answer using the paypal-js npm package
<template>
<div ref="paypalBtn"></div>
</template>
<script>
import { onMounted, ref } from 'vue';
import { loadScript } from '#paypal/paypal-js';
const paypalBtn = ref(null);
onMounted(async () => {
let paypal;
try {
paypal = await loadScript({
'client-id': 'you_client_id_goes_here',
});
} catch (error) {
console.error('failed to load the PayPal JS SDK script', error);
}
if (paypal) {
try {
await paypal.Buttons().render(paypalBtn.value);
} catch (error) {
console.error('failed to render the PayPal Buttons', error);
}
}
});
</script>
This is a very basic question but I'm not getting anywhere...
Doing a simple shopping cart with Vue and Vuex. I had the products hard-coded in the state but now I'm trying to do an axios call and add that result to the state. I have the axios function and I'm trying to load the function and have the JSON result appended to the array all.
How do I put the result of loadItems() into the all array? Thank you
EDIT: I have updated with my entire vuex file.
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import * as types from './mutation-types'
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production'
// axios call
const api = {
loadItems() {
// Init variables
var self = this
var app_id = "asdfasf";
var app_key = "asdfasfaf";
this.items = []
axios.get(
"https://api.airtable.com/v0/"+app_id+"/Products",
{
headers: { Authorization: "Bearer "+app_key }
}
).then(function(response){
self.items = response.data.records.map((item)=>{
return {
id: item.id,
...item.fields
}
})
}).catch(function(error){
console.log(error)
})
}
}
// initial state
const state = {
added: [],
all: []
}
// getters
const getters = {
allProducts: state => state.all, // would need action/mutation if data fetched async
getNumberOfProducts: state => (state.all) ? state.all.length : 0,
cartProducts: state => {
return state.added.map(({ id, quantity }) => {
const product = state.all.find(p => p.id === id)
return {
name: product.name,
price: product.price,
quantity
}
})
}
}
// actions
const actions = {
addToCart({ commit }, product){
commit(types.ADD_TO_CART, {
id: product.id
})
},
removeFromCart({ commit }, product){
commit(types.REMOVE_FROM_CART, {
id: product.id
})
}
}
// mutations
const mutations = {
[types.ADD_TO_CART] (state, { id }) {
const record = state.added.find(p => p.id === id)
if (!record) {
state.added.push({
id,
quantity: 1
})
} else {
record.quantity++
}
},
[types.REMOVE_FROM_CART] (state, { id }) {
const record = state.added.find(p => p.id === id)
if (!record) {
state.added.pop({
id,
quantity: 1
})
} else {
record.quantity--
}
},
}
// one store for entire application
export default new Vuex.Store({
state,
strict: debug,
getters,
actions,
mutations
})
This is a little approach to your goal:
You can access with state.yourStateNameVariable or better make a getter and commit to get/set value from that state.
Observations:
[types.ADD_TO_CART] is not a good name for commit, maybe addToCart and removeFromCart?
You dont need to save in items your response from axios, you can directly after resolve send to all state with state.all = yourdata or better, add mutation
setAllData({ state }, data){
state.all = data
}
I do not fixed all your code but here you are an approach:
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import * as types from './mutation-types'
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production'
export default new Vuex.Store({
state:{
added: [],
all: [
{
id: 'cc919e21-ae5b-5e1f-d023-c40ee669520c',
name: 'COBOL 101 vintage',
description: 'Learn COBOL with this vintage programming book',
price: 399
},
{
id: 'bcd755a6-9a19-94e1-0a5d-426c0303454f',
name: 'Sharp C2719 curved TV',
description: 'Watch TV like never before with the brand new curved screen technology',
price: 1995
},
{
id: '727026b7-7f2f-c5a0-ace9-cc227e686b8e',
name: 'Remmington X mechanical keyboard',
description: 'Excellent for gaming and typing, this Remmington X keyboard ' +
'features tactile, clicky switches for speed and accuracy',
price: 595
}
]
},
getters: {
allProducts: state => state.all, // would need action/mutation if data fetched async
getNumberOfProducts: state => (state.all) ? state.all.length : 0,
cartProducts: state => {
return state.added.map(({ id, quantity }) => {
const product = state.all.find(p => p.id === id)
return {
name: product.name,
price: product.price,
quantity
}
})
}
},
mutations: {
setAllData({ state }, data){
state.all = data
},
[types.ADD_TO_CART] ({ state }, id) {
const record = state.added.find(p => p.id === id)
if (!record) {
state.added.push({
id,
quantity: 1
})
} else {
record.quantity++
}
},
[types.REMOVE_FROM_CART] ({ state }, id) {
const record = state.added.find(p => p.id === id)
if (!record) {
state.added.pop({
id,
quantity: 1
})
} else {
record.quantity--
}
}
},
actions:{
loadItems({getters, commit}, somethingYouReceive) {
// Init variables
var self = this
var app_id = "adsfasfasgag";
var app_key = "agasdgagasgasg";
this.items = []
axios.get(
"https://api.airtable.com/v0/"+app_id+"/Products",
{
headers: { Authorization: "Bearer "+app_key }
}
).then(function(response){
commit('setAllData',response.data.records.map((item)=>{
return {
id: item.id,
...item.fields
}
})
}).catch(function(error){
console.log(error)
})
},
addToCart({ commit }, product){
commit(types.ADD_TO_CART, {
id: product.id
})
},
removeFromCart({ commit }, product){
commit(types.REMOVE_FROM_CART, {
id: product.id
})
}
}
})