This question already has answers here:
Returning Promises from Vuex actions
(5 answers)
Closed 2 years ago.
I would like to populate my bootstrap-vue table with data from my database. I am using vuex to attempt to achieve this; however, the bootstrap-vue table isn't getting the data for some reason. I checked Vuex dev tools:
I added console.log('Getters: ' + this.$store.getters.allEmployees); to my component to see what that retrieves and it only outputs Getters: to the console.
To further troubleshoot this, I hardcoded: [{"id":1,"name":"Jane Smith","email":"jane#smith.com","job_title":"Lead"},{"id":2,"name":"John Smith","email":"john#smith.com","job_title":"Installer"}] into the employees state and now the data gets loaded into the table.
Vuex module employees.js
const state = {
employees: [],
employeesStatus: null,
};
const getters = {
allEmployees: state => {
return state.employees;
},
};
const actions = {
fetchAllEmployees({commit, state}) {
commit('SET_EMPLOYEES_STATUS', 'loading');
axios.get('/api/employees')
.then(res => {
commit('SET_EMPLOYEES', res.data);
//console.log(state.employees);
commit('SET_EMPLOYEES_STATUS', 'success');
})
.catch(error => {
commit('SET_EMPLOYEES_STATUS', 'error');
});
},
};
const mutations = {
SET_EMPLOYEES(state, employees) {
state.employees = employees;
},
SET_EMPLOYEES_STATUS(state, status) {
state.employeesStatus = status;
}
};
export default {
state, getters, actions, mutations,
};
VueJS component EmployeeDataTable.vue:
<template>
<div class="overflow-auto pb-3" style="background: white; ">
<b-card
header="Employees"
header-tag="header"
>
<b-pagination
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
aria-controls="my-table"
></b-pagination>
<p class="mt-3">Current Page: {{ currentPage }}</p>
<b-table
id="employee-table"
ref="employee-table"
:items="items"
:per-page="perPage"
:current-page="currentPage"
small
></b-table>
</b-card>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: "EmployeeDataTable",
data() {
return {
perPage: 3,
currentPage: 1,
items: [],
}
},
computed: {
...mapGetters(['allEmployees']),
rows() {
return this.items.length
}
},
methods: {
getEmployees() {
this.$store.dispatch('fetchAllEmployees').then(() => {
this.items = this.$store.getters.allEmployees;
console.log('Getters: ' + this.$store.getters.allEmployees); //<-Returns Getters:
});
//this.items = this.$store.getters.allEmployees;
}
},
mounted() {
this.getEmployees();
}
}
</script>
Phil's explanation did the trick. The snippets I changed are:
const actions = {
fetchAllEmployees({commit, state}) {
commit('SET_EMPLOYEES_STATUS', 'loading');
return axios.get('/api/employees')
.then(res => {
commit('SET_EMPLOYEES', res.data);
//console.log(state.employees);
commit('SET_EMPLOYEES_STATUS', 'success');
})
.catch(error => {
commit('SET_EMPLOYEES_STATUS', 'error');
});
},
};
and
<b-table
id="employee-table"
ref="employee-table"
:items="allEmployees"
:per-page="perPage"
:current-page="currentPage"
small
></b-table>
Related
I am Japanese. Therefore, my sentences may be strange. Please keep that in mind.
I am writing code using vue.js, vuex, vue-chart.js and vue-chart.js to display the population of each prefecture of Japan when checked.I’m code is written to redraw the graph when the input element for each prefecture is checked.However, it does not redraw when checked.Also, it may redraw after half of the check.I believe this phenomenon can be confirmed from the following URL.
https://yumemi-coding.web.app/
※There are no errors.
Here's a question: what causes the graphs to redraw or not? Also, how can I code to remedy this?
What I have done to counteract the cause is as follows
I went to the official website and used the rendering process as a reference.
URL:https://vue-chartjs.org/migration-guides/#new-reactivity-system
=> The way we did it was right.
We thought there was a problem with VueX and coded in a way that did not use it. => There was nothing wrong with vuex.
TopFroont.vue
<template>
<div class="Bar_area">
<Bar :options="chartOptions" :data="chartData" class="Bar_item" />
</div>
</template>
<script>
import { Bar } from "vue-chartjs"
import { Chart as ChartJS, registerables } from "chart.js"
ChartJS.register(...registerables)
export default {
name: "BarChart",
components: { Bar },
data() {
return {
chartOptions: {
responsive: true,
},
}
},
computed: {
chartData() {
return {
labels: this.$store.state.years,
datasets: this.$store.state.prefectures,
}
},
},
}
</script>
NaviBar.vue
<template>
<div class="navApp">
<ul>
<li v-for="(pref, index) in prefData" :key="index" class="pref_itemBox">
<label>
<input type="checkbox" #change="checkItem(pref)" />
<span class="pref_text">{{ pref.prefName }}</span>
</label>
</li>
</ul>
</div>
</template>
<script>
import resasInfo from "#/library/resas.js"
import axios from "axios"
export default {
data() {
return {
resasInfo: resasInfo,
url: resasInfo.url_prefectures,
api: resasInfo.api,
prefData: [],
prefectures: [],
}
},
async created() {
const request_Header = {
headers: { "X-API-KEY": this.api.key },
}
await axios.get(this.url, request_Header).then((res) => {
const value = res.data.result
this.prefData.push(...value)
})
},
methods: {
checkItem(pref) {
// チェックされてる都道府県のみを配列に入れる
const isExistencePref = this.prefectures.indexOf(pref)
isExistencePref === -1
? this.prefectures.push(pref)
: this.prefectures.splice(isExistencePref, 1)
this.$store.dispatch("getPrefectures", this.prefectures)
},
},
}
</script>
vuex => store/index.js
import axios from "axios"
import { createStore } from "vuex"
import createPersistedState from "vuex-persistedstate"
export default createStore({
state: {
prefectures: [],
years: [],
},
mutations: {
getPrefs(state, payload) {
state.prefectures = payload
},
getYears(state, payload) {
state.years = payload
},
},
actions: {
getPrefectures({ commit }, payload) {
// payload => 各都道府県のprefCode + prefName
const allPrefecture_Data = []
const result = payload.map(async (el) => {
const prefCode_data = el.prefCode
axios
.get(
`https://opendata.resas-portal.go.jp/api/v1/population/composition/perYear?prefCode=${prefCode_data}&cityCode=-`,
{
headers: {
"X-API-KEY": "5RDiLdZKag8c3NXpEMb1FcPQEIY3GVwgQwbLqFIx",
},
}
)
.then((res) => {
const value = res.data.result.data[0].data
const TotalPopulation_Year = []
const TotalPopulation_Data = []
// 都道府県の総人口データと年データを各配列に入れ込む
value.forEach((element) => {
TotalPopulation_Data.push(element.value)
TotalPopulation_Year.push(element.year)
})
// rgbaを自動生成する関数 => backgroundColor
const generateRGBA = () => {
const r = Math.floor(Math.random() * 256)
const g = Math.floor(Math.random() * 256)
const b = Math.floor(Math.random() * 256)
const a = 0.8
return `rgba(${r}, ${g}, ${b}, ${a})`
}
// chart.jsに入れ込むデータ
const prefData = {
label: el.prefName,
data: TotalPopulation_Data,
backgroundColor: generateRGBA(),
}
allPrefecture_Data.push(prefData)
commit("getPrefs", allPrefecture_Data)
commit("getYears", TotalPopulation_Year)
})
.catch((err) => {
console.log(err)
})
})
return result
},
},
plugins: [createPersistedState()],
getters: {},
modules: {},
})
I have a snackbar from Vuetify. It's in default.vue and the vuex store controls the v-model, message and color:
DefaultSnackBar.vue
<template>
<v-container>
<v-snackbar
v-model="snackbarProperties.show"
:color="snackbarProperties.color"
timeout="7000"
multi-line
>
{{ snackbarProperties.message }}
<template v-slot:action="{ attrs }">
<v-btn
text
v-bind="attrs"
#click="hideSnackbar"
>
Close
</v-btn>
</template>
</v-snackbar>
</v-container>
</template>
<script>
import { mapActions } from "vuex";
import { mapGetters } from "vuex";
export default {
methods :{
...mapActions("Snackbar",["showSnackbar","hideSnackbar"]),
},
computed: {
...mapGetters("Snackbar",["snackbarProperties"])
},
}
</script>
Snackbar.js
export const state = () => ({
message: "",
color: "",
show: false,
});
export const getters = {
snackbarProperties: state => {
return state;
},
}
export const mutations = {
showSnackbar: (state, payload) => {
state.message = payload.message;
state.color = payload.color;
state.show = true;
},
hideSnackbar: (state) => {
state.message = "";
state.color = ""
state.show = false;
},
}
export const actions = {
showSnackbar({ commit }, payload) {
commit('showSnackbar', payload)
},
hideSnackbar({ commit }) {
commit('hideSnackbar')
}
}
When I call showSnackbar({...}) the bar appears correctly with no errors, but when it disappears (timeout is reached) is get this error and everything crashes
do not mutate vuex store state outside mutation handlers
I think it's because when the bar disappears the component changes the value of the v-model it's attached to but I'm not sure how to work around this.
I found the answer from this vue forum:
Use an action with the setTimeout code in it. Then in the timeout
commit the mutation. Mutations should be synchronous which is why
using a timeout in them is throwing a warning.
I've updated Snackbar.js to suit:
export const state = () => ({
message: "",
color: "",
show: false,
});
export const getters = {
snackbarProperties: state => {
return state;
},
}
export const mutations = {
showSnackbar: (state, payload) => {
state.message = payload.message;
state.color = payload.color;
state.show = true;
},
hideSnackbar: (state) => {
state.message = "";
state.color = ""
state.show = false;
},
}
export const actions = {
showSnackbar({ commit }, payload) {
commit('showSnackbar', payload)
setTimeout(() => {
commit('hideSnackbar')
}, 500);
},
hideSnackbar({ commit }) {
commit('hideSnackbar')
}
}
try this if you need showing multiple
<template>
<div class="text-center">
<v-snackbar
v-for="(snackbar, index) in snackbars.snackbars.filter(
(s) => s.isVisible
)"
:key="snackbar.text + Math.random()"
v-model="snackbar.isVisible"
:color="snackbar.color"
:timeout="-1"
:right="true"
:top="true"
:style="`top: ${index * 60}px`"
>
<v-row no-gutters>
<v-col md="11" sm="11">
{{ snackbar.text }}
</v-col>
<v-col md="1" sm="1">
<v-btn class="mx-2" icon small #click="hideNotify(index)">
<v-icon color="error"> mdi-close </v-icon>
</v-btn>
</v-col>
</v-row>
</v-snackbar>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState({ snackbars: 'notification' }),
},
methods: {
hideNotify(index) {
this.$store.dispatch('notification/HIDE_NOTIFY_WITH_INDEX',index)
},
},
}
</script>
add this into the vuex as notification.js
export const state = () => ({
snackbars: [],
})
export const mutations = {
SET_SNACKBAR(state, snackbar) {
state.snackbars = state.snackbars.concat(snackbar)
},
HIDE_NOTIFY_WITH_INDEX(state,index) {
if (index in state.snackbars) {
state.snackbars.splice(index, 1)
}
},
HIDE_NOTIFY(state) {
state.snackbars = []
},
}
export const actions = {
SET_SNACKBAR({ commit }, snackbar) {
snackbar.isVisible = true
snackbar.color = snackbar.color || 'dark'
commit('SET_SNACKBAR', snackbar)
setTimeout(() => {
commit('HIDE_NOTIFY')
}, 6000)
},
HIDE_NOTIFY_WITH_INDEX({ commit },index) {
commit('HIDE_NOTIFY_WITH_INDEX',index)
},
}
I try to use nuxtServerInit method.
index.js
import productsService from "../services/productsService";
export const state = () => ({
hotDeals: [],
specialities: []
})
export const mutations = {
SET_SPECIALITIES(state, payload) {
state.specialities = payload;
}
}
export const actions = {
async nuxtServerInit({ dispatch}, ctx) {
try {
await dispatch('fetchSpecialities');
}catch (e) {
console.log(e);
}
},
fetchSpecialities({ commit }) {
productsService.getSpecialities()
.then(response => {
commit('SET_SPECIALITIES', response.data);
});
}
}
component usage
<template>
<v-layout
justify-center
align-center
>
<div>
<v-row >
<span v-for="item in specialities">{{item.productCode}}</span>
</v-row>
</div>
</v-layout>
</template>
<script>
import { mapState } from 'vuex';
export default {
computed: {
...mapState(["specialities"])
}
}
</script>
But it show nonthing on page. If I try to use console.log(state.specialities) in mutation after change state I can see data in web storm console. But in component data is not showing.
i think using watchers will solve your problem
watch: {
specialities(newValue, oldValue) {
console.log(`Updating from ${oldValue} to ${newValue}`);
},
},
I'm using VueX with Nuxt.JS so let's suppose the following code in the file store/search.js:
export const state = () => ({
results: null
});
export const mutations = {
setResults(state, { results }) {
state.results = results;
}
};
export const actions = {
startSearch({ commit, dispatch }, { type, filters }) {
commit("setResults", { type, filters });
}
};
export const getters = {
results: state => state.results
};
Now in my component results.vue, under the computed property I have something like this:
<template>
<button #click="handleSearch">Search</button>
<div v-if="results && results.length" class="results" >
<div v-for="item in results" :key="item.id">
{{item}}
</div>
</div>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
data() {
return {
selected_type: null,
filters: null
};
},
methods: {
setType(type) {
this.selected_type = type;
this.handleSearch();
},
setFilters(filters) {
this.filters = filters;
},
handleSearch() {
this.startSearch({ type: this.selected_type, filters: this.filters });
},
...mapActions("search", {
startSearch: "startSearch"
})
},
computed: {
...mapGetters("search", {
results: "results"
})
}
</script>
My question is: why the item in the for loop (in the template section) always return undefined ?
Thank you very much for your answers.
So far, I found it:
in computed should be an array, not an object so:
...mapGetters("search", [
"results"
]
// Now results is populated.
Here is how CourseDescriptionPage.vue looks
import CourseCover from './CourseDescription/CourseCover.vue'
import WhyJoin from './CourseDescription/WhyJoin.vue'
import CourseStructure from './CourseDescription/CourseStructure.vue'
export default {
props: ['id'],
data () {
return {
hasDetails: false
}
},
created () {
this.$store.dispatch('loadCourseDetails', this.id).then(() => {
this.hasDetails = true
})
},
computed: {
course () {
return this.$store.state.courseDetails[this.id]
}
},
components: {
CourseCover,
WhyJoin,
CourseStructure
},
name: 'CourseDescriptionPage'
}
<template>
<div v-if="hasDetails">
<course-cover :courseTitle="course.title" :courseDuration="course.duration"></course-cover>
<why-join :courseTitle="course.title" :courseJobs="course.jobs"></why-join>
<course-structure :lectureList="course.lectureList"></course-structure>
</div>
</template>
Here is how my store looks
import Vuex from 'vuex'
import * as firebase from 'firebase'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
courseDetails: {},
loading: false
},
mutations: {
setCourseDetails (state, payload) {
const { id, data } = payload
state.courseDetails[id] = data
},
setLoading (state, payload) {
state.loading = payload
}
},
actions: {
loadCourseDetails ({commit}, payload) {
commit('setLoading', true)
firebase.database().ref(`/courseStructure/${payload}`).once('value')
.then((data) => {
commit('setCourseDetails', {
id: payload,
data: data.val()
})
commit('setLoading', false)
})
.catch(
(error) => {
console.log(error)
commit('setLoading', false)
}
)
}
}
Here is how my CourseCover.vue looks
export default {
props: {
courseTitle: {
type: String,
required: true
},
courseDuration: {
type: String,
required: true
}
},
name: 'CourseCover'
}
<template>
<v-jumbotron
src="./../../../static/img/course_cover_background.png">
<v-container fill-height>
<v-layout align-center>
<v-flex>
<h3>{{ courseTitle }}</h3>
<span>{{ courseDuration }}</span>
<v-divider class="my-3"></v-divider>
<v-btn large color="primary" class="mx-0" #click="">Enroll</v-btn>
</v-flex>
</v-layout>
</v-container>
</v-jumbotron>
</template>
I think there is something wrong with the way I am using props here but I couldn't figure out.
The data is loaded in store by the firebase that I know for sure because it shows in Vue dev tools but I just couldn't understand why Vue is complaining about that.
Thanks in advance.
course is undefined on component initialize ,so then you should return an empty object:
computed: {
course () {
return this.$store.state.courseDetails[this.id] || {}
}
},