How to pass multiple mutations to update Vuex store? - vue.js

If I mutate the state only once (by committing either of the two mutations shown below), there is no error and chart is updated correctly.
If I run both mutations:
commit('SET_COURSE_MATERIAL', data)
commit('SET_TOOLS_EQUIPMENT', data)
then I get Maximum call stack exceeded: RangeError.
If I comment out the code in the watch property of chart.vue, there are no errors and I can see the state with correct values in console.log
I am getting the error regarding maximum call stack only when I run "npm run dev". When I deploy it to Google Cloud, the site works as expected and I don't get any errors. I even re-checked this by editing some code and re-deploying it twice while also noticing the time in the build logs.
summary.vue
<v-card-text>
<chart
:chart-config.sync="this.$store.state.summary.courseMaterial"
/>
</v-card-text>
...
<v-card-text>
<chart
:chart-config.sync="this.$store.state.summary.toolsEquipment"
/>
</v-card-text>
chart.vue
<template>
<v-flex>
<no-ssr><vue-c3 :handler="handler"/></no-ssr>
</v-flex>
</template>
<script>
import Vue from 'vue'
export default {
name: 'chart',
props: ['chartConfig'],
data() {
return {
handler: new Vue()
}
},
watch: {
chartConfig: function(val) {
console.log('chart component > watch > chartConfig', val)
this.drawChart()
}
},
created() {
this.drawChart()
},
methods: {
drawChart() {
this.handler.$emit('init', this.chartConfig)
}
}
}
</script>
store/summary.js
import axios from 'axios'
import _ from 'underscore'
import Vue from 'vue'
import {
courseMaterialChartConfig,
toolsEquipmentChartConfig,
} from './helpers/summary.js'
axios.defaults.baseURL = process.env.BASE_URL
Object.filter = (obj, predicate) =>
Object.assign(
...Object.keys(obj)
.filter(key => predicate(obj[key]))
.map(key => ({ [key]: obj[key] }))
)
export const state = () => ({
courseMaterial: '',
toolsEquipment: '',
})
export const getters = {
courseMaterial(state) {
return state.courseMaterial
},
toolsEquipment(state) {
return state.toolsEquipment
}
}
export const actions = {
async fetchData({ state, commit, rootState, dispatch }, payload) {
axios.defaults.baseURL = process.env.BASE_URL
let { data: initialData } = await axios.post(
'summary/fetchInitialData',
payload
)
console.log('initialData', initialData)
let [counterData, pieChartData, vtvcData, guestFieldData] = initialData
//dispatch('setCourseMaterial', pieChartData.coureMaterialStatus)
//dispatch('setToolsEquipment', pieChartData.toolsEquipmentStatus)
},
setCourseMaterial({ commit }, data) {
commit('SET_COURSE_MATERIAL', courseMaterialChartConfig(data))
},
setToolsEquipment({ commit }, data) {
commit('SET_TOOLS_EQUIPMENT', toolsEquipmentChartConfig(data))
}
}
export const mutations = {
// mutations to set user in state
SET_COURSE_MATERIAL(state, courseMaterial) {
console.log('[STORE MUTATIONS] - SET_COURSEMATERIAL:', courseMaterial)
state.courseMaterial = courseMaterial
},
SET_TOOLS_EQUIPMENT(state, toolsEquipment) {
console.log('[STORE MUTATIONS] - SET_TOOLSEQUIPMENT:', toolsEquipment)
state.toolsEquipment = toolsEquipment
},
}
helpers/summary.js
export const courseMaterialChartConfig = data => {
return {
data: {
type: 'pie',
json: data,
names: {
received: 'Received',
notReceived: 'Not Received',
notReported: 'Not Reported'
}
},
title: {
text: 'Classes',
position: 'right'
},
legend: {
position: 'right'
},
size: {
height: 200
}
}
}
export const toolsEquipmentChartConfig = data => {
return {
data: {
type: 'pie',
json: data,
names: {
received: 'Received',
notReceived: 'Not Received',
notReported: 'Not Reported'
}
},
title: {
text: 'Job Role Units',
position: 'right'
},
legend: {
position: 'right'
},
size: {
height: 200
}
}
}

Deep copy the chart config.
methods: {
drawChart() {
this.handler.$emit('init', {...this.chartConfig})
}
}

Related

Problems with chart.js redrawing or not redrawing graphs

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: {},
})

How to create a queue of alert messages in Vue.js/Vuetify?

