Toggle true/false using vuex modules? - javascript

i am using vuex modules and i have a module named customer.js. In that i have a state showName which is by default set to false. I set a mutation to toggle that state. and in my about.vue file i have a button that calls that mutation but everytime i try to run that mutation, it gives me an error message Property or method "showCustomerName" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
In my customer.js module
enter code here: state: {
showName:false,
},
mutations: {
showCustomerName : state => {
state.showName = !state.showName
console.log('works??!!!')
}
},
actions: {
}
})
In my about.vue file
<template>
<div>
<button #click="showCustomerName">Click Me</button>
</div>
</template>
<script>
import {mapMutations} from "vuex"
export default {
methods : {
...mapMutations ([
"customer/showCustomerName"
])
}
}
</script>
I would assume this would work, but not sure what i am doing wrong. Thank you.

Okay so figured it out, instead of ...mapMutations ([
"customer/showCustomerName"
])
change to
...mapMutations ('customer',{
showCustomerName: "showCustomerName"
})

Related

Why am I getting "$attrs is readonly" and "$listeners is readonly" errors in the child component of the external library?

Why am I getting "$attrs is readonly" and "$listeners is readonly" errors in the child component of the external library every time something is updated in the parent component inside the Nuxt application?
This is how the whole bunch of errors looks like, caused by one problem:
[Vue warn]: $attrs is readonly.
found in
---> <MyComponent>
// ...
[Vue warn]: $listeners is readonly.
found in
---> <MyComponent>
// ...
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "data"
found in
---> <MyComponent>
// ...
About the mutation of the props. I don’t do it! How can I update props from MyComponent while in the parent component? MyComponent is a library that is imported into the parent component. It is not possible to update props inside MyComponent, I only pass props to MyComponent...
Sometimes the prop mutation error occurs only once during the initial page load.
I have an external library. Its essence is to provide access to its internal component. This is how main.ts looks like:
import MainComponent from './main.vue';
export default MainComponent;
The main.vue file is a regular vue component wrapped in Vue.extend for TypeScript.
I have reduced all the code as much as possible in order to make it as easy as possible to understand the problem:
<template>
<div>test</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
data: {
type: Object,
required: true,
},
},
data(): { sourceData: Record<string, any> } {
return {
sourceData: {},
};
},
watch: {
sourceData: {
handler(newData: Record<string, any>): void {
this.$emit('update', newData);
},
deep: true,
},
},
created(): void {
this.sourceData = { ...this.data };
},
});
</script>
Problems in this piece of code:
watch: {
sourceData: {
handler(newData: Record<string, any>): void {
this.$emit('update', newData);
},
deep: true,
},
},
On the Nuxt side of the application, it looks like this:
<MyComponent
#update="updateData($event)"
:data="sourceData"
/>
export default Vue.extend({
layout: 'profile',
data (): Record<string, any> {
return {
sourceData: {
// some source data
},
updatedSourceData: {},
}
},
methods: {
updateData(newSourceData: Record<string, any>): void {
this.updatedSourceData = { ...newSourceData }
}
},
// ...
In other words, I can say that this code is the problem:
this.updatedSourceData = { ...newSourceData }
I want to put new data in a separate object to render it (for example, in pre code). But this is what causes the error.
I have been trying to solve this problem for several hours, tried everything I could find and understand. Help me please.
UPD
I rewrote the component using a different approach. Also I gave up on building the library. Now I just import the component file from the package.
This is how the component code looks now:
<template>
<div>
{{ syncedData }}
</div>
</template>
<script lang="ts">
import { Component, PropSync, Vue } from 'vue-property-decorator';
#Component
export default class MyComponent extends Vue {
#PropSync('data', { type: Object }) syncedData!: Record<string, unknown>;
}
</script>
Inside the application, I use it like this:
<MyComponent
:data.sync="data"
/>
The problem is identical, it does not change throughout all my changes. Whatever I do with this component inside the package (library) - nothing helps.
And the craziest thing:
import MyComponent from '/Users/Colibri/projects/my-lib/src/components/my_component.vue'
That is, it's not even a library already, I just import it from another directory. I’m going crazy because I don’t understand why this is happening.
If I copy this component into a Nuxt project, then the errors disappear. The problem is somewhere here, but I absolutely do not understand it.

Expected T, got Object in Vue-Prop of custom Object-Type

Description
Hello there,
I would like to share my Vue-components using bit.dev.
I got a Vue-component like this:
<template>
...
</template>
<script>
import CustomItem from "../../../../objects/CustomItem";
export default {
name: "Test",
props: {
item: {
type: CustomItem,
},
},
};
</script>
As you can see, this component requires the prop to be a specific object.
This is the CustomObject
export default class CustomItem {
constructor ({id, name}) {
this.id = id;
this.name = name;
}
// provide cool functions here
}
This works fine in my project, but not if I include this this way:
<template>
<div v-if="!$wait.is('item.loading')">
<MyComponent :item="item"/>
</div>
</template>
<script>
import MyComponent from '#bit/myproject.my-component'
import CustomItem from '#bit/myproject.custom-item';
export default {
name: 'Home',
components: {MyComponent},
data () {
return {
item: {}
};
},
beforeRouteEnter (to, _from, next) {
const promises = [
axios.get (`/api/item/1`)
];
next (vm => {
vm.$wait.start ('item.loading');
axios.all (promises)
.then (([itemRes]) => {
vm.item = new CustomItem(itemRes.data.data);
}).finally(()=>{
vm.$wait.end ('item.loading');
});
});
},
};
</script>
In this case I get this error:
[Vue warn]: Invalid prop: type check failed for prop "item". Expected T, got Object
found in
---> <MyComponent> at resources/js/components/Items/MyComponent.vue
What did I miss here?
Edit
As I can see, the component #bit/myproject.my-component which has been provided by bit.dev, provides a packed and minified version of my component. In there, the prop looks like this:
props:{item:{type:function t(e){var n=e.id,r=e.name})...
So I guess this is why this happens.
Basically it seems that in your project actually there are 2 classes CustomItem:
the one you imported relatively:
import CustomItem from "../../../../objects/CustomItem";
and the one you imported as an external package:
import CustomItem from '#bit/myproject.custom-item';
So I would try first to check if it works when you unify your imports to one form:
import CustomItem from '#bit/myproject.custom-item';
But is JS world things are not simple sometimes and even this may not help you - sometimes even referring to CustomItem in this way does not guarantee that there won't be more than one CustomItem in your production codebase. The solution I would suggest is to enforce 'duck-typing' in a custom props validator, if it's really important to check the type of the prop. You still cannot use JS instanceof as it won't work, even checking item.prototype.name === 'CustomItem' is not a good idea, as class names are changed during code minimalization, so duck-typing seems to be the only reasonable solution for you.

How do you eventBus a bus to communicate updates to a view in a Vue component?

Listen for custom events for the bus in component b. However, after dispatching events in component a, it accesses component b. the listening function of component b is executed, but msg of data function is not updated
Please don't say Vuex.
The relevant code is based on Vue CLi3
Here code:
Component A:
<template>
<div>
Component A
<button #click="sendMsg">pushB</button>
</div>
</template>
<script>
import bus from './bus'
export default {
methods: {
sendMsg() {
bus.$emit('send', 'hello Component B')
this.$router.push('/bbb')
}
}
}
</script>
component B:
<template>
<div>
<p>component B:{{ msg }}</p>
</div>
</template>
<script type="text/javascript">
import bus from './bus'
export default {
data () {
return {
msg: 'bbb'
}
},
mounted () {
bus.$on('send', data => {
console.log(data)
console.log(this)
this.msg = data
})
}
}
</script>
bus.js
import Vue from 'vue';
export default new Vue()
router:
const aaa = () => import('#/components/demo/bus/a')
const bbb = () => import('#/components/demo/bus/b')
export default new Router({
routes: [{
path: '/aaa',
component: aaa
},
{
path: '/bbb',
component: bbb
}]
})
I tried using 'watch' to observe 'msg', but it didn't work.
Can you help me?
If possible, I would like to deeply understand 'bus'
This will work only if both component A and component B are present in the page at the time you are emitting. From the code it seems that you are emitting the value from component A and then navigating to component B and expecting the value there.
What you are doing is something like kicking a ball and then running after it and then picking it only to find that the ball has disappeared. What you need is another person already present at that location who picks up the ball.
A solution in this case can be to set the value in localstorage, navigate to the other route and then read the value from localstorage.
If the value you need to pass is a simple value, you can just pass it in query string and then read from $router params in component B.
Your code will not work as expected as your are changing route after emitting event from Component A. So it can't be catch by Component B.
You can save the changed value in mixing look here for mixins or use localstorage. And you can also use query string as stated in previous answer

Wait for VueX value to load, before loading component

When a user tries to directly navigate load a component url, an http call is made in my vuex actions, which will define a value in my state once it resolves.
I don't want to load my component until the http call is resolved, and the state value is defined.
For Example, in my component
export default {
computed: {
...mapState({
// ** this value needs to load before component mounted() runs **
asyncListValues: state => state.asyncListValues
})
},
mounted () {
// ** I need asyncListValues to be defined before this runs **
this.asyncListValues.forEach((val) => {
// do stuff
});
}
}
How can I make my component wait for asyncListValues to load, before loading my component?
One way to do it is to store state values.
For example, if your store relies on single API, you would do something like this. However, for multiple APIs, it's a good idea to store each api load state individually, or using a dedicated object for each API.
There are usualy 4 states that you can have, which I prefer to have in a globally accessible module:
// enums.js
export default {
INIT: 0,
LOADING: 1,
ERROR: 2,
LOADED: 3
};
Then, you can have the variable stored in the vuex state, where the apiState is initialized with INIT. you can also initialize the array with [], but that shouldn't be necessary.
import ENUM from "#/enums";
// store.js
export default new Vuex.Store({
state: {
apiState: ENUM.INIT,
accounts: [],
// ...other state
},
mutations: {
updateAccounts (state, accounts) {
state.accounts = accounts;
state.apiState = ENUM.LOADED;
},
setApiState (state, apiState) {
state.apiState = apiState;
},
},
actions: {
loadAccounts ({commit) {
commit('setApiState', ENUM.LOADING);
someFetchInterface()
.then(data=>commit('updateAccounts', data))
.catch(err=>commit('setApiState', ENUM.ERROR))
}
}
});
Then, by adding some computed variables, you can toggle which component is shown. The benefit of using state is that you can easily identify the Error state, and show a loading animation when state is not ready.
<template>
<ChildComponent v-if="apiStateLoaded"/>
<Loader v-if="apiStateLoading"/>
<Error v-if="apiStateError"/>
</template>
<script>
import ENUM from "#/enums";
export default {
computed: {
...mapState({
apiState: state=> state.apiState
}),
apiStateLoaded() {
return this.apiState === ENUM.LOADED;
},
apiStateLoading() {
return this.apiState === ENUM.LOADING || this.apiState === ENUM.INIT;
},
apiStateError() {
return this.apiState === ENUM.ERROR;
},
})
}
</script>
aside... I use this pattern to manage my applications as a state machine. While this example utilizes vuex, it can be adapted to use in a component, using Vue.observable (vue2.6+) or ref (vue3).
Alternatively, if you just initialize your asyncListValues in the store with an empty array [], you can avoid errors that expect an array.
Since you mentioned vue-router in your question, you can use beforeRouteEnter which is made to defer the rendering of a component.
For example, if you have a route called "photo":
import Photo from "../page/Photo.vue";
new VueRouter({
mode: "history",
routes: [
{ name: "home", path: "/", component: Home },
{ name: "photo", path: "/photo", component: Photo }
]
});
You can use the beforeRouteEnter like this:
<template>
<div>
Photo rendered here
</div>
</template>
<script>
export default {
beforeRouteEnter: async function(to, from, next) {
try {
await this.$store.dispatch("longRuningHttpCall");
next();
} catch(exception) {
next(exception);
}
}
}
</script>
What it does is, waiting for the action to finish, updating your state like you want, and then the call to next() will tell the router to continue the process (rendering the component inside the <router-view></router-view>).
Tell me if you need an ES6-less example (if you do not use this syntax for example).
You can check the official documentation of beforeRouteEnter on this page, you will also discover you can also put it at the route level using beforeEnter.
One approach would be to split your component into two different components. Your new parent component could handle fetching the data and rendering the child component once the data is ready.
ParentComponent.vue
<template>
<child-component v-if="asyncListValues && asyncListValues.length" :asyncListValues="asyncListValues"/>
<div v-else>Placeholder</div>
</template>
<script>
export default {
computed: {
...mapState({
asyncListValues: state => state.asyncListValues
})
}
}
</script>
ChildComponent.vue
export default {
props: ["asyncListValues"],
mounted () {
this.asyncListValues.forEach((val) => {
// do stuff
});
}
}
Simple way for me:
...
watch: {
vuexvalue(newVal) {
if (newVal == 'XXXX')
this.loadData()
}
}
},
computed: {
...mapGetters(['vuexvalue'])
}
Building on some of the other answers, if you're using Router, you can solve the problem by only calling RouterView when the state has been loaded.
Start with #daniel's approach of setting a stateLoaded flag when the state has been loaded. I'll just keep it simple here with a two-state flag, but you can elaborate as you like:
const store = createStore({
state () {
return {
mysettings: {}, // whatever state you need
stateLoaded: false,
}
},
mutations: {
set_state (state, new_settings) {
state.settings = new_settings;
state.stateLoaded = true;
},
}
}
Then, in app.vue you'll have something like this:
<div class="content">
<RouterView/>
</div>
Change this to:
<div class="content">
<RouterView v-if="this.$store.state.stateLoaded"/>
</div>
The v-if won't even attempt to do anything with RouterView until the (reactive) stateLoaded flag goes true. Therefore, anything you're rendering with the Router won't get called, and so there won't be any undefined state variables in it when it does get loaded.
You can of course build on this with a v-else to perhaps show a "Loading..." screen or something, just in case the state loading takes longer than expected. Using #daniel's multi-state flag, you could even report if there was a problem loading the state, and offer a Retry button or something.

Vue mapState non reactive

I try to get the state from the store using the mapState function, But I can't use the generated code that returns the values into my template code ...
<template>
// Some code
<template v-if="!isLoggedIn">
// Some code
</template>
<template v-else>
// Some code
{{ currentUser.name }}
</template>
// Some code
</template>
<script>
import { mapState } from "vuex";
export default {
// Some code
computed: {
...mapState({auth : ['currentUser', 'isLoggedIn','customers']})
}
}
</script>
instead the following code work
<script>
import { mapState } from "vuex";
export default {
// Some code
computed: {
currentUser() {
return this.$store.state.auth.currentUser
},
isLoggedIn() {
return this.$store.state.auth.isLoggedIn
},
}
}
</script>
Warning message
[Vue warn]: Property or method "isLoggedIn" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
Thanks in advance
The right syntax to access non-root properties is the following (using arrow functions) :
computed: {
...mapState({
currentUser: state => state.auth.currentUser,
isLoggedIn: state => state.auth.isLoggedIn,
customers: state => state.auth.customers
})}
Check the documentation.
If you're trying to access values from a namespaced vuex module called auth, pass the name of the module as the first argument and the array of values to map as the second argument:
computed: {
...mapState('auth', ['currentUser', 'isLoggedIn','customers'])
}
You can mapState the module and then use, say this.auth.isLoggedin

Categories

Resources