How to handle vuex store to fetch data from rest api? - javascript

I'm using Vuex to handle my application state.
I need to make an Ajax Get request to a rest api and then show some objects list.
I'm dispatching an action that loads this data from the server but then I don't know how to handle it on the component.
Now I have this:
//component.js
created(){
this.$store.dispatch("fetch").then(() => {
this.objs = this.$store.state.objs;
})
}
But I don't think that the assignment of the incoming data to the local property is the correct way to handle store data.
Is there a way to handle this better? Maybe using mapState?
Thanks!

There are many ways you can do it, you must experiment and find the one that fits your approach by yourself. This is what I suggest
{ // the store
state: {
something: ''
},
mutations: {
setSomething (state, something) {
// example of modifying before storing
state.something = String(something)
}
},
actions: {
fetchSomething (store) {
return fetch('/api/something')
.then(data => {
store.commit('setSomething', data.something)
return store.state.something
})
})
}
}
}
{ // your component
created () {
this.$store
.dispatch('fetchSomething')
.then(something => {
this.something = something
})
.catch(error => {
// you got an error!
})
}
}
For better explanations: https://vuex.vuejs.org/en/actions.html
Now, if you're handling the error in the action itself, you can simply call the action and use a computed property referencing the value in the store
{
computed: {
something () { // gets updated automatically
return this.$store.state.something
}
},
created () {
this.$store.dispatch('loadSomething')
}
}

Related

How to search an array of objects obtained by axios for an id? Vue 2

