vuex with 2 components not updating - vuejs2

can someone help me with the lifecycle of this?
I have 2 vue components 1. has a button (Header.vue)
and 2. is sidebar that I want to hide/show depends on value
header looks like this
<template>
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a role="button" class="navbar-burger is-pulled-left" aria-label="menu" aria-expanded="false"
#click='getToggleSidebarMobile'>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
<a class="navbar-item " href="/">
<img :src="'/../images/logo.png'">
</a>
</div>
<div class="navbar-end is-pulled-right">
<div class="navbar-item">
</div>
</div>
</nav>
</template>
<script>
import {store} from '../store';
export default {
data() {
return {
hideSidebarMobile: store.state.hideSidebarMobile
}
},
methods: {
getToggleSidebarMobile(){
this.hideSidebarMobile = !this.hideSidebarMobile;
store.dispatch('getToggleSidebarMobile', this.hideSidebarMobile);
}
}
}
</script>
sidebar is bigger but trimmed version is this:
<template>
<aside class="menu " v-bind:class="{'is-hidden-touch' : store.state.hideSidebarMobile}" >
....
</aside>
</tamplate>
....
data() {
return {
sidebar: {
hideSidebarMobile: store.state.hideSidebarMobile
},
}
},
methods: {
getToggleSidebarMobile(){
store.dispatch('getToggleSidebarMobile');
}
...
update: store.js added
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
hideSidebarMobile: true
},
actions: {
getToggleSidebarMobile(context, payload){
console.log("action "+payload)
context.commit('getToggleSidebarMobile', payload)
}
},
mutations: {
getToggleSidebarMobile(state, data){
state.hideSidebarMobile = data;
console.log("Mutation "+data);
}
},
getters: {
getToggleSidebarMobile(state){
return state.hideSidebarMobile;
}
},
I also tried to use in template v-bind:class="{'is-hidden-touch' : sidebar.hideSidebarMobile}", but no luck in there as well, as I can see values are updated but I'm unable to add/remove that class where did I go wrong?
updated... forgot to upload store

The store.state.hideSidebarMobile reference in the sidebar's template is not going to work. (Unless you've set a store property on the sidebar's Vue instance equal to the Vuex store, which I'm assuming you haven't.)
If you've registered the Vuex module properly:
const store = new Vuex.Store({ ... }); // your store config
Vue.use(Vuex); // registering the plugin
new Vue({ // your root Vue instance
el: '#app',
store: store, // passing the `store` so components can reference this.$store
...
});
then you should be able to reference the store in your sidebar component via this.$store. Which also means that there is no need to import store into every file that needs to reference it.
So in your template:
v-bind:class="{'is-hidden-touch' : $store.state.hideSidebarMobile}"

Related

Async loading child component doesn't trigger v-if

