Hi I am using Vuex in Nuxt.
If I comment h2 and click on click button, lifecycle hook updated() does not run even when the store variable counter changes in it.
If I uncomment the h2 line updated() lifecycle hook runs on every click.
How can I get the updated changes from store counter in component without using it in template.
<template>
<div>
<!-- <h2>{{ counter }}</h2> -->
<button #click="onSubmit">click</button>
</div>
</template>
<script>
import { createNamespacedHelpers } from 'vuex'
const { mapState } = createNamespacedHelpers('profile')
export default {
name: 'App',
methods: {
onSubmit() {
console.log('clicked')
this.$store.commit('profile/increment', 1)
},
},
computed: {
...mapState(['counter']),
},
created() {
console.log('created', this.$store.state.profile.counter)
},
updated() {
console.log('updated', this.$store.state.profile.counter)
console.log('updated', this.counter)
},
}
</script>
Store
export const state = () => ({
counter: 0,
})
export const mutations = {
increment(state) {
state.counter++
},
}
updated() hook is called when vue decides to rerender the component. So if your template doesn't use the counter value, there is no need for rerender and that's why updated() is not called.
If you want to run some code whenever the counter is updated, you need a watcher:
watch: {
'$store.state.profile.counter'(val) {
console.log(`The counter was updated. New value is ${val}`)
}
}
Related
I am trying to make use of mapState and running into issues with reactive data. I have the following inside my Test.vue component
<template>
<div> {{ name }} </div>
</template>
computed: {
...mapState('user', ['age','name]
}
when my state user.name updates outside of the Test.vue component, the new value is not showing inside Test.vue.
so for example, if I have an update via a mutation in my userStore,
[SET_USER_NAME_MUTATION](state, value) {
state.name = value;
},
commit('SET_USER_NAME_MUTATION', "John")
now in my Vuex store when I check chrome DevTools , user { name: "John" } , which is correct
You should mutate state through vuex actions instead of directly calling the mutation.
Try with something like this, assuming your state contains a user object with name property:
Vue component
<template>
<div>
<span>{{ name }}</span>
<button #click="changeName">Change name</button>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'MyComponent',
computed: {
...mapState({
name: state => state.user.name
})
},
methods: {
changeName () {
this.$store.dispatch('changeName', 'John Smith')
}
}
}
</script>
Vuex store
// state
const state = {
user: {
name: null
}
}
// getters
const getters = {
// ...
}
// actions
const actions = {
changeName ({ commit }, payload) {
commit('setName', payload)
}
}
// mutations
const mutations = {
setName (state, payload) {
state.user.name = payload
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
Anyway, it will be very helpful to know your state structure to a better approach as per your specific case
I'm trying to render a d3 graph using stored data in vuex. but I'm not getting data in renderGraph() function.
how to get data in renderGraph()?
Following is store methods.
store/index.js
import Vue from "vue";
import Vuex from "vuex";
import * as d3 from "d3";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
subscribers: []
},
getters: {
getterSubscribers: state => {
return state.subscribers;
}
},
mutations: {
mutationSubscribersData: (state, payload) => {
state.subscribers = payload;
}
},
actions: {
actionSubscribersData: async ({ commit }) => {
let subsData = await d3.json("./data/subscribers.json"); // some json fetching
commit("mutationSubscribersData", subsData);
}
}
});
Below is parent component
Home.vue
<template>
<div>
<MyGraph /> // child component rendering
</div>
</template>
<script>
import MyGraph from "./MyGraph.vue";
export default {
components: {
MyGraph
},
};
</script>
Below is child component.
MyGraph.vue
<template>
<div>
<svg width="500" height="400" />
</div>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
export default {
computed: {
...mapGetters(["getterSubscribers"])
},
methods: {
...mapActions(["actionSubscribersData"]),
renderGraph(data) {
console.log(data); // DATA NOT COMING HERE
// MyGraph TO BE RENDERED WITH THIS DATA
}
},
mounted() {
this.actionSubscribersData();
this.renderGraph(this.getterSubscribers);
}
};
</script>
I have tried mounted, created lifecycle hooks. but did not find data coming.
There is race condition. actionSubscribersData is async and returns a promise. It should be waited for until data becomes available:
async mounted() {
await this.actionSubscribersData();
this.renderGraph(this.getterSubscribers);
}
There must be delay for the actionSubscribersData action to set value to store. Better you make the action async or watch the getter. Watching the getter value can be done as follows
watch:{
getterSubscribers:{ // watch the data to set
deep:true,
handler:function(value){
if(value){ // once the data is set trigger the function
this.renderGraph(value);
}
}
}
}
I have a component which is hid based on the route which is active, it kicks off a function which is stored using vuex store.
It works as intended, the sidenav is hidden on login, logout, and register.
However, I noticed when I am on an authenticated page such as admin panel, or dashboard, etc, the component displays correctly, but when/if someone reloads the webpage, the component disappears, only to be displayed when clicking a link to another page.
App.Vue
<template>
<div id="app">
<navbar />
<sidenav v-show="sidenav_toggle" />
<div class="row router-container">
<div class="col router-row">
<router-view/>
</div>
</div>
</div>
</template>
<script>
import Vue from 'vue'
import Vuex from 'vuex'
import router from '#/router'
import axios from 'axios'
import AxiosStorage from 'axios-storage'
let sessionCache = AxiosStorage.getCache('localStorage');
import materializecss from '../static/css/main.css'
import materializejs from '../static/materialize-css/dist/js/materialize.js'
import navbar from '#/components/navbar'
import sidenav from '#/components/sidenav'
Vue.use(Vuex)
const state = {
sidenav:{
show: false
}
}
const mutations = {
show_sidenav(state){
state.sidenav.show = true
},
hide_sidenav(state){
state.sidenav.show = false
}
}
const store = new Vuex.Store({
state,
mutations
})
export default {
router,
name: 'App',
watch:{
$route: function(){
if(this.$route.path === '/login' || this.$route.path === '/logout' || this.$route.path === '/register'){
store.commit('hide_sidenav')
console.log('not authd')
}else{
store.commit('show_sidenav')
console.log('authd')
}
},
deep: true,
immediate: true
},
computed: {
sidenav_toggle(){
return store.state.sidenav.show
}
},
data: function(){
return{
}
},
components: {
navbar,
sidenav
},
methods: {
},
created: function(){
}
}
</script>
Your watcher is not called if you land directly on the admin page because the $route property never changes (and watchers only watch for changes).
What you could do is move your watcher function in a method, and call this method in the created hook and in your watcher.
An even better way to do this would be to use vue-router navigation-guards
Example:
export default {
// ...
methods: {
adaptSidebar(path) {
if (['/login', '/logout', '/register'].includes(path)) {
store.commit('hide_sidenav')
} else {
store.commit('show_sidenav')
}
},
},
beforeRouterEnter(from, to, next) {
// As stated in the doc, we do not have access to this from here
next(vm => {
vm.adaptSidebar(to.path)
})
},
beforeRouteChange(from, to, next) {
this.adaptSidebar(to.path)
},
}
I'm having difficulty finding why an event bus emits only once between two Vue components.
A button interaction is to open a child components' panel and then dynamically/lazyly loads a pair of chart components. Then, on the second execution, close the panel and destroy the dynamically loaded components. The functions cycle works but only once.
UPDATE:
After adding some UI components inside of the <q-card> directive, I discovered that this.toggleChartPanel() toggles on every interaction with the button event. It appears that the the props loads only on the FIRST-TIME the button is clicked.
A code example with explanation would be greatly appreciated.
Component containing the emit:
<script>
import Store from '#store'
import BaseHrsBtn from './_base-hrs-btn'
export default {
name: 'TotHrs',
parent: 'LogSummaryWidget',
components: {
BaseHrsBtn,
},
data() {
return {
dynamicCharts: {
dynamicChartA: 'test-line-chart',
dynamicChartB: 'test-line-chart'
}
}
},
computed: {
totHrs () {
return Store.state.fetchLogSummary.data.total
},
},
methods: {
emitChartPanelToggle () {
this.$bus.$emit('chart-panel-toggled', this.dynamicCharts)
this.dynamicCharts = {}
},
},
}
</script>
<template>
<base-hrs-btn
class="col-6 col-md-4"
:hours="totHrs"
icon="clock"
title="TOT"
#click.native="emitChartPanelToggle"
/>
</template>
<script>
export default {
name : 'ChartPanel',
parent: 'LogSummaryWidget',
components: {
TestLineChart: () => import("./_charts/test-line-chart"),
},
data () {
return {
chartPanelOpen: false,
dynamicChartA: '',
dynamicChartB: '',
}
},
created() {
this.$bus.$on('chart-panel-toggled', ({ dynamicChartA, dynamicChartB}) => {
this.toggleChartPanel()
this.dynamicChartA = dynamicChartA
this.dynamicChartB = dynamicChartB
});
},
beforeDestroy() {
this.$bus.$off('chart-panel-toggled');
},
methods: {
toggleChartPanel () {
this.chartPanelOpen = !this.chartPanelOpen
}
},
}
</script>
<template>
<q-card
v-show-slide="chartPanelOpen"
class="q-mx-md"
>
<component :is="dynamicChartA"></component>
<component :is="dynamicChartB"></component>
</q-card>
</template>
Also, how can I insure that the $bus.$on fires asynchronously too?
I accidentally left this in:
this.dynamicCharts = {}
Removing it fixed it.
I have a component (modal) which relies on a store. The store has the state of the modal component - whether it is active or not.
I need to be able to call this modal to open from other components or even just on a standard link. It opens by adding an .active class.
How can I change the state of the store - either by calling the stores action or calling the modal components method (which is mapped to the store).
Modal Store:
class ModalModule {
constructor() {
return {
namespaced: true,
state: {
active: false,
},
mutations: {
toggleActive: (state) => {
return state.active = ! state.active;
},
},
actions: {
toggleActive({ commit }) {
commit('toggleActive');
},
},
getters: {
active: state => {
return state.active;
}
}
};
}
}
export default ModalModule;
Vue Component:
<template>
<div class="modal" v-bind:class="{ active: active }">
<div class="modal-inner">
<h1>modal content here</h1>
</div>
<div class="modal-close" v-on:click="this.toggleActive">
X
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
computed: {
...mapGetters('Modal', [
'active',
])
},
methods: {
...mapActions('Modal', [
'toggleActive',
]),
}
}
</script>
And somewhere else I want to be able to have something like:
<button v-on:click="how to change the state??">OPEN MODAL</button>
Edit:
Here's the store:
import Vuex from 'vuex';
import ModalModule from './ModalModule';
class Store extends Vuex.Store {
constructor() {
Vue.use(Vuex);
super({
modules: {
Modal: new ModalModule(),
}
});
};
}
You do not need an action for your particular usecase . You just just define a mutation as you are just changing the boolean value of a property in a state. Actions are for async functionality. You usecase is just synchronous change of Boolean value
So you can do
<button v-on:click="$store.commit('toggleActive')">OPEN MODAL</button>
EDIT:
Just export a plain object
const ModalModule = {
namespaced: true,
state: {
active: false,
},
mutations: {
toggleActive: (state) => {
return state.active = ! state.active;
},
},
actions: {
toggleActive({ commit }) {
commit('toggleActive');
},
},
getters: {
active: state => {
return state.active;
}
}
}
export default ModalModule;// export the module
Even remove the class based definition of the store
import Vue from 'vue'
import Vuex from 'vuex';
import ModalModule from './ModalModule';
Vue.use(Vuex);
export const store = new Vuex.Store({
modules: {
ModalModule
}
});
And change it like this in you component for mapping of the mutation (<MODULE_NAME>/<MUTATION_NAME>)
...mapMutations([
'ModalModule/toggleActive'
])
You can access the store from your components via this.$store. There you can call your actions and mutations. So
<button v-on:click="$store.commit('your mutation name', true)">OPEN MODAL</button>