I am trying to verify if the user is inside that list that I capture by axios, the issue is that I have used the FILTER option but it always returns undefined or [], being that if the user exists in that array.
I can't think what else to do, because I validate if it is by console.log() the variable with which I ask and if it brings data.
created() {
this.getStagesDefault()
this.getSalesman()
this.getStagesAmountByUser()
},
methods: {
async getSalesman(){
const { data } = await axios.get('salesman')
this.employees = data.data
},
getStagesAmountByUser(){
console.log(this.user['id'])
var objectUser = this.employees.filter(elem => {
return elem.id === this.user['id']
})
console.log(objectUser)
},
Console
Vue data
The method getSalesman is asynchronous, meaning that getStagesAmountByUser will start executing before getSalesman finishes.
Two ways to fix the problem:
Await the getSalesman method, but you have to make the created method async as well. Change the code as follows:
async created() {
this.getStagesDefault()
await this.getSalesman()
this.getStagesAmountByUser()
}
Attach a .then to the getSalesman function, and start the next one inside the .then. Change the code as follows:
created() {
this.getStagesDefault()
this.getSalesman().then(() => this.getStagesAmountByUser())
}
getSalesman is an async method. At the time of the filter, the array being filtered is still empty.
this.getSalesman() // this runs later
this.getStagesAmountByUser() // this runs right away
Have the methods run sequentially by awaiting the async method:
await this.getSalesman()
this.getStagesAmountByUser()
You can avoid the inefficient clientside filtering if you pass the id to the backend and only select by that id.
Additionally, created only gets called once unless you destroy the component which is also inefficient, so watch when user.id changes then call your method again.
Plus don't forget you must wrap any async code in a try/catch else you will get uncaught errors when a user/salesman is not found etc, you can replace console.error then with something which tells the user the error.
{
data: () => ({
employee: {}
}),
watch: {
'user.id' (v) {
if (v) this.getEmployee()
}
},
created() {
this.getEmployee()
},
methods: {
getEmployee() {
if (typeof this.user.id === 'undefined') return
try {
const {
data
} = await axios.get(`salesman/${this.user.id}`)
this.employee = data.data
} catch (e) {
console.error(e)
}
}
}
}

How to show a variable before axios petition

I am making a get request where I save the result in a variable called name_student. How can I show this variable in other methods? or how should I declare it?
This is my code:
getStudent(){
axios.get('https://backunizoom.herokuapp.com/student/2')
.then((result) => {
console.log(result.data)
this.name_student=result.data.name
console.log(name_student)
})
console.log(name_student)
},
Shared data props
As #scar-2018 suggested in comments, you just need to declare name_student as a data prop to make the prop available across all component methods and hooks:
export default {
data() {
return {
name_student: '',
}
},
methods: {
getStudent() {
axios.get(/*...*).then((result) => {
this.name_student = result.data.name
this.updateStudentName()
})
},
updateStudentName() {
this.name_student += ' (student)'
}
}
}
Accessing async data
You commented that you're seeing undefined when you log name_student. Assuming the code in your question does not have a typo with regards to the location of the console.log() calls, the axios callback is run asynchronously, and you're logging this.name_student before axios modifies it:
axios.get(/*...*/).then((result) => {
this.name_student = result.data.name
console.log(this.name_student) // => 'foo' ✅
})
console.log(this.name_student) // => undefined ❌ (not yet set)

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

Vue JS component not reactive when retrieving data using fetch

I have a strange problem in my Vue application.
The component looks like this:
...
<template v-for="foo in foos">
<Elm v-if="foo.visible" :key="foo.label" :bar="foo" />
</template>
...
"Elm" is a value in an object, retrieved from a JSON file.
The component is reactive if I get the JSON file locally:
<script>
import datas from "datafile.json";
...
methods: {
fillFoos() {
datas.forEach(data => {
this.foos.push(data)
})
}
},
mounted: {
this.fillFoos()
}
...
</script>
But when I retrieve the file remotely using fetch, the component is no longer reactive and no longer disappears when the foo.visible value is changed :
<script>
methods: {
getDataFromApi() {
return new Promise((resolve, reject) => {
fetch(this.apiUrl)
.then(response => {
return response.json();
})
.then(jsonResponse => {
resolve(jsonResponse);
})
.catch(e => {
...
})
})
},
fillFoos() {
this.getDataFromApi()
.then(response => {
response.forEach(data => {
this.foos.push(data);
});
});
}
},
mounted: {
this.fillFoos()
}
...
</script>
In both cases the "foos" array is correctly filled, the only difference is that the v-if directive seems to be broken in the second case.
To be more precise, the display is done correctly at initialization (foo.visible is true for all the elements and they're all displayed), but in case of a later change of the foo.visible value, they don't disappear.
I can't find what's wrong...
I believe the issue you are having is that the method getDataFromApi is returning a promise, but when you consume it in fillFoos the promise is not awaited, instead you call forEach on it.
You need to use the getDataFromApi().then(x => {}) syntax to resolve the promise, or alteratively you can use async await.
You can try something like this
methods: {
async getDataFromApi() {
const response= await fetch(this.apiUrl);
return response.json();
},
async fillFoos() {
try {
await foos = this.getDataFromApi();
this.foos = foos;
} catch(error) {
//handle error.
}
}
}
Someone posted a response very close to the solution yesterday but deleted it, I don't know why.
The problem was that I stored the fetch response in a variable in the data section, before using it to fill in the "foos" table
data: function() {
return {
dataFromApi: null
}
}
By removing this variable, and thus creating it on the fly after the fetch, everything works normally.... I didn't specify that I stored the answer in this variable because I didn't think it could be related... Morality: always specify everything !

Apollo Client: Upsert mutation only modifies cache on update but not on create

I have an upsert query that gets triggered on either create or update. On update, Apollo integrates the result into the cache but on create it does not.
Here is the query:
export const UPSERT_NOTE_MUTATION = gql`
mutation upsertNote($id: ID, $body: String) {
upsertNote(id: $id, body: $body) {
id
body
}
}`
My client:
const graphqlClient = new ApolloClient({
networkInterface,
reduxRootSelector: 'apiStore',
dataIdFromObject: ({ id }) => id
});
The response from the server is identical: Both id and body are returned but Apollo isn't adding new ids into the data cache object automatically.
Is it possible to have Apollo automatically add new Objects to data without triggering a subsequent fetch?
Here is what my data store looks like:
UPDATE
According to the documentation, the function updateQueries is supposed to allow me to push a new element to my list of assets without having to trigger my origin fetch query again.
The function gets executed but whatever is returned by the function is completely ignored and the cache is not modified.
Even if I do something like this:
updateQueries: {
getUserAssets: (previousQueryResult, { mutationResult }) => {
return {};
}
}
Nothing changes.
UPDATE #2
Still can't get my assets list to update.
Inside updateQueries, here is what my previousQueryResult looks like:
updateQueries: {
getUserAssets: (previousQueryResult, { mutationResult }) => {
return {
assets: []
.concat(mutationResult.data.upsertAsset)
.concat(previousQueryResult.assets)
}
}
}
But regardless of what I return, the data store does not refresh:
For reference, here is what each asset looks like:
Have you followed the example here ?
I would write the updateQueries in the mutate like this:
updateQueries: {
getUserAssets: (previousQueryResult, { mutationResult }) => {
const newAsset = mutationResult.data.upsertAsset;
return update(prev, {
assets: {
$unshift: [newAsset],
},
});
},
}
Or with object assign instead of update from immutability-helper:
updateQueries: {
getUserAssets: (previousQueryResult, { mutationResult }) => {
const newAsset = mutationResult.data.upsertAsset;
return Object.assign({}, prev, {assets: [...previousQueryResult.assets, newAsset]});
},
}
As you state in your update, you need to use updateQueries in order to update the queries associated with this mutation. Although your question does not state what kind of query is to be updated with the result of the mutation, I assume you have something like this:
query myMadeUpQuery {
note {
id
body
}
}
which should return the list of notes currently within your system with the id and body of each of the notes. With updateQueries, your callback receives the result of the query (i.e. information about a newly inserted note) and the previous result of this query (i.e. a list of notes) and your callback has to return the new result that should be assigned to the query above.
See here for an analogous example. Essentially, without the immutability-helper that the given example uses, you could write your updateQueries callback as follows:
updateQueries: {
myMadeUpQuery: (previousQueryResult, { mutationResult }) => {
return {
note: previousQueryResult.note(mutationResult.data.upsertNode),
};
}
}

Categories