Okay, I have two different components and each of those get Axios response. But I don't want to fetch data in each component separate. That's is not right, and it cause components run separate...
Updated 3
I did some changes on the code, but still having some problems. I am doing axios call with Vuex in Store.js and import it into my component. it's like below.
This is my store.js component;
import Vue from "vue";
import Vuex from "vuex";
var actions = _buildActions();
var modules = {};
var mutations = _buildMutations();
const state = {
storedData: []
};
Vue.use(Vuex);
const getters = {
storedData: function(state) {
return state.storedData;
}
};
function _buildActions() {
return {
fetchData({ commit }) {
axios
.get("/ajax")
.then(response => {
commit("SET_DATA", response.data);
})
.catch(error => {
commit("SET_ERROR", error);
});
}
};
}
function _buildMutations() {
return {
SET_DATA(state, payload) {
console.log("payload", payload);
const postData = payload.filter(post => post.userId == 1);
state.storedData = postData;
}
};
}
export default new Vuex.Store({
actions: actions,
modules: modules,
mutations: mutations,
state: state,
getters
});
Now importing it into Average component.
import store from './Store.js';
export default {
name:'average',
data(){
return{
avg:"",
storedData: [],
}
},
mounted () {
console.log(this.$store)
this.$store.dispatch('fetchDatas')
this.storedData = this.$store.dispatch('fetchData')
},
methods: {
avgArray: function (region) {
const sum = arr => arr.reduce((a,c) => (a += c),0);
const avg = arr => sum(arr) / arr.length;
return avg(region);
},
},
computed: {
mapGetters(["storedData"])
groupedPricesByRegion () {
return this.storedData.reduce((acc, obj) => {
var key = obj.region;
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(obj.m2_price);
return acc;
}, {});
},
averagesByRegion () {
let arr = [];
Object.entries(this.groupedPricesByRegion)
.forEach(([key, value]) => {
arr.push({ [key]: Math.round(this.avgArray(value)) });
});
return arr;
},
}
}
I can see the data stored in the console. But there are errors too. I can't properly pass the data in myComponent
https://i.stack.imgur.com/J6mlV.png
if you don't want use vuex to distrib data maybe you can try eventBus, when you get data form the axios respose #emit the event and in another component #on this event
The issue is that
To resolve the error you're getting, Below are the steps.
Import your store file inside the file where your Vue Instance is initialized.
// Assuming your store file is at the same level
import store from './store';
Inside your, Add store object inside your Vue Instance
function initApp(appNode) {
new Vue({
el: appNode,
router: Router,
store // ES6 sytax
});
}
There you go, you can now access your store from any component.
UPDATE: For Second Error
Instead of changing data inside your component, change it inside mutation in the store because you do not want to write the same login in other components where the same method is used.
Hence,
computed: {
...mapGetters(["storedData"]),
anotherFunction() {
// Function implementation.
}
}
Inside your mutation set the data.
SET_DATA(state, payload) {
console.log("payload", payload);
state.storedData = payload;
}
Inside getters, you can perform what you were performing inside your computed properties.
storedData: function(state) {
const postData = state.storedData.filter(post => post.userId == 1);
return postData;
}
Vuex Official docs
Here is the working codesandbox
Hope this helps!
Related
I use #nuxtjs/composition-api(0.15.1), but I faced some problems about accessing Vuex getters in computed().
This is my code in composition API:
import { computed, useContext, useFetch, reactive } from '#nuxtjs/composition-api';
setup() {
const { store } = useContext();
const products = computed(() => {
return store.getters['products/pageProducts'];
});
const pagination = computed(() => {
return store.getters['products/pagination'];
});
useFetch(() => {
if (!process.server) {
store.dispatch('products/getPage');
}
});
return {
products,
pagination,
};
}
And the console keeps reporting the warning:
[Vue warn]: Write operation failed: computed value is readonly.
found in
---> <Pages/products/Cat.vue> at pages/products/_cat.vue
<Nuxt>
<Layouts/default.vue> at layouts/default.vue
<Root>
I'm really confused. Because I didn't try to mutate the computed property, just fetching the Data with the AJAX and then simply assign the data to the state in the Vuex mutations.
But I rewrite the code in option API in this way:
export default {
components: {
ProductCard,
Pagination,
},
async fetch() {
if (process.server) {
await this.$store.dispatch('products/getPage');
}
},
computed: {
products() {
return this.$store.getters['products/pageProducts'];
},
pagination() {
return this.$store.getters['products/pagination'];
},
},
};
Everything works fine, there's no any errors or warnings. Is it the way I'm wrongly accessing the getters in the composition API or that's just a bug with the #nuxtjs/composition-api plugin?
fix: computed property hydration doesn't work with useFetch #207
This problem might not can be solved until the Nuxt3 come out.
But I found an alternative solution which use the middleware() instead of use useFetch(), if you want to the prevent this bug by fetching AJAX data with Vuex Actions and then retrieve it by Getters via the computed().
I make another clearer example which it's the same context like the question above.
~/pages/index.vue :
<script>
import { computed, onMounted, useContext, useFetch } from '#nuxtjs/composition-api';
export default {
async middleware({ store }) {
await store.dispatch('getUser');
},
setup() {
const { store } = useContext();
const user = computed(() => store.getters.user);
return {
user,
};
},
}
</script>
~/store/index.js (Vuex)
const state = () => ({
user: {},
});
const actions = {
async getUser({ commit }) {
const { data } = await this.$axios.get('https://randomuser.me/api/');
console.log(data.results[0]);
commit('SET_USER', data.results[0]);
},
};
const mutations = {
SET_USER(state, user) {
state.user = user;
},
};
const getters = {
user(state) {
return state.user;
},
};
If there's something wrong in my answer, please feel free to give your comments.
I was working with Vuex and had a problem that I could solve on my own. The problem was that action I created doesn't return data inside state with method created inside my Vue component. This problem got solved by simply adding return before new Promise.
So problem solved but I don't really understand the difference that made the problem get solved by using return. What does the difference having return makes?
This is my created function which before using return with actions didn't load data on initial loading
created () {
this.$store.dispatch('updateNews')
.then( response => {
this.news = this.$store.getters.getNews
})
.catch( error => this.error = "Error happened during fetching news" );
},
This is my store after adding return
import Vue from "vue";
import Vuex from "vuex";
import axios from 'axios';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
news: []
},
getters:{
getNews(state){
return state.news
}
},
mutations: {
UPDATE_NEWS(state, payload){
state.news = payload
}
},
actions: {
updateNews(context){
var url = 'https://newsapi.org/v2/top-headlines?' +
'country=us&' +
'apiKey=something';
return new Promise ( (res, rej) => {
axios
.get(url)
.then(response => {
context.commit('UPDATE_NEWS', response.data)
res()
})
.catch( error => rej() )
})
}
},
});
a promise doesn't work as a simple declaration inside a function, you have actually return the promise to work with it. The case here is a little bit weird tho, becuase axios already returns a promise to work with. I think the problem is that you want to assing the value of a variable in the state of the store programatically to a variable in component data, when the correct flow for something like that would be accessing that value with a computed property, like this:
Vuex
import Vue from "vue";
import Vuex from "vuex";
import axios from 'axios';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
news: []
},
getters:{
getNews(state){
return state.news
}
},
mutations: {
UPDATE_NEWS(state, payload){
state.news = payload
}
},
actions: {
updateNews(context){
var url = 'https://newsapi.org/v2/top-headlines?' +
'country=us&' +
'apiKey=something';
axios
.get(url)
.then(response => {
context.commit('UPDATE_NEWS', response.data)
})
.catch( error => console.log('Oops, something went wrong with the news', error) );
}
},
});
Component
created () {
this.$store.dispatch('updateNews');
},
computed: {
news() {
return this.$store.getters.getNews;
}
}
Using it like this, you don't have to make a variable called news inside component's data, just the computed property, and access it the same way you would access a variable returned in component's data
Can anyone see why this wouldn't work please,
Trying to use vuex store to manage my axios requests and transfer to a component as follows:
In my vuex store module I have the following
import axios from "axios";
export const state = () => ({
cases: [],
})
export const mutations = {
listCases (state, cases) {
state.cases = cases;
},
}
export const actions = {
loadCases ({ commit, context }) {
return axios.get('http')
.then(res => {
const convertCases = []
for (const key in res.data) {
convertCases.push({ ...res.data[key], id: key })
}
commit('listCases', convertCases)
})
.catch(e => context.error(e));
},
export const getters = {
// return the state
cases(state) {
return state.cases
}
}
I checked amd my axios request is returning my results as expected and passing to the mutation
In my component I have
import { mapMutations, mapGetters, mapActions } from 'vuex'
export default {
created () {
this.$store.dispatch('cases/loadCases');
},
computed: {
...mapGetters ({
cases: 'cases/cases'
})
},
</script>
Now i assumed based on what I've learnt that i could call with
and this would return my items.
but i get an error cases is not defined,
Anyone abe to tell me my error please
Many Thanks
Take a look here: https://v2.vuejs.org/v2/guide/list.html#Array-Change-Detection
You may be able to make it reactive this way:
export const mutations = {
listCases (state, cases) {
state.cases = [];
cases.forEach((c) => {
state.cases.push(c);
});
},
}
My component vue like this :
<template>
...
</template>
<script>
import {mapActions, mapGetters} from 'vuex'
export default {
...
methods: {
add(event) {
this.addProduct(this.filters)
console.log(this.getStatusAddProduct)
if(this.getStatusAddProduct) {
...
}
},
...mapActions(['addProduct'])
},
computed: {
...mapGetters(['getStatusAddProduct'])
}
}
</script>
This code : this.addProduct(this.filters), it will call addProduct method n the modules vuex
My modules vuex like this :
import { set } from 'vue'
import product from '../../api/product'
import * as types from '../mutation-types'
// initial state
const state = {
statusAddProduct: null
}
// getters
const getters = {
getStatusAddProduct: state => state.statusAddProduct
}
// actions
const actions = {
addProduct ({ dispatch, commit, state }, payload)
{
product.add(payload,
data => {
commit(types.ADD_PRODUCT_SUCCESS)
},
errors => {
commit(types.ADD_PRODUCT_FAILURE)
}
}
}
}
// mutations
const mutations = {
[types.ADD_PRODUCT_SUCCESS] (state){
state.statusAddProduct = true
},
[types.ADD_PRODUCT_FAILURE] (state){
state.statusAddProduct = false
}
}
export default {
state,
getters,
actions,
mutations
}
This code : product.add(payload, in the modules vuex, it will call api
The api like this :
import Vue from 'vue'
export default {
add (filter, cb, ecb = null ) {
axios.post('/member/product/store', filter)
.then(response => cb(response))
.catch(error => ecb(error))
}
}
My problem here is if add method in vue component run, the result of console.log(this.getStatusAddProduct) is null. Should if product success added, the result of console.log(this.getStatusAddProduct) is true
I think this happens because at the time of run console.log(this.getStatusAddProduct), the process of add product in vuex modules not yet finished. So the result is null
How can I make console.log(this.getStatusAddProduct) run when the process in the vuex module has been completed?
You have to pass the property down all the way from the .add() method.
You do that by returning it in the intermediary methods and, lastly, using .then().
The api:
add (filter, cb, ecb = null ) {
return axios.post('/member/product/store', filter) // added return
.then(response => cb(response))
.catch(error => ecb(error))
}
And, the action:
addProduct ({ dispatch, commit, state }, payload) // added return
{
return product.add(payload,
data => {
commit(types.ADD_PRODUCT_SUCCESS)
},
errors => {
commit(types.ADD_PRODUCT_FAILURE)
}
}
}
Finally:
methods: {
add(event) {
this.addProduct(this.filters).then(() => { // added then
console.log(this.getStatusAddProduct) // moved inside then
if(this.getStatusAddProduct) {
...
}
})
},
I'm just starting to learn Vuex here. Until now I've been storing shared data in a store.js file and importing store in every module but this is getting annoying and I'm worried about mutating state.
What I'm struggling with is how to import data from firebase using Vuex. From what I understand only actions can make async calls but only mutations can update the state?
Right now I'm making calls to firebase from my mutations object and it seems to be working fine. Honestly, all the context, commit, dispatch, etc. seems a bit overload. I'd like to just be able to use the minimal amount of Vuex necessary to be productive.
In the docs it looks like I can write some code that updates the state in the mutations object like below, import it into my component in the computed property and then just trigger a state update using store.commit('increment'). This seems like the minimum amount necessary to use Vuex but then where do actions come in? Confused :( Any help on the best way to do this or best practices would be appreciated!
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
My code is below
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const db = firebase.database();
const auth = firebase.auth();
const store = new Vuex.Store({
state: {
userInfo: {},
users: {},
resources: [],
postKey: ''
},
mutations: {
// Get data from a firebase path & put in state object
getResources: function (state) {
var resourcesRef = db.ref('resources');
resourcesRef.on('value', snapshot => {
state.resources.push(snapshot.val());
})
},
getUsers: function (state) {
var usersRef = db.ref('users');
usersRef.on('value', snapshot => {
state.users = snapshot.val();
})
},
toggleSignIn: function (state) {
if (!auth.currentUser) {
console.log("Signing in...");
var provider = new firebase.auth.GoogleAuthProvider();
auth.signInWithPopup(provider).then( result => {
// This gives you a Google Access Token. You can use it to access the Google API.
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// Set a user
var uid = user.uid;
db.ref('users/' + user.uid).set({
name: user.displayName,
email: user.email,
profilePicture : user.photoURL,
});
state.userInfo = user;
// ...
}).catch( error => {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// ...
});
} else {
console.log('Signing out...');
auth.signOut();
}
}
}
})
export default store
main.js
import Vue from 'vue'
import App from './App'
import store from './store'
new Vue({
el: '#app',
store, // Inject store into all child components
template: '<App/>',
components: { App }
})
App.vue
<template>
<div id="app">
<button v-on:click="toggleSignIn">Click me</button>
</div>
</template>
<script>
import Hello from './components/Hello'
export default {
name: 'app',
components: {
Hello
},
created: function () {
this.$store.commit('getResources'); // Trigger state change
this.$store.commit('getUsers'); // Trigger state change
},
computed: {
state () {
return this.$store.state // Get Vuex state into my component
}
},
methods: {
toggleSignIn () {
this.$store.commit('toggleSignIn'); // Trigger state change
}
}
}
</script>
<style>
</style>
All AJAX should be going into actions instead of mutations. So the process would start by calling your action
...which commits data from the ajax callback to a mutation
...which is responsible for updating the vuex state.
Reference: http://vuex.vuejs.org/en/actions.html
Here is an example:
// vuex store
state: {
savedData: null
},
mutations: {
updateSavedData (state, data) {
state.savedData = data
}
},
actions: {
fetchData ({ commit }) {
this.$http({
url: 'some-endpoint',
method: 'GET'
}).then(function (response) {
commit('updateSavedData', response.data)
}, function () {
console.log('error')
})
}
}
Then to call your ajax, you will have to call the action now by doing this:
store.dispatch('fetchData')
In your case, just replace this.$http({...}).then(...) with your firebase ajax and call your action in the callback.