Vuefire data not subscribing to changes - javascript

I have several different queries that I need to keep 'live' data from. When I hardcode the query it successfully shows all live changes that happen in the database. But when I pass a payload the data won't change until reloaded.
Working Query:
getOnline: firestoreAction(({ bindFirestoreRef }) => {
return bindFirestoreRef('online', db.collection('units').where('unit.on', '==', true).orderBy('info.timestamp', 'desc').orderBy('unit.status'))
}),
Not working Payload Query: gets data once
getFilterSystemId: firestoreAction(({ bindFirestoreRef} , payload) => {
return bindFirestoreRef('filterBySystemId', db.collection('units').where('unit.group', '==', payload.group).orderBy('info.timestamp', 'desc').orderBy('unit.status'))
}),
I pass the payload simply:
filterSystemId(grouphex){
let payload = {
group: grouphex.toString(),
}
this.getFilterSystemId(payload);
},
How do I pass a payload AND get live updates to any changes that happen in the database?

Ended up using vuefire instead of vuexfire and dynamically binding my queries like this.
const vuefire = db.collection('vuefire')
export default {
components: {},
data() {
return {
//view
vuefire: [],
id: 'true',
user: [],
code: 'true'
};
},
created() {
},
// firestore: {
// vuefire: db.collection('vuefire')
// }
watch: {
id: {
// call it upon creation too
immediate: true,
handler(id) {
this.$bind('user', vuefire.where('a', '==', id))
},
},
Anytime 'id' changes the dataset ('user') is recomputed and accomplishes my goal

Related

Vue component is not reactive

My issue is that when i make a request to delete an item from my component, the component does not automatically update to show new state.
template
<div v-for="house in allHouses" :key="house.id" >
<div class="edit-delete-wrap" v-if="house.madeByMe">
<img class="edit-delete-btn" src="/images/edit.png" alt="">
<img #click="deleteHouse(house.id)" class="edit-delete-
btn" src="/images/delete.png" alt="" srcset="">
</div>
{{house.street}}
</div>
this is an example of the template, it is card with a house details on it, there is a button to delete this item from the list.
Scripts for house card component
<script>
import {mapActions, mapGetters} from 'vuex'
export default {
name: "HouseCard",
props: ["searchHouses", "sortHouses"],
computed: {
...mapGetters(['allHouses']),
},
methods: {
...mapActions(['fetchHouses', 'houseDetail', 'deleteHouse']),
},
created(){
this.fetchHouses()
},
}
</script>
The list data comes from the allHouses houses computed function.
vuex store
import api from "../../api/houses";
const state = {
houses: [],
selectedHouse: [],
};
const getters = {
allHouses: (state) => state.houses,
selectedHouse: (state) => state.selectedHouse,
};
const actions = {
async fetchHouses({ commit }) {
const response = await api.fetchHouses();
commit("setHouses", response.data);
console.log(response.data);
},
createNewHouse(_, formData) {
api.createNewHouse(formData);
},
async deleteHouse(_, house) {
api.deleteHouse(house)
const response = await api.fetchHouses();
commit("setHouses", response.data);
},
async houseDetail({ commit }, id) {
const response = await api.fetchHouses();
response.data.forEach((house) => {
if (house.id === id) {
console.log(house);
commit("setSelectedHouse", house);
}
});
},
};
const mutations = {
setHouses: (state, houses) => {
state.houses = houses;
},
setSelectedHouse: (state, selectedHouse) => {
state.selectedHouse = selectedHouse;
},
};
export default {
state,
getters,
actions,
mutations,
};
here is the store where i have the manage the state of the app, in the deleteHouse action function i delete the house then try to get a new api response and set the state of houses to the new updated state of the houses array.
api
import axios from "axios";
const API_KEY = "xxxxxxxxx";
export default {
fetchHouses() {
return axios.get("https://api.intern.d-tt.nl/api/houses", {
headers: {
"X-Api-Key": API_KEY,
},
});
},
async deleteHouse(id) {
axios
.delete(`https://api.intern.d-tt.nl/api/houses/${id}`, {
headers: {
"X-Api-Key": API_KEY,
},
})
.then(() => {
console.log("successful deletion");
});
},
createNewHouse(formData) {
console.log("api page", formData);
const image = formData.houseImage;
return axios
.post("https://api.intern.d-tt.nl/api/houses", formData.form, {
headers: {
"X-Api-Key": API_KEY,
},
})
.then((res) => {
console.log("REACHED FIRST POST");
const id = res.data.id;
const formData = new FormData();
formData.append("image", image[0]);
axios
.post(
`https://api.intern.d-tt.nl/api/houses/${id}/upload`,
formData,
{
headers: {
"X-Api-Key": API_KEY,
},
}
)
.then(console.log("success"))
.catch((err) => console.log(err));
})
.catch((err) => {
console.log(err);
});
},
};
here is the api file that i use to make all api requests. i hope this information helps.
Per #RenauldC5, you didn't provide enough information about your allHouses getter or where and how the data gets into the store or where setHouses stores the data, and how.
However a question/tips to guide you:
#setHouses - I presume your api returns an array .. (?) So make sure you initialize the property (key) in the store (at whatever key #setHouses stores this array) at instantiation so that the store and Vue instances that use the allHouses getter know to watch that array property
#deleteHouses - When you delete the array, you seem to return a new array from the api. If this is true, simply setting the new non-reactive array overtop of the old reactive one will create an unreactive condition. This is a fundamental understanding of Vue's reactivity system - and is likely the cause of your problem.
Fixes:
Whatever key #setHouses uses to set data on the Vuex store, instantiate it as an empty array when the store is created.
#setHouses must iterate response.data and array.push(item) onto this reactive array, rather than simply replace the reactive array with a new (non-reactive) array.
#deleteHouse - should first use array.splice(0) to remove all children in the reactive array, then setHouses will array.push(child) into this reactive array
Update: including examples
//- update: state:
const state = {
houses: [],
selectedHouse: null,
};
//- update: #setHouses
setHouses: (state, houses) => {
// empty the previous reactive array
state.houses.splice(0);
// push the new houses to the original reactive array
state.houses.push(...houses);
// state.houses now remains bound to your getters, vue instances and remains reactive
},
Update: add examples of changes
//- update:state
const state = {
houses: [],
selectedHouse: null,
};
//- update:#setHouses
setHouses: (state, houses) => {
// empty the previous reactive array
state.houses.splice(0);
// push the new houses to the original reactive array
state.houses.push(...houses);
// state.houses now remains bound to your getters, vue instances and remains reactive
},
PS: maybe I'm not clear on what your action #houseDetail does but it seems to re-load ALL houses ... perhaps this is what you want (?)

VUEX mutate array data each request

I have some data from an API that I am storing in VUEX and then displaying in the UI. On initial page load there is a request that pulls in the initial data and displays. All works well. My issues is When I now submit a form input for another request using an event handler I am just pushing to the array and it is adding to the array (which makes sense) and creates another instance below the current data which I do not want. Is there a way to actually CHANGE / MUTATE the current data that is in the array and update the UI with the new values?
STORE
import { createStore } from 'vuex';
import axios from 'axios';
export default createStore({
state: {
ipData: [],
currentIP: '',
},
mutations: {
SET_CURRENT_IP(state, currentIP) {
state.currentIP = currentIP;
},
SET_IP_DATA(state, ipData) {
state.ipData.push(ipData);
},
},
});
FORM SUBMIT
methods: {
async submitForm() {
const isFormValid = await this.v$.$validate();
if (!isFormValid) return;
axios
.get(`${this.url}${this.getIP}`, {
headers,
})
.then((response) => {
this.$store.commit('SET_IP_DATA', response.data);
})
.catch((error) => {
console.log(error.response);
});
},
},
VUEX OBJECT:
ipData:Array[1]
0:Object
as:Object
domains:Array[5]
ip:"8.8.8.8"
isp:"Google LLC"
location:Object
city:"Mountain View"
country:"US"
geonameId:5375480
lat:37.38605
lng:-122.08385
postalCode:"94035"
region:"California"
timezone:"-07:00"
If your ipData is array of object, you can create another mutation for updating your array (use id or some other identifier to match right object):
UPDATE_IP_DATA(state, payload) {
state.ipData = [
...state.ipData.map((item) =>
item.id !== payload.id
? item
: {
...item,
...payload,
}
),
];
}

Pass id from Vue router param to component's Vuex dispatch

I have backend Laravel API where i get
{
"id": 1,
"name": "Toni Stoyanov",
"email": "toni.nikolaev.23#gmail.com"
},
"id": 2,
"name": "Thomas Shelby",
"email": "st3851ba#gmail.com"
}
]
In my routes in Vue :
{
path: '/users/:id',
component: UserDetails,
props:true
},
I want to Display every single user for example with users/1 i want to get my first record from API.
In Vuex state i have :
namespaced: true,
state(){
return{
users: {
}
}
},
getters:
getUsers(state){
return state.users
}
mutations:
SET_USER(state, data){
state.users = data
}
and this action where i fetch all users:
async setUsers(context){
let response = await axios.get('users/all')
context.commit('SET_USER',response.data)
}
In my DisplayUser.vue i have :
props:['id'],
data(){
return{
selectedUser: null
}
},
created(){
this.$store.dispatch('users/setUsers')
this.selectedUser = this.$store.getters['users/getUsers'].find(user => user.id === this.id);
},
First i dispatch my action to get data from API and after that in selectedUsers try to find the same id user..but in console i get
this.$store.getters['users/getUsers'].find is not a function.
If i set in users static data everything works sometimes! But when i fetch them from API no.
You're trying to access the getter before the http request has completed. It should be enough to wait for the promise in created.
The prop is a string
Also, the prop is coming as a string, so a === will not match. You can cast it to a Number(this.id) first:
Using .then syntax:
created(){
this.$store.dispatch('users/setUsers').then(res => {
this.selectedUser = this.$store.getters['users/getUsers'].find(user => user.id === Number(this.id));
});
}
Or using async/await syntax:
async created(){ // async keyword
await this.$store.dispatch('users/setUsers') // await this
this.selectedUser = this.$store.getters['users/getUsers'].find(user => user.id === Number(this.id));
}

