Pass prop as module name when mapping to namespaced module - javascript

I'm trying to pass the store module namespace via props to a component. When I try and map to getters with the prop, it throws this error,
Uncaught TypeError: Cannot convert undefined or null to object
If I pass the name as a string it works.
This Works
<script>
export default {
props: ['store'],
computed: {
...mapGetters('someString', [
'filters'
])
}
}
</script>
This does not work
this.store is defined
this.store typeof is a String
<script>
export default {
props: ['store'],
computed: {
...mapGetters(this.store, [
'filters'
])
}
}
</script>

I used this style utilising beforeCreate to access the variables you want, I used the props passed into the component instance:
import { createNamespacedHelpers } from "vuex";
import module from '#/store/modules/mymod';
export default {
name: "someComponent",
props: ['namespace'],
beforeCreate() {
let namespace = this.$options.propsData.namespace;
const { mapActions, mapState } = createNamespacedHelpers(namespace);
// register your module first
this.$store.registerModule(namespace, module);
// now that createNamespacedHelpers can use props we can now use neater mapping
this.$options.computed = {
...mapState({
name: state => state.name,
description: state => state.description
}),
// because we use spread operator above we can still add component specifics
aFunctionComputed(){ return this.name + "functions";},
anArrowComputed: () => `${this.name}arrows`,
};
// set up your method bindings via the $options variable
this.$options.methods = {
...mapActions(["initialiseModuleData"])
};
},
created() {
// call your actions passing your payloads in the first param if you need
this.initialiseModuleData({ id: 123, name: "Tom" });
}
}
I personally use a helper function in the module I'm importing to get a namespace, so if I hadmy module storing projects and passed a projectId of 123 to my component/page using router and/or props it would look like this:
import { createNamespacedHelpers } from "vuex";
import projectModule from '#/store/project.module';
export default{
props['projectId'], // eg. 123
...
beforeCreate() {
// dynamic namespace built using whatever module you want:
let namespace = projectModule.buildNamespace(this.$options.propsData.projectId); // 'project:123'
// ... everything else as above with no need to drop namespaces everywhere
this.$options.computed = {
...mapState({
name: state => state.name,
description: state => state.description
})
}
}
}
Hope you find this useful.

I tackled this problem for hours, too. Then I finally came up with one idea.
Add attachStore function in a child vue component. A function nama is not important. Any name is ok except vue reserved word.
export default {
:
attachStore (namespace) {
Object.assign(this.computed, mapGetters(namespace, ['filters']))
}
}
When this vue component is imported, call attachStore with namespace parameter. Then use it at parent components attributes.
import Child from './path/to/child'
Child.attachStore('someStoresName')
export default {
name: 'parent',
components: { Child }
:
}

The error you're encountering is being thrown during Vue/Vuex's initialization process, this.store cannot be converted because it doesn't exist yet. I haven't had to work with namespacing yet, and this is untested so I don't know if it will work, but you may be able to solve this problem by having an intermediary like this:
<script>
export default {
props: ['store'],
data {
namespace: (this.store !== undefined) ? this.store : 'null',
},
computed: {
...mapGetters(this.namespace, [
'filters'
])
}
}
</script>
That ternary expression will return a string if this.store is undefined, if it isn't undefined then it will return the value in this.store.

Note that there is also a discussion about this on Vue's Github page here: https://github.com/vuejs/vuex/issues/863
Until Vue formally supports it, I replaced something like
...mapState({
foo: state => state.foo
})
with
foo () {
return this.$store.state[this.namespace + '/foo'] || 0
}
Where namespace is passed to my child component using a prop:
props: {
namespace: { type: String, required: true }
}

Related

Vue use computed property in data

