how to update ui after graphql apollo mutation in vue3? - vue.js

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 });
},
}

Related

How to re-run useQuery and FlatList?

I use FlatList with useState.
const [state, setState] = useState(route);
<FlatList
keyboardDismissMode={true}
showsVerticalScrollIndicator={false}
data={state}
keyExtractor={(comment) => "" + comment.id}
renderItem={renderComment}
/>
When I change the datㅁ which is contained in state, I want to re-run Flatlist with new data.
So after I mutate my data, I try to rerun useQuery first in order to change state. I put refetch module here.
1)
const { data: updatePhoto, refetch } = useQuery(SEE_PHOTO_QUERY, {
variables: {
id: route?.params?.photoId,
},
});
If I put button, this onValid function will executed.
<ConfirmButton onPress={handleSubmit(onValid)}>
onValid function changes data and after all finished, as you can see I put refetch().
=> all this process is for that if I add comment and press confirm button, UI (flatlist) should be changed.
const onValid = async ({ comments }) => {
await createCommentMutation({
variables: {
photoId: route?.params?.photoId,
payload: comments,
},
});
await refetch();
console.log(updatePhoto);
};
But when I console.log data after all, it doesnt' contain added data..
what is the problem here?
If you need more explanation, I can answer in real time.
please help me.
add full code
export default function Comments({ route }) {
const { data: userData } = useMe();
const { register, handleSubmit, setValue, getValues } = useForm();
const [state, setState] = useState(route);
const [update, setUpdate] = useState(false);
const navigation = useNavigation();
useEffect(() => {
setState(route?.params?.comments);
}, [state, route]);
const renderComment = ({ item: comments }) => {
return <CommentRow comments={comments} photoId={route?.params?.photoId} />;
};
const { data: updatePhoto, refetch } = useQuery(SEE_PHOTO_QUERY, {
variables: {
id: route?.params?.photoId,
},
});
const createCommentUpdate = (cache, result) => {
const { comments } = getValues();
const {
data: {
createComment: { ok, id, error },
},
} = result;
if (ok) {
const newComment = {
__typename: "Comment",
createdAt: Date.now() + "",
id,
isMine: true,
payload: comments,
user: {
__typename: "User",
avatar: userData?.me?.avatar,
username: userData?.me?.username,
},
};
const newCacheComment = cache.writeFragment({
data: newComment,
fragment: gql`
fragment BSName on Comment {
id
createdAt
isMine
payload
user {
username
avatar
}
}
`,
});
cache.modify({
id: `Photo:${route?.params?.photoId}`,
fields: {
comments(prev) {
return [...prev, newCacheComment];
},
commentNumber(prev) {
return prev + 1;
},
},
});
}
};
const [createCommentMutation] = useMutation(CREATE_COMMENT_MUTATION, {
update: createCommentUpdate,
});
const onValid = async ({ comments }) => {
await createCommentMutation({
variables: {
photoId: route?.params?.photoId,
payload: comments,
},
});
await refetch();
console.log(updatePhoto);
};

Vue.js Cannot read properties of undefined (reading 'router') error

I'm new to Vue.js and I have created one simple form for the user and storing data using API.
On submit I'm calling this function:
setup(props, { emit }) {
const blankData = {
customer: '',
template: '',
rate: '',
property_from: '',
property_to: '',
move_date: '',
num_days: '',
token: '',
details: '',
customer_options: [],
template_options: [],
rate_options: [],
property_from_options: [],
property_to_options: [],
}
const userData = ref(JSON.parse(JSON.stringify(blankData)))
const resetuserData = () => {
userData.value = JSON.parse(JSON.stringify(blankData))
}
const toast = useToast()
const onSubmit = () => {
store.dispatch('app-user/addUser', userData.value)
.then(
response => {
if (response.status === 1) {
this.$router.push({ name: 'edit-user', params: { id: 10 } })
}
toast({
component: ToastificationContent,
props: {
title: response.message,
icon: response.toastIcon,
variant: response.toastVariant,
},
})
},
error => {
console.log(error)
},
)
}
const {
refFormObserver,
getValidationState,
resetForm,
} = formValidation(resetuserData)
return {
userData,
onSubmit,
refFormObserver,
getValidationState,
resetForm,
}
},
And trying to redirect the user to the edit page after user creation but I'm getting this error and not redirecting:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'router')
I have tried with this stackoverflow answer but getting same error:
const onSubmit = () => {
const self = this
store.dispatch('app-user/addUser', userData.value)
.then(
response => {
if (response.status === 1) {
self.$router.push({ name: 'edit-user', params: { id: 10 } })
}
},
error => {
console.log(error)
},
)
}
Any idea what I'm doing wrong in my code?
You're using Vue 3 and a setup function; there is no this in a setup function.
See Accessing the Router and current Route inside setup.
Untested, but probably something like this will work:
setup() {
const router = useRouter()
const onSubmit = () => {
// ... code omitted ...
router.push({ name: 'edit-user', params: { id: 10 } })
}
return {
onSetup,
// other stuff...
}
}
I think this might be help
router with composition api
https://next.router.vuejs.org/guide/advanced/composition-api.html

Adding result of Axios call to State

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
})
}
}
})