I have written some code that requires some amendments to cater for multiple alerts. It should also be able to accept a timeout duration so by default it needs to be 5000 but you should be able to override this property. Please could someone help me with this as I'm not sure how to go about it from this point.
~/store/toast-messages.js
export const state = () => ({
color: '',
message: '',
type: ''
})
export const mutations = {
showToast (state, payload) {
state.color = payload.color
state.message = payload.message
state.type = payload.type
}
}
~/plugins/toasts.js
export default ({ store }, inject) => {
inject('toasts', {
showToast ({ color = '', message = '', type = '' }) {
store.commit('toast-messages/showToast', { color, message, type })
}
})
}
nuxt.config.js
export default {
...
plugins: [
'~/plugins/toasts.js'
],
...
}
~components/toasts/Toasts.vue
<template>
<v-alert
v-model="show"
transition="slide-y-transition"
:color="color"
:type="type"
dense
prominent>
{{ message }}
</v-alert>
</template>
<script>
export default {
name: 'Toasts',
data: () => {
return {
show: false,
color: '',
message: '',
type: 'error',
}
},
watch: {
show (val) {
if (val) {
this.hideToast()
}
},
},
created () {
this.$store.subscribe((mutation, state) => {
if (mutation.type === 'toast-messages/showToast') {
this.color = state['toast-messages'].color
this.message = state['toast-messages'].message
this.type = state['toast-messages'].type
this.show = true
}
})
},
methods: {
hideToast () {
window.setInterval(() => {
this.show = false
}, 3000)
},
},
}
</script>
Called like this from anywhere in the app
this.$toasts.showToast({
color: 'red',
type: 'error',
message: err.message,
})
You can use the excellent component Vue-SNotify - example here.

Testing vue watchers with vue-testing-library

Anyone know how I would test a watcher in a component with vue-testing-library?
Here is my component. I want to test that the method is called when the brand vuex state is updated. With vue test utils it would be easy but I have not found a good way to do this with vue testing library. Has anyone did this before using vue testing library.
<template>
<v-data-table
data-testid="builds-table"
:headers="headers"
:items="builds"
:items-per-page="10"
class="elevation-1"
:loading="loading"
>
<template v-slot:[getItemStatus]="{ item }">
<v-chip :color="getStatusColor(item.status)" dark>
{{ item.status }}
</v-chip>
</template>
</v-data-table>
</template>
<script>
import { mapState } from "vuex";
import { getScheduledBuilds } from "../services/buildActivationService";
import { getStatusColor } from "../utils/getStatusColor";
export default {
name: "BuildsTable",
data() {
return {
loading: false,
headers: [
{
text: "Activation Time",
align: "start",
value: "buildActivationTime",
},
{ text: "Build ID", value: "buildId" },
{ text: "Build Label", value: "buildLabel" },
{ text: "Status", value: "status" },
],
error: "",
};
},
async mounted() {
this.getBuilds();
},
computed: {
...mapState(["brand", "builds"]),
getItemStatus() {
return `item.status`;
},
},
watch: {
brand() {
this.getBuilds();
},
},
methods: {
getStatusColor(status) {
return getStatusColor(status);
},
async getBuilds() {
try {
this.loading = true;
const builds = await getScheduledBuilds(this.$store.getters.brand);
this.$store.dispatch("setBuilds", builds);
this.items = this.$store.getters.builds;
this.loading = false;
} catch (error) {
this.loading = false;
this.error = error.message;
this.$store.dispatch("setBuilds", []);
}
},
},
};
</script>
Vue Testing Library is just a wrapper for Vue Test Utils, so the same call verification techniques apply.
Here's how to verify the call with Jest and Vue Testing Library:
Spy on the component method definition before rendering the component:
import { render } from '#testing-library/vue'
import BuildsTable from '#/components/BuildsTable.vue'
const getBuilds = jest.spyOn(BuildsTable.methods, 'getBuilds')
render(BuildsTable)
Render the component with a given store and a callback to capture the Vuex store instance under test:
let store = {
state: {
brand: '',
builds: [],
}
}
const storeCapture = (_, vuexStore) => store = vuexStore
render(BuildsTable, { store }, storeCapture)
Update the store's brand value, and wait a macro tick for the watcher to take effect, then verify the getBuilds spy is called twice (once in mounted() and again in the brand watcher):
store.state.brand = 'foo'
await new Promise(r => setTimeout(r)) // wait for effect
expect(getBuilds).toHaveBeenCalledTimes(2)
The full test would look similar to this:
import { render } from '#testing-library/vue'
import BuildsTable from '#/components/BuildsTable.vue'
describe('BuildsTable.vue', () => {
it('calls getBuilds when brand changes', async() => {
const getBuilds = jest.spyOn(BuildsTable.methods, 'getBuilds')
let store = {
state: {
brand: '',
builds: [],
}
}
const storeCapture = (_, vuexStore) => store = vuexStore
render(BuildsTable, { store }, storeCapture)
store.state.brand = 'foo'
await new Promise(r => setTimeout(r)) // wait for effect
expect(getBuilds).toHaveBeenCalledTimes(2)
})
})

how to get nested getters in vuex nuxt