Hi everyone and sorry for the title, I'm not really sure of how to describe my problem. If you have a better title feel free to edit !
A little bit of context
I'm working on a little personal project to help me learn headless & micro-services. So I have an API made with Node.js & Express that works pretty well. I then have my front project which is a simple one-page vue app that use vuex store.
On my single page I have several components and I want to add on each of them a possibility that when you're logged in as an Administrator you can click on every component to edit them.
I made it works well on static elements :
For example, here the plus button is shown as expected.
However, just bellow this one I have some components, that are loaded once the data are received. And in those components, I also have those buttons, but they're not shown. However, there's no data in this one except the title but that part is working very well, already tested and in production. It's just the "admin buttons" part that is not working as I expect it to be :
Sometimes when I edit some codes and the webpack watcher deal with my changes I have the result that appears :
And that's what I expect once the data are loaded.
There is something that I don't understand here and so I can't deal with the problem. Maybe a watch is missing or something ?
So and the code ?
First of all, we have a mixin for "Auth" that isn't implemented yet so for now it's just this :
Auth.js
export default {
computed: {
IsAdmin() {
return true;
}
},
}
Then we have a first component :
LCSkills.js
<template>
<div class="skills-container">
<h2 v-if="skills">{{ $t('skills') }}</h2>
<LCAdmin v-if="IsAdmin" :addModal="$refs.addModal" />
<LCModal ref="addModal"></LCModal>
<div class="skills" v-if="skills">
<LCSkillCategory
v-for="category in skills"
:key="category"
:category="category"
/>
</div>
</div>
</template>
<script>
import LCSkillCategory from './LCSkillCategory.vue';
import { mapState } from 'vuex';
import LCAdmin from '../LCAdmin.vue';
import LCModal from '../LCModal.vue';
import Auth from '../../mixins/Auth';
export default {
name: 'LCSkills',
components: {
LCSkillCategory,
LCAdmin,
LCModal,
},
computed: mapState({
skills: (state) => state.career.skills,
}),
mixins: [Auth],
};
</script>
<style scoped>
...
</style>
This component load each skills category with the LCSkillCategory component when the data is present in the store.
LCSkillCategory.js
<template>
<div class="skillsCategory">
<h2 v-if="category">{{ name }}</h2>
<LCAdmin
v-if="IsAdmin && category"
:editModal="$refs.editModal"
:deleteModal="$refs.deleteModal"
/>
<LCModal ref="editModal"></LCModal>
<LCModal ref="deleteModal"></LCModal>
<div v-if="category">
<LCSkill
v-for="skill in category.skills"
:key="skill"
:skill="skill"
/>
</div>
<LCAdmin v-if="IsAdmin" :addModal="$refs.addSkillModal" />
<LCModal ref="addSkillModal"></LCModal>
</div>
</template>
<script>
import LCSkill from './LCSkill.vue';
import { mapState } from 'vuex';
import LCAdmin from '../LCAdmin.vue';
import LCModal from '../LCModal.vue';
import Auth from '../../mixins/Auth';
export default {
name: 'LCSkillCategory',
components: { LCSkill, LCAdmin, LCModal },
props: ['category'],
mixins: [Auth],
computed: mapState({
name: function() {
return this.$store.getters['locale/getLocalizedValue']({
src: this.category,
attribute: 'name',
});
},
}),
};
</script>
<style scoped>
...
</style>
And so each category load a LCSkill component for each skill of this category.
<template>
<div class="skill-item">
<img :src="img(skill.icon.hash, 30, 30)" />
<p>{{ name }}</p>
<LCAdmin
v-if="IsAdmin"
:editModal="$refs.editModal"
:deleteModal="$refs.deleteModal"
/>
<LCModal ref="editModal"></LCModal>
<LCModal ref="deleteModal"></LCModal>
</div>
</template>
<script>
import LCImageRendering from '../../mixins/LCImageRendering';
import { mapState } from 'vuex';
import Auth from '../../mixins/Auth';
import LCAdmin from '../LCAdmin.vue';
import LCModal from '../LCModal.vue';
export default {
name: 'LCSkill',
mixins: [LCImageRendering, Auth],
props: ['skill'],
components: { LCAdmin, LCModal },
computed: mapState({
name: function() {
return this.$store.getters['locale/getLocalizedValue']({
src: this.skill,
attribute: 'name',
});
},
}),
};
</script>
<style scoped>
...
</style>
Then, the component with the button that is added everywhere :
LCAdmin.js
<template>
<div class="lc-admin">
<button v-if="addModal" #click="addModal.openModal()">
<i class="fas fa-plus"></i>
</button>
<button v-if="editModal" #click="editModal.openModal()">
<i class="fas fa-edit"></i>
</button>
<button v-if="deleteModal" #click="deleteModal.openModal()">
<i class="fas fa-trash"></i>
</button>
</div>
</template>
<script>
export default {
name: 'LCAdmin',
props: ['addModal', 'editModal', 'deleteModal'],
};
</script>
Again and I'm sorry it's not that I haven't look for a solution by myself, it's just that I don't know what to lookup for... And I'm also sorry for the very long post...
By the way, if you have some advice about how it is done and how I can improve it, feel free, Really. That how I can learn to do better !
EDIT :: ADDED The Store Code
Store Career Module
import { getCareer, getSkills } from '../../services/CareerService';
const state = () => {
// eslint-disable-next-line no-unused-labels
careerPath: [];
// eslint-disable-next-line no-unused-labels
skills: [];
};
const actions = {
async getCareerPath ({commit}) {
getCareer().then(response => {
commit('setCareerPath', response);
}).catch(err => console.log(err));
},
async getSkills ({commit}) {
getSkills().then(response => {
commit('setSkills', response);
}).catch(err => console.log(err));
}
};
const mutations = {
async setCareerPath(state, careerPath) {
state.careerPath = careerPath;
},
async setSkills(state, skills) {
state.skills = skills;
}
}
export default {
namespaced: true,
state,
actions,
mutations
}
Career Service
export async function getCareer() {
const response = await fetch('/api/career');
return await response.json();
}
export async function getSkills() {
const response = await fetch('/api/career/skill');
return await response.json();
}
Then App.vue, created() :
created() {
this.$store.dispatch('config/getConfigurations');
this.$store.dispatch('certs/getCerts');
this.$store.dispatch('career/getSkills');
this.$store.dispatch('projects/getProjects');
},
Clues
It seems that if I remove the v-if on the buttons of the LCAdmin, the button are shown as expected except that they all show even when I don't want them to. (If no modal are associated)
Which give me this result :
Problem is that refs are not reactive
$refs are only populated after the component has been rendered, and they are not reactive. It is only meant as an escape hatch for direct child manipulation - you should avoid accessing $refs from within templates or computed properties.
See simple demo below...
const vm = new Vue({
el: "#app",
components: {
MyComponent: {
props: ['modalRef'],
template: `
<div>
Hi!
<button v-if="modalRef">Click!</button>
</div>`
}
},
data() {
return {
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-component :modal-ref="$refs.modal"></my-component>
<div ref="modal">I'm modal placeholder</div>
</div>
The solution is to not pass $ref as prop at all. Pass simple true/false (which button to display). And on click event, $emit the event to the parent and pass the name of the ref as string...

Vuex- Page is not updating after commit changes made to state

I am trying to make a simple project which has 2 Vue pages add.vue and read.vue. If we add a message in add.vue(using commit to mutate teh state), results should be displayed in read.vue , using Vuex store. (I am using Nuxt)
store/index.js file=>
export const state = () => ({
messages:[]
})
export const mutations={
addMessage:(state, comment)=> {
state.messages.push(comment)
}
}
add.vue file
<template>
<div class="addContainer">
<b-form #submit.prevent="handleSubmit">
<b-form-group id="input-group-2" label="Comment:" label-for="input-2">
<b-form-input
id="input-2"
v-model="comment"
required
placeholder="Enter Your Comment"
></b-form-input>
</b-form-group>
<b-button type="submit" variant="primary">Submit</b-button>
</b-form>
</div>
</template>
<script>
import Header from '~/components/Header'
import {mapActions} from 'vuex'
import {mapState} from 'vuex'
export default {
data() {
return {
comment:''
}
},
computed:{
...mapState([
'messages'
])
},
methods:{
handleSubmit(){
this.$store.commit('addMessage', this.comment)
console.log('Messages is '+this.messages )
this.comment = ''
}
},
components:{
Header
}
}
</script>
read.vue
<template>
<div>
<p>Read Messages</p>
<ul>
<li v-for="(msg, index) in messages" :key="index">
<b>{{ msg}}</b>
<br>
{{ messages}}
</li>
</ul>
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
data() {
return {}
},
computed:{
...mapState({
messages:state=>state.messages
})
}
}
</script>
In Vue component , I can see messages array inside state changes after adding a message , but same changes are not reflected in read page.
Add getters to your store:
export const getters= {
getMessages: state => state.messages
}
and then use the getters in your component instead of accessing the state directly:
// read.vue
<script>
import {mapGetters} from 'vuex'
export default {
data() {
return {}
},
computed:{
...mapGetters({
messages: 'getMessages',
})
}
}
</script>

Why won't vue.js update the DOM?

Working my way learning about Vue. I chose it as the better alternative after looking at React, Angular and Svelte.
I have a simple example that its not working probably because I'm not getting/understanding the reactive behaviour of Vue.
Plain simple App:
<template>
<div id="app">
<app-header></app-header>
<router-view />
<app-footer></app-footer>
</div>
</template>
<script>
import Header from './components/Header.vue'
import Home from './components/Home.vue'
import Footer from './components/Footer.vue'
export default {
components: {
name: 'App',
'app-header': Header,
'app-footer': Footer
}
}
</script>
Where Home.vue and Footer.vue have plain HTML content on the template.
On Header.vue I have:
<template>
<div>
<h1>The Header</h1>
<nav>
<ul>
<li>Curr Player: {{ ethaccount }}</li>
<li>Prop owner: {{ propOwner }}</li>
</ul>
</nav>
<hr />
</div>
</template>
<script>
export default {
data() {
return {
ethaccount: 'N/A',
propOwner: 'N/A'
}
},
methods: {
update() {
var ethaccount = '0xAAAAAA123456789123456789123456789'
console.log('ETH Account: ' + ethaccount)
var propOwner = '0xPPPPPPPPPPP987654321987654321'
console.log('Prop Account: ' + propOwner)
}
},
mounted() {
this.update()
}
}
</script>
But I'm unable to get the header updated and unable to find what I'm doing wrong. Help.
If you need to read a little bit more about the reactivity of the datas in vuejs check this link : https://v2.vuejs.org/v2/guide/reactivity.html
If you need to access/change your data try to do it like that :
this.$data.ethaccount = 'foo';
this.$data.propOwner = 'bar';
For me the problem is taht you re-declare your variable locally by doing :
var ethaccount = "0xAA...";
By doing such you never change the value of the data you're accessing through your template.
Hope it will solve your problem.

Vuex: Child component wait for parent component dispatch action

Parent component (Dashboard):
<template>
<div id="dashboard">
<Header />
<b-container>
<div>
<b-row>
<b-col>
<Overview />
</b-col>
</b-row>
</div>
</b-container>
</div>
</template>
<script>
import Header from '#/components/common/Header';
import Overview from '#/components/dashboard/Overview';
import { mapGetters } from 'vuex';
export default {
name: 'dashboard',
components: {
Header,
Overview
},
mounted() {
const sessionId = this.$cookie.get('connect.sid');
this.$store.dispatch('user/getUser', sessionId).then((userData) => {
this.$store.dispatch('project/getProject', userData.data.user);
});
},
computed: {
...mapGetters('user', {
user: 'getUser'
})
}
}
</script>
Child component (Overview):
<template>
<div class="overview">
<div class="overview__title">
<h1>
Welcome {{user.cn[0]}} // Works
</h1>
</div>
<div class="overview__project">
<p v-for="project in runningprojects" :key="project._id">
{{project.name}} // Does not work at refresh
</p>
</div>
</div>
</template>
<script>
import {mapGetters} from 'vuex';
export default {
name: 'dashboard-overview',
data() {
return {
runningprojects: []
}
},
computed: {
...mapGetters('user', {
user: 'getUser'
}),
...mapGetters('project', {
projects: 'getProjects',
allProjects: 'getAllProjects'
})
},
mounted() {
console.log("mounted this.projects", this.projects);
// add something from this.projects to this.runningprojects
},
methods: {
calcReq() {
...
},
...
}
</script>
In my Dashboard component (parent) I fetch the user data with a vuex action dispatch('user/getUser) and after that I fetch the projects of this user dispatch('project/getProject).
In my Overview component (child) I want to show the project information of this user. I call my mapGetters and I have a component variable runningprojects inside data(). In my mounted() lifecycle I want to push data from my getters to this data array.
The following problem is given:
When I refresh my application, the console.log from my child component mounted() is called before the dispatch jobs are finished in the parent component (dashboard).
It only works if change something in my local files and vue-cli does a live reload.
Because of the page lifecycle of the vue app. when component renders mounted is called after created and it wont wait for the ajax or any async calls.
One solution would be to not render the child component until the async return
<template>
<div id="dashboard">
<Header />
<b-container>
<div>
<b-row>
<b-col>
<Overview v-if="finished"/>
</b-col>
</b-row>
</div>
</b-container>
</div>
</template>
<script>
import Header from '#/components/common/Header';
import Overview from '#/components/dashboard/Overview';
import { mapGetters } from 'vuex';
export default {
name: 'dashboard',
data() {
return {
finished: false,
}
},
components: {
Header,
Overview
},
mounted() {
const sessionId = this.$cookie.get('connect.sid');
this.$store.dispatch('user/getUser', sessionId).then((userData) => {
this.$store.dispatch('project/getProject', userData.data.user);
this.finished = true;
});
},
computed: {
...mapGetters('user', {
user: 'getUser'
})
}
}
</script>
Just add a v-if in the child component and when dispatch has return then set the value to true which will render the child component and the then the mounted will have the values you want
Other solution would be.
Use updated function instead of mounted and which will be called when ever there is a change in the state.

Share an object with another Vue component

I know this has been asked several times before, but as a Vue.js beginner I had trouble interpreting some of the other discussions and applying them to my situation. Using this CodeSandbox example, how would one pass the indicated object from "Hello" to "Goodbye" when the corresponding button is pressed? I'm unsure if I should be trying to use props, a global event bus, a plugin, vuex, or simply some sort of global variable.
Edit:
Here is the code for App.vue, Hello.vue and Goodbye.vue (from the previously linked CodeSandbox example).
App.vue
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "app"
};
</script>
Hello.vue:
<template>
<div class="hello">
<h1>This is Hello</h1>
<div v-for="(obj, index) in objects" :key="index">
<router-link class="button" :to="{ path: '/goodbye'}">Share obj[{{ index }}] with Goodbye</router-link>
</div>
</div>
</template>
<script>
export default {
name: "hello",
data() {
return {
objects: [0, 1, 2, 3]
};
}
};
</script>
Goodbye.vue:
<template>
<div class="goodbye">
<h1>This is Goodbye</h1>
<p>Obj = "???"</p>
<router-link class="button" :to="{ path: '/hello'}">Hello</router-link>
</div>
</template>
<script>
export default {
name: "goodbye"
};
</script>
Props are used to share data with child components. Since the components never exist at the same time, this is not useful for you. Similarly, events are not very useful to you here. You can send an event on a global bus, but since the other component does not exist yet, it cannot listen for the event.
I am not sure what you would want to do with a plugin in this case. You should never use a global variable, unless you have a very good reason to (e.g. you use Google Analytics, which happens to use a global variable, or you want to expose something within Vue in development mode for debugging purposes). In your case, you likely want to change some global app state, which is exactly what Vuex was made for. Call a Vuex mutator or action either when clicking, or in a router hook such as router.beforeEach to save the information in a structured manner so you can then retrieve it with a mapped getter. Keep in mind that you want to structure your vuex store, so don't use a state variable thingsIWantToShareWithGoodbye, but instead split it up in previousPage, lastClickOffset and numberOfClicks.
For example:
// store/index.js
import Vuex from "vuex";
import Vue from "vue";
Vue.use(Vuex);
const state = {
button: null
};
const getters = {
button(state) {
return state.button;
}
};
const mutations = {
setButton(state, payload) {
state.button = payload;
}
};
export default new Vuex.Store({
state,
getters,
mutations
});
// Hello.vue
<template>
<div class="hello">
<h1>This is Hello</h1>
<div v-for="(obj, index) in objects" :key="index">
<router-link #click.native="setButtonState(obj)" class="button" :to="{ path: '/goodbye'}">Share obj[{{ index }}] with Goodbye</router-link>
</div>
</div>
</template>
<script>
export default {
name: "hello",
data() {
return {
objects: [0, 1, 2, 3]
};
},
methods: {
setButtonState (obj) {
this.$store.commit('setButton', obj)
}
}
};
</script>
// Goodbye.vue
<template>
<div class="goodbye">
<h1>This is Goodbye</h1>
<p>Obj = {{ button }}</p>
<router-link class="button" :to="{ path: '/hello'}">Hello</router-link>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: "goodbye",
computed: {
...mapGetters({
button: 'button'
})
}
};
</script>