My mutations aren't working! How do I correct this?

I am trying to set the breakfastMenu array in state as shown below but I can't see the array being filled in my vue-devtools.
I have properly set-up the Vuex methods and checked twice, also I didn't receive any sort of error. So, why do I have a logical error in my code?
store.js:
export default new Vuex.Store({
state: {
menu: [],
breakfastMenu: [],
lunchMenu: [],
dinnerMenu: []
},
mutations: {
'SET_MENU': (state, menuMaster) => {
state.menu = menuMaster;
},
'SET_BREAKFAST_MENU': (state, order) => {
state.breakfastMenu.unshift(order);
},
'SET_LUNCH_MENU': (state, order) => {
state.breakfastMenu.unshift(order);
},
'SET_DINNER_MENU': (state, order) => {
state.breakfastMenu.unshift(order);
},
},
actions: {
initMenu: ({ commit }, menuMaster) => {
commit('SET_MENU', menuMaster)
},
initBreakfastMenu: ({ commit, state }) => {
state.menu.forEach((element) => {
if (element.categoryId == 1) {
commit('SET_BREAKFAST_MENU', element)
}
});
},
initLunchMenu: ({ commit, state }) => {
state.menu.forEach((element) => {
if (element.categoryId == 2) {
commit('SET_LUNCH_MENU', element)
}
});
},
initDinnerMenu: ({ commit, state }) => {
state.menu.forEach((element) => {
if (element.categoryId == 3) {
commit('SET_DINNER_MENU', element)
}
});
},
},
getters: {
getBreakfastMenu(state) {
return state.breakfastMenu
},
getLunchMenu(state) {
return state.lunchMenu
},
getDinnerMenu(state) {
return state.dinnerMenu
},
}
})
Breakfast.vue:
import { mapActions, mapGetters } from 'vuex';
export default {
data() {
return {
breakfastArray: []
};
},
methods: {
...mapActions(['initBreakfastMenu']),
...mapGetters(['getBreakfastMenu']),
},
created() {
this.initBreakfastMenu;
this.breakfastArray = this.getBreakfastMenu;
}
};
No error messages so far!
I need the breakfastMenu array filled in store.js.
Please help out!
A few thoughts.
Firstly, this line:
this.initBreakfastMenu;
You aren't actually calling the method. It should be:
this.initBreakfastMenu();
Next problem is this:
...mapGetters(['getBreakfastMenu']),
The line itself is fine but it's inside your methods. It should be in the computed section.
You haven't included any sample data for state.menu but it's also worth noting that initBreakfastMenu won't do anything unless there is suitable data inside state.menu. I suggest adding some console logging to ensure that everything is working as expected there.
SET_BREAKFAST_MENU, SET_LUNCH_MENU and SET_DINNER_MENU are all modifying state.breakfastMenu. I would assume that this is incorrect and each should be modifying their respective menu.
I would also note that using local data for breakfastArray is suspicious. Generally you'd just want to use the store state directly via the computed property rather than referencing it via local data. This is not necessarily wrong, you may want to detach the component data from the store in this way, but keep in mind that both are referencing the same array so modification to one will also affect the other. You aren't taking a copy of the array, you're just creating a local reference to it.
You should also consider whether you actually need the 4 menu types within your state. If breakfastMenu, lunchMenu and dinnerMenu are all just derived from menu then you'd be better off just implementing those using getters. getters are the store equivalent of computed properties and can contain the relevant filtering logic to generate their value from state.menu.
initBreakfastMenu is an action and you may want to use this.initBreakfastMenu()

