Using root functions inside of prop object - javascript

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>

Related

Modifying variable in method does not update child component

I'm struggling with how Vue updates props/child components.
Suppose the following component:
<template>
<v-card>
<Modification v-model="newObject"></Modification>
<OtherComponent #close="resetObject"></OtherComponent>
</v-card>
</template>
<script>
import { MyClass } from "classes";
import Modification from "b";
import OtherComponent from "a";
export default {
name: "MyForm",
components: { OtherComponent, Modification },
props: {
existingObject: {
type: [MyClass, typeof undefined],
required: false,
default: undefined
}
},
data() {
return {
newObject: undefined
};
},
created() {
this.newObject =
this.existingObject !== undefined
? this.existingObject.clone()
: new MyClass();
},
methods: {
resetObject() {
this.newObject =
this.existingObject !== undefined
? this.existingObject.clone()
: new MyClass();
}
}
};
</script>
How MyClass is defined:
export class MyClass {
constructor({ a= null, b=null} = {}) {
this.a = a;
this.b = b;
}
toPayload(){
return { a:this.a , b:this.b };
}
clone() {
return new MyClass(this.toPayload());
}
}
This component receives an existing class instance of MyClass, clones it (clone => new MyClass(...)) and passes it to the Modification component which does some modification upon user input. So far so good, the modification works. However once the customEvent is fired and the resetObject method is called the newObject is reset but the Modification component is not updated with the now reset newObject - it still displays the old, modified values. I also checked inside the Modification component wether or not the update happens: It doesn't.
Why is this the case? Am I missing a step? Am I not aware of a Vue specific mechanism?
Note: I found this blog which provides solutions to force the Modificationcomponent to update. For now it seems to hacky for me to be "THE" solution.
Thanks in advance.
EDIT:
Adding a computed property which includes a console.log(JSON.stringify(this.newObject)) fires everytime newObject is updated.
Also adding a <span> {{ newObject.a }} </span> to the template updates evertime.
Both these tests convince me that the variable not only should be but actually IS reactive.
EDIT 2:
The Modification component consists, for now, of 2 Input components.
It looks like this.
<template>
<v-card-text>
<ModifyA v-model="object.a" #input="handleInput" />
<ModifyB v-model="object.b" #input="handleInput" />
</v-card-text>
</template>
<script>
import { MyClass } from "classes";
import ModifyA from "...";
import ModifyB from "...";
export default {
name: "ShiftFormFields",
components: { ModifyA, ModifyB },
props: {
value: {
type: MyClass,
required: true
}
},
data() {
return { object: this.value };
},
methods: {
handleInput() {
this.$emit("input", this.object);
}
}
};
</script>
If I try adding the ModifyA Input into the component instead of the Modification component like this
<template>
<v-card>
<ModifyA v-model="newObject.a"></Modification>
<OtherComponent #close="resetObject"></OtherComponent>
</v-card>
</template>
the resetObject also resets the value shown in the ModifyA component.
You didn't show how MyClass clones your object.
I'm guessing something in there isn't reactive.
You can check by doing console.log() and see what it says on the console.
If it's reactive, it should show something like MyClass {__ob__: Observer}
You can probably use this.$set('propName', value) to fix your problem
Docs: https://v2.vuejs.org/v2/api/#vm-set
Adds a property to a reactive object, ensuring the new property is also reactive, so triggers view updates. This must be used to add new properties to reactive objects, as Vue cannot detect normal property additions (e.g. this.myObject.newProperty = 'hi').
Either there is a typo in your post, or the typo also exists in your code and is the source of your problem.
In your post you're binding "newObjekt" to the Modification component, but your parent component has the property "newObject"
is this the source of your issue?
I found the solution in this answer.
As I edited my original post with the definition of the Modification component
<template>
<v-card-text>
<ModifyA v-model="object.a" #input="handleInput" />
<ModifyB v-model="object.b" #input="handleInput" />
</v-card-text>
</template>
<script>
import ModifyA from "...";
import ModifyB from "...";
export default {
name: "ShiftFormFields",
components: { ModifyA, ModifyB },
props: {
value: {
type: MyClass,
required: true
}
},
data() {
return { object: this.value };
},
methods: {
handleInput() {
this.$emit("input", this.object);
}
}
};
</script>
it shows the "problem" why the Fields ModifyA and ModifyB do not update if the value updates in the parent component.
As seen in the above definition the variable object is only set to the value once the Component is initialized. It follows that object is not reactive on behalf of value.
To solve this one can use the approach of the above mentioned answer:
<template>
<v-card-text>
<ModifyA v-model="object.a" />
<ModifyB v-model="object.b" />
</v-card-text>
</template>
<script>
import { Shift } from "classes";
import ModifyA from "...";
import ModifyB from "...";
export default {
name: "ShiftFormFields",
components: { ModifyA, ModifyB },
props: {
value: {
type: MyClass,
required: true
}
},
data() {
return { object: this.value };
},
watch: {
value(val) {
this.object = val;
},
object(value) {
this.$emit("input", value);
}
}
};
</script>
Due to the watcher, the object variable is updated whenever the value get's updated by the parent.

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

