Vuex: Mutation occurs but state does not change - vuetify.js

I'm Using Nuxt + Vuetify for a eCommerce app.
There are two separate components - Header and Cart.
The Header component contains a button which on click will toggle the cart visibility.
<template>
<v-app-bar>
<v-btn #click="$store.commit('toggleCart')" icon>
<v-icon>mdi-cart</v-icon>
</v-btn>
</v-app-bar>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
name: "Header",
methods: {
...mapState(["cartOpen"])
}
};
</script>
In the Cart component, I'm using Vuetify's drawer component which will be hidden by default and shown on click of the button in header.
<template>
<v-navigation-drawer v-model="cartOpen" app clipped right>Products in Cart</v-navigation-drawer>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "Cart",
methods: {
...mapState(["cartOpen"])
}
};
</script>
This is my store index.js file
export const state = () => ({
cartOpen: false
})
export const mutations = {
toggleCart(state, cartOpen) {
return cartOpen = !cartOpen;
}
}
Now when I click on the button, a mutation happens in the develoer tools but the state is always false. Any help is appreciated.

The better way is to modify your mutation like this:
export const mutations = {
toggleCart(state) {
state.cartOpen = !state.cartOpen;
}
}
You shouldn't return anything from mutation, just set new value to state property.
As alternative:
You call your mutation without payload. You should add payload to your commit method:
<v-btn #click="$store.commit('toggleCart', cartOpen)" icon>
But you should also add cartOpen to you data which is not so good.

Related

VueJS: How do I initialise data so it shows in my template on component load

This is driving me nuts!
//ProfilePage.vue
<template>
<div>
<p>{{ this.$data.profile.desc }}</p>
<profileImage v-bind:profile="profile"></profileImage>
<profileText v-bind:profile="profile" v-on:updateData="updateDesc"></profileText>
</div>
</template>
<script>
import profileText from './ProfileText.vue';
import profileImage from './ProfileImage.vue';
export default {
name: 'profilePage',
component: {
profileText,
profileImage
},
data() {
return {
profile: {
image: '',
desc: ''
}
}
},
created() {
this.fetchProfile();
},
methods: {
async fetchProfile() {
const uri = 'http://localhost:8000/api/......get';
const response = await axios.get(uri);
.then(response => this.updateProfileData(response.data))
},
updateProfileData(data) {
this.$data.profile.image = data['image'];
this.$data.profile.desc = data['description'];
},
updateDesc(data) {
this.$data.profile.desc = data.desc;
},
}
}
</script>
<style scoped>
</style>
In the above .vue file. I execute a fetch to the back end which successfully returns the correct data from the DB. I successfully save the data returned to the data() part of the file. Next I import a component (the code for which is below) from the correct page, add it as a component and add it to the template and use v-bind to pass in profile from the data() part of this page. Now the imported/child component looks like this:
//ProfileText.vue
<template>
<div>
<form #submit="update">
<textarea v-model="description"></textarea>
<button type="submit">Submit</button>
</form>
<div>
<template>
<script>
export default{
name: "profileText",
props: ["profile"],
data() {
return {
description: this.$props.profile.desc
}
},
methods: {
update(e) {
e.preventDefault();
const newData = {
desc: this.$data.description
}
this.$emit('updateData', newData);
}
}
}
</script>
<style scoped>
</style>
I use v-model to bind the contents of "description" in data() to the contents of the textarea. I have it so when i edit the text area and click submit the function emits the data to the parent component which triggers a function that updates the parent data() with the new data from the text area of this component. This parts works perfectly.
However, the part I can't figure out is when the parent component executes the fetch and binds the response with the child component, why isn't the response showing up in the textarea when it loads.
I have done the exact same thing with another lot of components and it works fine on that lot. The only difference there is that with that lot the execute function brings back a response with an array of data and I use v-for(x in xs) and then bind the attributes of data() with the component x. That's the only difference. What am I missing in the code above to load the data sent in "profile" from the parent component with v-bind to the textarea in the child component with v-model. In data() i have it to return description: this.$props.profile.desc, but it is not initialising description with profile.desc - Going nuts here $#! I've been staring at the code for two days straight trying different things.
mounted Function
Called after the instance has been mounted, where el is replaced by
the newly created vm.$el. If the root instance is mounted to an
in-document element, vm.$el will also be in-document when mounted is
called.
Note that mounted does not guarantee that all child components have
also been mounted. If you want to wait until the entire view has been
rendered, you can use vm.$nextTick inside of mounted:
mounted: function () { console.log('component mounted'); }
This hook is not called during server-side rendering.
Source
Component Lifecycle
Few things:
Your syntax has errors in the ProfileText.vue file. Missing closing template and div tags
<template>
<div>
<form #submit="update">
<textarea v-model="description"></textarea>
<button type="submit">Submit</button>
</form>
</div>
</template>
You are mixing async/await and .then(). It should be:
async fetchProfile() {
const uri = 'http://localhost:8000/api/......get';
const response = await axios.get(uri);
this.updateProfileData(response.data)
},