I have the following const properties in my Vue data.
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
data() {
const properties = ['Property 1', 'Property 2']
return {
properties,
form: propertyNames.map(name => ({ property_type: [] })),
}
},
I however am computing the properties in a computed property as follows
computed: {
properties() {
const properties = this.property_form.property_name;
const filtered = properties.filter(p => p != null)
return filtered;
}
},
so I want to use the computed property in the data as follows:
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
data() {
const properties = this.properties
return {
properties,
form: propertyNames.map(name => ({ property_type: [] })),
}
},
but when I do this, I get the error Property or method "properties" is not defined on the instance.
data() is invoked before computed props are resolved, so you can't reference computed props from there.
I assume this question is related to your other question, where form needs to be used in v-model. Since form depends on the computed prop, you should use a watcher on the computed prop that updates form accordingly:
export default {
data() {
return {
form: []
}
},
computed: {
properties() {/*...*/},
},
watch: {
properties(properties) {
this.form = properties.map(_ => ({ property_type: [] }))
}
}
}
What is const properties = ['Property 1', 'Property 2'] ?
Pretty sure you cannot have it nested inside of data. Move it to the upper scope maybe or define it directly like
data() {
return {
properties: ['Property 1', 'Property 2']
}
}
Also, it's not very clear as of what you're trying to achieve here. A lot of things are mixed together. You're not really supposed to make a map in data since it's here for raw data (as it's called).
Here is the official documentation on computed properties, maybe give it a second read or update your question by explaining a bit better what is happening here.

[Vue warn]: Failed to mount component when using mixin with a parameter

since yesterday I'm struggling with creating a Vue mixin with a parameter and I'm getting a [Vue warn]: Failed to mount component: template or render function not defined.
Here is my JS file including mixin:
export default (dataObject) => ({
data() {
return {
inputValue: ''
}
},
methods: {
updateValue(newValue) {
this.inputValue = newValue
}
},
mounted() {
this.$bus.$on('value-changed', this.updateValue)
},
beforeDestroy() {
this.$bus.$off('value-changed');
},
computed: {
filteredData() {
if (this.inputValue !== '') {
let newData = Object.keys(dataObject)
.filter(key => key.includes(this.inputValue))
.reduce(
(newData, current) => ((newData[current] = dataObject[current]), newData), {}
)
return newData
}
else return dataObject
}
}
})
And here is my Vue component:
import searchLogic from '../logic/searchLogic.js'
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['champions']),
},
mixins: [searchLogic(this.champions)]
}
Importing this file works, because when I try to import a normal mixin without arguments it works properly. I also tried passing champions and "champions" instead of this.champions but none seem to work. Is it some problem with a mixin? I read that it's possible to return a function returning an object to use parameters when creating mixins.
Based on this article you should be able to statically pass a parameter, but not dynamically.
https://forum.vuejs.org/t/pass-parameters-to-a-mixin-method/26401/3
However, what I can see from the warn you get and the code you shared, is that you haven't defined any template for the component using the template property or the template element in the .vue file.

Should we use v-model to modify Vuex store?