How do I export a string to another Vue file?

I have a masterData.js file that is a store for my master data, in short the file reads my mongo db data & sends it to other project components. I created a function to export the string in the masterData.js file as:
/ ***************************** MUTATIONS
const mutations = {
exportColumns (payload) {
Object.keys(payload[0]).map(x => { return x; });
}
}
Where payload will store all the rows and payload[0] holds the value of column header names. The output of this chunk of code is like this:
["_id","businessAreaName","businessAreaDisplay","councilDisplay","councilID"]
I want to transfer the values to masterData.vue file. My code on masterData.Vue is:
importColumns ()
{
let payload = {
vm: this,
mutation: 'masterData/exportColumns'
};
}
What else should I add to to check whether the column names are received or not?
If you're trying to access the data in your store from within a component, then you'll want to either just map the state to the component or map a getter to the component. Mutations are used by components (or by actions) to modify the state of the store. So instead you would do something like...
//masterData.js
//assuming this gets rolled up as a module called masterdata to the primary store
//store for payload state
const state = {
payload: null,
}
//allows payload to be set -- not sure how you are retrieving the payload but you can use this to store it however you get it
const mutations = {
setPayload (state, payload) {
state.payload = payload
}
}
//get just the columns
const getters = {
getColumns (state) {
Object.keys(state.payload[0]).map(x => { return x; })
}
}
export default {
state,
mutations,
getters,
}
Then
//masterData.vue
<template>
//...
</template>
<script>
import { mapGetters, mapState } from 'vuex'
export default {
computed: {
//I believe getting state from a store module requires a function like this
...mapState({
payload: function(state) {
return state.masterdata.payload
},
}),
//I think for getters you can just reference the method and it will find it
...mapGetters([
'getColumns',
])
},
}
</script>
This is how you import stuff in a single file component.
<template>
<!-- html stuff -->
</template>
<script>
import Mutations from 'yourModule.js'
export default {
name: 'YourComponent',
props: {},
data(){
return {
foo: 'foo'
}
},
methods{
mymethod() {
Mutations.exportColumn(this.foo);
},
}
}
</script>

Accessing data in vue component

Im trying to access some data I have binded to a component with no luck. How should I do that?
My component:
export default {
name: 'component-vallingby',
data() {
return {
}
},
created() {},
methods: {}
}
Here's where im rendering it. Offer is an object:
<component-vallingby v-bind="{ offer: offer }"></component-vallingby>
Pass the prop to the component like :order="order". Inside the component vm, list this in props array as a String.
export default {
name: 'component-vallingby',
props: ['offer']
}
Then instantiate your component passing order like this:
<component-vallingby :offer="offer"></component-vallingby>
You need props inside of your export default {} mentioned.
props: ['offer']
You should bind it as
<component-vallingby :offer="offer"></component-vallingby>

Pass prop as module name when mapping to namespaced module

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 }
}

Categories

Resources