Updating Apollo store when mutating list filter

I have a graphql schema that looks like this :
type User {
entries(status: EntryStatus): [Entry]
}
type Mutation {
updateEntry(status: EntryStatus): Entry
}
I can filter the list of entries by status (which is an enum) and I can update the status of an entry. I'd like to update the store when the status is updated so that the entry appears in the right list (entries(status: FOO) to entries(status: BAR)).
I know I can update the store with the update method.
const withMutation = graphql(updateEntryMutation, {
props: ({ mutate }) => ({
updateEntry: updateEntryInput =>
mutate({
variables: { updateEntryInput },
update: (proxy, newData) => {
// update data here
// Which would involve removing the entry from its previous filtered "list", and adding it to the one with the new status
})
})
});
But how can I know from which list to remove the entry since I don't have access to the old data (previous entry.status) from update ?
(apart from enumerating all lists by status and removing the updated entry if I find it...)
You need to store.readQuery() first, then write the new data to the store.
Kinda like this:
const EntriesQuery = 'yourQueryHere';
const withMutation = graphql(updateEntryMutation, {
props: ({ mutate }) => ({
updateEntry: updateEntryInput =>
mutate({
variables: { updateEntryInput },
update: (store, { data: { updateEntry }}) => {
const data = store.readQuery({ query: EntriesQuery });
data.entries.push(updateEntry)
store.writeQuery({ query: EntriesQuery, data })
})
})
});

Categories