create dynamic events for a vue component

I have a problem with vue routes. I am using laravel 6 with vue#2.6.10
I want to create the actions button in the header dynamically (the actions are different which depends on the component). This AppHeader component is on every component and on the current component I want to create in the header the events for the current component.
For example the component CategoryDetails I want to have two actions in the header (save and exit).
The route foe the category is this:
path: '/',
redirect: 'dashboard',
component: DashboardLayout,
children: [
{
path: '/categories',
component: Category,
name: 'category',
meta: {
requiresAuth: true
}
},
{
path: '/categories/:CategoryID',
component: CategoryDetails,
name: 'category-details',
props: true,
meta: {
requiresAuth: true
}
},
]
In the component CategoryDetails:
<template>
<div>
<app-header :actions="actions"></app-header>
// other code
</div>
</template>
<script>
import AppHeader from "../../layout/AppHeader";
export default {
name: "CategoryDetails",
components: {AppHeader},
data() {
actions: [{label: 'Save', event: 'category.save'}, {label: 'Exit', event: 'category.exit'}],
},
mounted() {
const vm = this;
Event.$on('category.save', function(){
alert('Save Category!');
});
Event.$on('category.exit', function(){
vm.$router.push({name: 'category'});
});
}
}
</script>
I crated the action object which tells the header component what events to emit and listen to them in this component.
In the AppHeader component:
<template>
<div v-if="typeof(actions) !== 'undefined'" class="col-lg-6 col-sm-5 text-right">
{{ btn.label }}
</div>
</template>
<script>
export default {
name: "AppHeader",
props: [
'actions'
],
methods: {
onActionClick(event) {
Event.$emit(event);
}
}
}
</script>
The Event is the "bus event" defined in the app.js
/**
* Global Event Listener
*/
window.Event = new Vue();
So... let`s tested :)
I am in the category component. Click on the category details ... the actions are in the header (save and exit). Click on exit...we area pushed back to the category component... click again to go in the category details and click save ... the alert appears TWICE.
Exit and enter again ... the alert "Save Category!" appears 3 times.....and so on ...
Why ?
I think the issue is not with your routes. I don't know but try testing with your event locally (not globally) in the component of interest. There may be duplicate action (CategoryDetails).
According to this post: https://forum.vuejs.org/t/component-not-destroying/25008/10
I have to destroy the "zombie effect"
destroyed() {
Event.$off('category.save');
Event.$off('category.exit');
}

Avoid fetching images everytime page load - vuejs

I'm building a page that show dynamically some photos in a feed like Instagram. I'm getting stuck trying to avoid everytime I load a page or I go into a photo's detail page and then go back, to do an API request to Laravel controller, so that means fetching data and images, losing the position of the page and starting on the top of the page.
My code:
Feed.vue
<template>
<div v-for="(image, index) in images" :key="index">
<v-img :src="image.path" class="image-masonry mini-cover" slot-scope="{ hover }"></v-img>
</div>
</template>
<script>
export default {
data() {
return {
images: []
}
},
mounted() {
this.getImagesHome();
},
methods: {
getImagesHome() {
this.axios.get('/api/images', {},
).then(response => {
this.images = response.data;
}).catch(error => {
console.log(error);
});
},
}
}
</script>
Edit:
I saw that keep-alive is primarily used to preserve component state or avoid re-rendering it. But i can't understand how to use it. I call my Feed.vue component in another Home.vue as below:
<template>
<v-app>
<Toolbar #toggle-drawer="$refs.drawer.drawer = !$refs.drawer.drawer"></Toolbar>
<Navbar ref="drawer"></Navbar>
<keep-alive>
<Feed></Feed>
</keep-alive>
</v-app>
</template>
<script>
import store from '../store';
export default {
components: {
'Toolbar' : () => import('./template/Toolbar.vue'),
'Navbar' : () => import('./template/Navbar.vue'),
'Feed' : () => import('./Feed.vue')
}
}
</script>
What i have to put more in keep-alive and what i have to change in my Feed.vue component?
mounted() should only be called once.
There seem to be multiple ways to go about this.
If you are using vue-router, then have a look at scrollBehaviour
https://router.vuejs.org/guide/advanced/scroll-behavior.html
From their documentation,
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return desired position
}
})
You can also try using keep-alive: https://v2.vuejs.org/v2/api/#keep-alive
It keeps the component in memory so it is not destroyed, you get activated and deactivated events to check when it comes into view.
But I don't think it saves scroll position, so you may want to use this in combination with scrollBehaviour

Vue Native: 'Invariant Violation'

I'm trying to get up and rolling with Vue Native, and I'm running into the same error whenever I attempt to navigate beyond the initial screen
Warning: React.createElement: type is invalid -- expected a string
(for built-in components) or a class/function (for composite
components) but got: undefined. You likely forgot to export your
component from the file it's defined in, or you might have mixed up
default and named imports.
Check the render method of ReactVueComponent.
in ReactVueComponent (at SceneView.js:17)
in SceneView (at CardStack.js:466)
in RCTView (at View.js:60)
in View (at createAnimatedComponent.js:154)
in AnimatedComponent (at Card.js:12)
When searching my dir for ReactVueComponent, it doesn't exist, nor does SceneView.js, nor does RCTCView, etc. My guess is that's because they are generated with the code compiles?
My router, index.vue is set up as follows
<template>
<root>
<app-navigation></app-navigation>
</root>
</template>
<script>
import React from "react";
import { StackNavigator, navigationService } from "vue-native-router";
import { Root } from "native-base";
import WelcomeScreen from "./screen/WelcomeScreen.vue";
import HomeScreen from "./screen/home.vue";
const AppNavigation = StackNavigator(
{
Welcome: { screen: WelcomeScreen },
Home: { screen: HomeScreen }
},
{
initialRouteName: "Welcome",
headerMode: "none"
}
);
export default {
components: { Root, AppNavigation }
};
</script>
My WelcomeScreen component(this loads correctly. The button, on push, throws the error)
<template>
<nb-content padder>
<nb-form>
<view :style="{marginTop:300}">
<nb-button block :on-press="login">
<nb-text>Login</nb-text>
</nb-button>
</view>
</nb-content>
</template>
<script>
import { Dimensions, Platform, AsyncStorage } from "react-native";
import { NavigationActions } from "vue-native-router";
export default {
props: {
navigation: {
type: Object
}
},
methods: {
login() {
this.navigation.navigate("Home");
}
}
};
</script>
The HomeScreen component, which fails to render:
<template>
<nb-container :style="{flex:1, backgroundColor: '#fff'}">
<nb-header>
<nb-body>
<nb-title>title</nb-title>
</nb-body>
</nb-header>
<nb-content>
<nb-list>
<li>thing 1</li>
<li>thing 2</li>
<li>thing 3</li>
</nb-list>
</nb-content>
</nb-container>
</template>
<script>
import React from "react";
import { Dimensions } from "react-native";
const SCREEN_WIDTH = Dimensions.get("window").width;
export default {
props: {
navigation: Object
}
};
</script>
Any tips on this would be much appreciated. Not much out there on Vue Native yet, and I've tried to follow the few examples I've seen to the best of my ability. Double and triple-checked my dependencies and they all seem to be in place.
Seems like you are using <li> tags which are not supported. If you check native base docs. The correct tag to be used within nb-list is nb-list-item. http://docs.nativebase.io/Components.html#list-def-headref

Unknown custom element: - did you register the component correctly?

I'm new to vue.js so I know this is a repeated issue but cannot sort this out.
the project works but I cannot add a new component. Nutrition component works, profile does not
My main.js
import Nutrition from './components/nutrition/Nutrition.vue'
import Profile from './components/profile/Profile.vue'
var Vue = require('vue');
var NProgress = require('nprogress');
var _ = require('lodash');
// Plugins
Vue.use(require('vuedraggable'));
// Components
Vue.component('nutrition', Nutrition);
Vue.component('profile', Profile);
// Partials
Vue.partial('payment-fields', require('./components/forms/PaymentFields.html'));
// Filters
Vue.filter('round', function(value, places) {
return _.round(value, places);
});
Vue.filter('format', require('./filters/format.js'))
// Transitions
Vue.transition('slide', {enterClass: 'slideInDown', leaveClass: 'slideOutUp', type: 'animation'})
// Send csrf token
Vue.http.options.headers['X-CSRF-TOKEN'] = Laravel.csrfToken;
// Main Vue instance
new Vue({
el: '#app',
components: {
},
events: {
progress(progress) {
if (progress === 'start') {
NProgress.start();
} else if (progress === 'done') {
NProgress.done();
} else {
NProgress.set(progress);
}
},
'flash.success': function (message) {
this.$refs.flash.showMessage(message, 'success');
},
'flash.error': function (message) {
this.$refs.flash.showMessage(message, 'error');
}
}
});
Profile.vue
<template>
<div class="reddit-list">
<h3>Profile </h3>
<ul>
</ul>
</div>
</template>
<script type="text/babel">
export default {
name: 'profile', // this is what the Warning is talking about.
components: {
},
props: {
model: Array,
}
}
</script>
profile.blade.php
#extends('layouts.app')
#section('title', 'Profile')
#section('body-class', 'profile show')
#section('content')
<script>
window.Laravel.profileData = []
</script>
<profile></profile>
#endsection
Whenever I try to go to this page I get:
[Vue warn]: Unknown custom element: <profile> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
I tried doing a local component such as
Vue.components('profile', {
template: '<div>A custom component!</div>'
});
or even I tried adding the profile into the components in vue but still no luck, can anyone point me in the right direction?
Simply clear the cache on your browser if you run into this problem. Worked pretty well for me
I didn't fixed it but it was fixed by itself it appears some kind of magic called (CACHE). i did have my gulp watch running but i powered off my computer, and then ON again and it works.

Resources