i have store/index.js like this
new Vuex.Store({
modules: {
nav: {
namespaced: true,
modules: {
message: {
namespaced: true,
state: {
count: 0,
conversations: [],
},
getters: {
getCount: state => {
return state.count;
},
},
mutations: {
updateCount(state) {
state.count++;
},
},
actions: {},
},
requests: {
namespaced: true,
state: {
friends: [],
},
getters: {
getFriends: state => {
return state.friends;
},
},
mutations: {
pushFriends(state, data) {
state.friends.push(data);
},
},
actions: {
pushFriends(commit, data) {
commit('pushFriends', data);
},
},
},
},
},
},
});
i want to use getters in computed property i have tested like this
computed: {
...mapGetters({
count: 'nav/message/getCount',
}),
},
butt getting error
[vuex] unknown getter: nav/message/getCount
what is am missing here
i also want to make separate folder for every modules like my nav have 3 modules message, requests & notifications
i did try but nuxt blow up my codes
I think your index is wrong, the correct thing is to separate the modules independently, something like this:
in your store/index.js
export const state = () => ({
config: {
apiURL: 'https://meuapp.com'
}
})
export const mutations = { }
export const actions = { }
// getters
export const getters = {
test: state => payload => {
if (!payload)
return {
message: 'this is an messagem from index without payload test.', // you don't need pass any payload is only to show you how to do.
result: state.config
}
else
// return value
return {
message: 'this is an message from index test with payload.',
result: state.config, // here is your index state config value
payload: payload // here is yours params that you need to manipulate inside getter
}
}
}
here is your store/navi.js
export const state = () => ({
navi: {
options: ['aaa', 'bbb', 'ccc']
}
})
export const mutations = { }
export const actions = { }
// getters
export const getters = {
test: state => payload => {
if (!payload)
return {
message: 'this is a messagem from nav store without payload test.', // you don't need pass any payload is only to show you how to do.
result: state.navi
}
else
// return value
return {
message: 'this is an messagem from navi test with payload.',
result: state.navi, // here is your index state config value
payload: payload // here is yours params that you need to manipulate inside getter
}
}
}
then in your component you can use as a computed properties:
<template>
<div>
without a paylod from index<br>
<pre v-text="indexTest()" />
with a paylod from index<br>
<pre v-text="indexTest( {name: 'name', other: 'other'})" />
without a paylod from navi<br>
<pre v-text="naviTest()" />
with a paylod from navi<br>
<pre v-text="naviTest( {name: 'name', other: 'other'})" />
access getters from methods<br>
<pre>{{ accessGetters('index') }}</pre>
<pre v-text="accessGetters('navi')" />
<br><br>
</div>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
computed: {
...mapGetters({
indexTest: 'test',
naviTest: 'navi/test'
})
},
methods: {
accessGetters (test) {
if (test && test === 'index' ) {
console.log('test is', test) // eslint-disable-line no-console
return this.indexTest()
}
else if (test && test === 'navi') {
console.log('test is:', test) // eslint-disable-line no-console
return this.naviTest()
}
else {
return 'test is false'
}
}
}
}
</script>
Whenever possible separate your code into smaller parts, one part for each thing. This makes it easier for you to update and keep everything in order.
Hope this helps.
I came here to find a way to access the getters of a module that was nested inside another module in Vue.js and the following solution worked for me:
this.$store.getters['outerModuleName/innerModuleName/nameOfTheGetter']
Maybe this helps someone with a similar problem.

VueJS - vue-charts.js

I am trying to pass data I fetch from API to vue-chartjs as props, I am doing as in the documentation but it does not work.
Main component
<monthly-price-chart :chartdata="chartdata"/>
import MonthlyPriceChart from './charts/MonthlyPriceChart'
export default {
data(){
return {
chartdata: {
labels: [],
datasets: [
{
label: 'Total price',
data: []
}
]
},
options: {
responsive: true,
maintainAspectRatio: false
}
}
},
components: {
MonthlyPriceChart
},
created() {
axios.get('/api/stats/monthly')
.then(response => {
let rides = response.data
forEach(rides, (ride) => {
this.chartdata.labels.push(ride.month)
this.chartdata.datasets[0].data.push(ride.total_price)
})
})
.catch(error => {
console.log(error)
})
}
}
In response I have an array of obejcts, each of which looks like this:
{
month: "2018-10",
total_distance: 40,
total_price: 119.95
}
Then I want to send the data somehow to the chart so I push the months to chartdata.labels and total_price to chartdata.datasets[0].data.
chart component
import { Bar } from 'vue-chartjs'
export default {
extends: Bar,
props: {
chartdata: {
type: Array | Object,
required: false
}
},
mounted () {
console.log(this.chartdata)
this.renderChart(this.chartdata, this.options)
}
}
console.log(this.chartdata) outputs my chartsdata object from my main component and the data is there so the data is passed correctly to chart but nothing is rendered on the chart.
The documentation says this:
<script>
import LineChart from './LineChart.vue'
export default {
name: 'LineChartContainer',
components: { LineChart },
data: () => ({
loaded: false,
chartdata: null
}),
async mounted () {
this.loaded = false
try {
const { userlist } = await fetch('/api/userlist')
this.chartData = userlist
this.loaded = true
} catch (e) {
console.error(e)
}
}
}
</script>
I find this documentation a bit vague because it does not explain what I need to pass in chartdatato the chart as props. Can you help me?
Your issue is that API requests are async. So it happens that your chart will be rendered, before your API request finishes. A common pattern is to use a loading state and v-if.
There is an example in the docs: https://vue-chartjs.org/guide/#chart-with-api-data