Hello I am beginner in Vue and I do have a problem that's really bugging me.
I am wondering should we use v-model directive to modify vuex store? Vuex says that we should modify vuex store only by mutations but v-model makes everything easier and shorter.(I am asking because i couldn't find clear answer)
https://vuex.vuejs.org/guide/forms.html
When using Vuex in strict mode, it could be a bit tricky to use v-model on a piece of state that belongs to Vuex.
The "Vuex way" to deal with it is binding the <input>'s value and call an action on the input or change event.
Be sure to check out the simple "Two-way Computed Property" example on that page:
<input v-model="message">
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
I think another good option which hasn't been mentioned in any answer here is to use vuex-map-fields. In fact, the library author has written a very nice explanation for the library's usefulness. As per its GitHub page, to use the library you can do something like this:
In your Vuex Store, you can have a snippet similar to this:
import Vue from 'vue';
import Vuex from 'vuex';
import { getField, updateField } from 'vuex-map-fields';
Vue.use(Vuex);
export default new Vuex.Store({
// ...
modules: {
fooModule: {
namespaced: true,
state: {
foo: '',
},
getters: {
getField,
},
mutations: {
updateField,
},
},
},
});
And in your component code, you can have something along the lines of this:
<template>
<div id="app">
<input v-model="foo">
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields';
export default {
computed: {
// `fooModule` is the name of the Vuex module.
...mapFields('fooModule', ['foo']),
},
};
</script>
Additional examples for various use cases are shown in the library's GitHub repository that I linked to in the first sentence of this answer.
Above solution can also implemented with mutations:
<template>
<input v-model="message">
</template>
<script>
import { mapMutations, mapState } from 'vuex';
export default {
computed: {
...mapState({messageFromStore: 'message'}),
message: {
get() {
return this.messageFromStore;
},
set(value) {
this.updateMessage(value);
}
}
},
methods: {
...mapMutations('updateMessage')
}
};
</script>
My Solution to this was to use a getter to set value and #input to call the mutation.
<input
type="text"
:value="$store.getters.apartmentStreet"
#input="value => $store.commit('apartmentValue', { handle: 'street', value })"
>
getters.js:
export default {
apartmentStreet: state => state.apartment.street,
};
mutations.js
export default {
apartmentValue(state, payload) {
let oldValue = state.apartment[payload.handle];
let newValue = payload.value;
if (newValue !== oldValue) state.apartment[payload.handle] = payload.value;
}
};
If you use this method be sure to check which event you want.
I use this solution.
data() {
return {
formData: {
username: '',
email: '',
bio: {
firstName: '',
lastName: ''
},
games: ['civ4', 'caesar3', 'homeworld', 'cataclysm'],
}
}
},
computed: {
...mapGetters({ //or mapState
user: 'users'
})
},
watch: {
user(newValue) {
this.formData.username = newValue.name;
this.formData.email = newValue.email;
this.formData.bio.firstName = newValue.bio.firstName;
this.formData.bio.lastName = newValue.bio.lastName;
this.formData.games = newValue.games.map(x=> { return x });
}
},
beforeCreate: fucntion() {
this.$store.dispatch('getUser');
}
And then you just regularly use v-model.
It is important to make deep copy of object from store, like using map for array, and how i did stuff with object inside.
And, also you need to have initiated this user object in store also, with empty fields.
Yes you can but is not the best practice.
As the documentation say the state should be updated only inside mutation to keep the control over the state.
But if you really want to do it you can with:
v-model="$store.state.yourProperty"

VueJS: Best practice for working with global object between components?

there is User.js class and user object(user = new User();).
The user object is being used in all nested components. in User class there are so many important methods.
How can I simply use/access this.user or this.$user and its methods in any component?
1-solution (temporary working solution): Setting user in vuex's store and define in all components' data:
data(){
return {
user:this.$store.state.user
}
}
Cons: in every component, this should be added. Note: there are so many components.
2-solution: adding user to Vue's prototype like plugin:
Vue.prototype.$user = user
Cons: when user's data changes, it doesn't effect in DOM element (UI).
3-solution: putting to components's props.
Cons: in every component, this should be added. Note: Again there are so many components.
All of the solutions I found have issues, especially as the project gets larger and larger.
Any suggestion and solution will be appreciated!
Note: Applies for Vue 2x
Proposal 1: Using getters from vuex
You could use getters along with mapGetters from Vuex to include users within computed properties for each component.
Vuex
getters: {
// ...
getUser: (state, getters) => {
return getters.user
}
}
component
import { mapGetters } from 'vuex'
computed: {
...mapGetters([getUser])
}
Proposal 2: add a watcher via plugin
Vue
// When using CommonJS via Browserify or Webpack
const Vue = require('vue')
const UserPlug = require('./user-watcher-plugin')
// Don't forget to call this
Vue.use(UserPlug)
user-watcher-plugin.js
const UserPlug = {
install(Vue, options) {
// We call Vue.mixin() here to inject functionality into all components.
Vue.watch: 'user'
}
};
export default UserPlug;
Proposal 3: add a computed property user as plugin via mixin
Vue
// When using CommonJS via Browserify or Webpack
const Vue = require('vue')
const UserPlug = require('./user-watcher-plugin')
// Don't forget to call this
Vue.use(UserPlug)
user-watcher-plugin.js
const UserPlug = {
install(Vue, options) {
// We call Vue.mixin() here to inject functionality into all components.
Vue.mixin({
computed: {
user: function() {
return this.$store.state.user
}
}
})
}
};
export default UserPlug;
Based on #Denis answer, specifically Proposal 3, Here is the UserPlugin.js:
import store from '#/store/store';
import User from './User';
const UserPlugin = {
install(Vue) {
const $user = new User();
window.$user = $user;
store.commit('setUser', $user);
Vue.mixin({
computed: {
$user() {
return store.state.user;
}
}
});
}
};
export default UserPlugin;
and main.js:
import UserPlugin from './common/UserPlugin';
Vue.use(UserPlugin);
new Vue({
render: h => h(App)
}).$mount('#app');
For further usage, I published small library for solving these kinda issues:
https://www.npmjs.com/package/vue-global-var
Assuming you don't actually use all methods/attributes of user in every component, but a subset of them everytime, I don't see any reason why solution 1 & 2 do not work for you, since passing the whole user object to every component is not necessary.
Let's say your object User have some attributes (a1, a2, a3, etc.) and methods (m1, m2, m3...). If a component only needs some of them (e.g. a1, a2, m1, m2, m3) then with Vuex, you can use mapping functions (mapState, mapGetters, mapMutations and mapActions) to get the exact info from user
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
...mapState('user', [ 'a1' ]),
...mapGetters('user', [ 'a2' ])
},
methods: {
...mapMutations('user', [ 'm1' ]),
...mapActions('user', [ 'm2', 'm3' ])
}
}
For solution 2 (using prototype), to make component update when user data changes, you can map the necessary data to component via methods.
export default {
methods: {
userA1() {
return this.$user.attributes.a1;
},
userM1() {
this.$user.methods.m1();
}
// and so on
}
}
Even better, you can create mixins to explicitly map data from user, and reuse your mixins to avoid duplicated code in components. It can be applied for both Vuex solution and prototype solution.
// mixin1:
const mixin1 = {
computed: {
...mapState('user', [ 'a1' ]),
},
methods: {
...mapMutations('user', [ 'm1' ])
}
}
// mixin2:
const mixin2 = {
computed: {
...mapGetters('user', [ 'a2' ]),
},
methods: {
...mapActions('user', [ 'm2', 'm3' ])
}
}
// component1
export default {
mixins: [ mixin1 ]
}
// component 2
export default {
mixins: [ mixin1, mixin2 ]
}
But if you really need to pass the whole object user to every component, then nothing could do. Rather, you should review your implementation and see if there is any better way to break the object into smaller meaningful ones.
You can use mixins to add User.js to your root component like
import userLib from './User';
//User.js path should correct
Then
var app = new Vue({
router,
mixins: [
userLib
],
//.....
});
After that you can use any of these User method in your any component like
this.$parent.userClassMehtod();
or if any data access
this.$parent.userClassData;
Finally dont forget to add export default{//..} in User.js
Note: This is only work if you export all method of User.js into export default
I just created the minimal codesandbox to clear the idea of how dependency Injection works in vue.
You can have a second Vue instance and declare a reactive property.
See: Reactivity in depth

Using root functions inside of prop object

I'm using Vuejs2.
I have a prop that contains some validation. I want to use a Lang object that is mixed into all my components. One of my props has a default value that needs to access this lang object. I can access the lang object in the create() function but not in the props.foo.default() option.
app.js
import Vue from 'vue';
import messages from '../lang/messages';
import lang from 'lang.js';
const Lang = new lang({
'messages': messages,
'locale': 'en'
});
Vue.mixin({
data: function () {
return {
lang: Lang
}
}
});
const app = new Vue({
el: '#app'
});
bar.component.vue
<template>
<div>{{ foo }}</div>
</template>
<script>
export default {
props: {
foo: {
type: String,
default: this.lang.get('some.lang') // Cannot read property 'get' of undefined
}
},
created () {
console.log(this.lang.get('some.lang'); // returns correct value
}
}
</script>
So to clarify I can access this.lang inside my created() function but I cannot get to it in my props object.
From what I could derive the context of this is different depending on the object / function you are in. I've tried to edit the props in the created hook but could not find a way to get access. I managed to get it working by creating a filter but it's inconsistent with some other code that I have in my component.
What would be a good way for a prop to have a default value that can access the root vm or be set outside of the props object?
You can't set a prop's default value based on a data property in a Vue instance. You can't access the Vue instance in the context of the props object, and you can't set the default value after the component has been instantiated.
Either import the lang object directly into the bar.component.vue file:
<script>
import lang from 'lang.js'
export default {
props: {
foo: {
type: String,
default: lang.get('some.lang')
}
}
}
</script>
Or, create a computed property fooVal which will return this.lang.get('some.lang') if the foo prop is not defined, and then use that in your template instead:
<template>
<div>{{ fooVal }}</div>
</template>
<script>
export default {
props: {
foo: { type: String }
},
computed: {
fooVal() {
return (this.foo === undefined) ? this.lang.get('some.lang') : this.foo;
}
}
}
</script>

Categories

Resources