Nuxt asyncData result is undefined if using global mixin head() method

I'm would like to get titles for my pages dynamically in Nuxt.js in one place.
For that I've created a plugin, which creates global mixin which requests title from server for every page. I'm using asyncData for that and put the response into storage, because SSR is important here.
To show the title on the page I'm using Nuxt head() method and store getter, but it always returns undefined.
If I place this getter on every page it works well, but I would like to define it only once in the plugin.
Is that a Nuxt bug or I'm doing something wrong?
Here's the plugin I wrote:
import Vue from 'vue'
import { mapGetters } from "vuex";
Vue.mixin({
async asyncData({ context, route, store, error }) {
const meta = await store.dispatch('pageMeta/setMetaFromServer', { path: route.path })
return {
pageMetaTitle: meta
}
},
...mapGetters('pageMeta', ['getTitle']),
head() {
return {
title: this.getTitle, // undefined
// title: this.pageMetaTitle - still undefined
};
},
})
I would like to set title in plugin correctly, now it's undefined
Update:
Kinda solved it by using getter and head() in global layout:
computed: {
...mapGetters('pageMeta', ['getTitle']),
}
head() {
return {
title: this.getTitle,
};
},
But still is there an option to use it only in the plugin?
Update 2
Here's the code of setMetaFromServer action
import SeoPagesConnector from '../../connectors/seoPages/v1/seoPagesConnector';
const routesMeta = [
{
path: '/private/kredity',
dynamic: true,
data: {
title: 'TEST 1',
}
},
{
path: '/private/kreditnye-karty',
dynamic: false,
data: {
title: 'TEST'
}
}
];
const initialState = () => ({
title: 'Юником 24',
description: '',
h1: '',
h2: '',
h3: '',
h4: '',
h5: '',
h6: '',
content: '',
meta_robots_content: '',
og_description: '',
og_image: '',
og_title: '',
url: '',
url_canonical: '',
});
export default {
state: initialState,
namespaced: true,
getters: {
getTitle: state => state.title,
getDescription: state => state.description,
getH1: state => state.h1,
},
mutations: {
SET_META_FIELDS(state, { data }) {
if (data) {
Object.entries(data).forEach(([key, value]) => {
state[key] = value;
})
}
},
},
actions: {
async setMetaFromServer(info, { path }) {
const routeMeta = routesMeta.find(route => route.path === path);
let dynamicMeta;
if (routeMeta) {
if (!routeMeta.dynamic) {
info.commit('SET_META_FIELDS', routeMeta);
} else {
try {
dynamicMeta = await new SeoPagesConnector(this.$axios).getSeoPage({ path })
info.commit('SET_META_FIELDS', dynamicMeta);
return dynamicMeta && dynamicMeta.data;
} catch (e) {
info.commit('SET_META_FIELDS', routeMeta);
return routeMeta && routeMeta.data;
}
}
} else {
info.commit('SET_META_FIELDS', { data: initialState() });
return { data: initialState() };
}
return false;
},
}
}

Is it possible to mutate a state prop in a module using another’s module mutator in vuejs2?

Lets say we have something like this:
const moduleA = {
state: { name: 'Cristian' },
namespaced: true,
}
const moduleB = {
state: { color: 'red' },
namespaced: true,
}
const store = new Vuex.Store({
state: { user: null, },
modules: {
a: moduleA,
b: moduleB
},
mutations: {
setPropValue(state, payload) {
for (let [key, value] of Object.entries(payload)) {
state[key] = value;
}
},
},
})
On my vue instance I have a firebase observer to get when the user logs in/out and change state accordingly. So what I want to achieve is something like this:
const payload = {
module: 'root',
payload: { user, },
}
this.$store.commit('setPropValue', {payload});
then for another module:
const payload = {
module: 'a',
payload: { name: 'Alexander', },
}
and run the same logic, but with different load and change the props from module a:
this.$store.commit('setPropValue', {payload});
Instead of committing to the mutation directly, consider using an action
this.$store.dispatch('setPropValue', { user });
Then in vuex, your action
actions: {
setPropValue({ commit }, { user }) {
commit("a/setName", user.name);
commit("b/setColor", user.favoriteColor);
}
}
Each module will need its own 'local' mutation in order to modify that modules state.
See https://vuex.vuejs.org/en/modules.html for details