VueJs - Set property in child component's $options based on prop - vue.js

Reproducible example
https://codesandbox.io/s/youthful-banach-tc83p?file=/src/components/ChildComponent.vue
Background
In the project I'm working on, there are helper functions which depend on a translationFile property in the $options of the component. However, when the component is a generic one being implemented in different places, that property will be different depending on the implementation.
What I want to do
Is there a way that I can set a property in the child component's $options dynamically based on the parent component?
Helper function example
getLabel(labelKey) {
const { translationFile } = this.$options;
if (translationFile && labelKey) {
return this.$tc(`${translationFile}.labels.${labelKey}`);
}
},
this would be called from the child component (CC), hence this.$options would refer to the CC.
When the parent component (PC) is PC1.vue, the file it would look in would be tran1.js, when PC2.vue, then tran2.js

I was able to achieve what I wanted with.
props: {
translationFile: {
type: String,
required: true
}
}
mounted() {
this.$options.translationFile = this.translationFile
}
but any components which depended on this needed to be wrapped with <client-only></client-only>

Related

Can we use props to pass variable in vue?

Slall question:
I have a 2 parent components nesting the same child component inside of them.
I use props so the parents can tell the child what title to show.
this child component is a photo gallery. It makes a query to the database, download photos and show them up. classic.
I need the parents to tell the child where to get the photos from:
Get the photos from All users, for the home page
or
get only the photos from a specific user for a user's page.
I'm wondering if I can pass this information through a prop.
Is that possible? Can we use the info from a prop as a varialble inside of the setup() function?
Is there a better way to do this?
Passing objects from one component to a child component is the purpose of props.
You can pass many items through props. VueJS has the following types built-in:
String
Number
Boolean
Array
Object
Function
Promise
In the V3 VueJS guide it gives the following example of a prop being passed into a component and then being logged to the console inside the setup() method.
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
However, when using props, you should always mark whether the is required, what its type is and what the default is if it is not required.
For example:
export default {
props: {
title: String, // This is the type
required: false, // If the prop is required
default: () => 'defaultText' // Default needed if required false
},
setup(props) {
console.log(props.title)
}
}
It's important to note that when using default, the value must be returned from a function. You cannot pass a default value directly.

VueJS: How to to access parent function in child to conditionally render css classes?

I have a parent component with a function like (simplified example)
isValid(value) { return value > validationModifier }
Both the parent and the child use that function to conditionally render e.g. CSS classes. So in my child I would like to use:
:class="{'my-class' : isValid(myValue)}"
But I don't have access to this function. I want to avoid duplicating it in the child, and I don't see how emitting an event would work in this case.
What is the appropriate way to deal with this?
If the function has reusable logic, rather than specific to that parent component, then I would use a mixin. If you want to add any other shared logic (methods, computed functions) you can edit the mixin and don't have to explicitly add the new parameter to parent and child
mixin code:
const myMixin = {
methods:{
isValid(param1){
return param1 < validationModifier
}
}
}
then to inject into any of your components
{
name: "my-custom-component",
mixins:[myMixin],
methods:{}
}
You can pass the function to the child like a classical function prop https://v2.vuejs.org/v2/guide/components-props.html#Prop-Types
No need to use the event/emit system here.
<child v-bind:is-valid="isValid"></child>
#Joel H's answer is one of the ways to reuse functions in Vue. Another way is to use dependency injection in Vue. See https://v2.vuejs.org/v2/guide/components-edge-cases.html#Dependency-Injection
You just have to provide the method and all the children components of the ParentComponent can access that isValid method. Dependency injection in Vue is not limited to functions only, you can pass variables and data too.
export default {
name: 'ParentComponent',
...
methods: {
isValid(value) { return value > validationModifier },
},
provide() {
return {
isValid: this.isValid
}
}
}
and in your ChildComponent ...
export default {
name: 'ChildComponent',
...
inject: ['isValid']
}
Now you can use the function in your ChildComponent using this.isValid(yourValueHere).

Tracking a child state change in Vue.js

I have a component whose purpose is to display a list of items and let the user select one or more of the items.
This component is populated from a backend API and fed by a parent component with props.
However, since the data passed from the prop doesn't have the format I want, I need to transform it and provide a viewmodel with a computed property.
I'm able to render the list and handle selections by using v-on:click, but when I set selected=true the list is not updated to reflect the change in state of the child.
I assume this is because children property changes are not tracked by Vue.js and I probably need to use a watcher or something, but this doesn't seem right. It seems too cumbersome for a trivial operation so I must assume I'm missing something.
Here's the full repro: https://codesandbox.io/s/1q17yo446q
By clicking on Plan 1 or Plan 2 you will see it being selected in the console, but it won't reflect in the rendered list.
Any suggestions?
In your example, vm is a computed property.
If you want it to be reactive, you you have to declare it upfront, empty.
Read more here: reactivity in depth.
Here's your example working.
Alternatively, if your member is coming from parent component, through propsData (i.e.: :member="member"), you want to move the mapper from beforeMount in a watch on member. For example:
propsData: {
member: {
type: Object,
default: null
}
},
data: () => ({ vm: {}}),
watch: {
member: {
handler(m) {
if (!m) { this.vm = {}; } else {
this.vm = {
memberName: m.name,
subscriptions: m.subscriptions.map(s => ({ ...s }))
};
}
},
immediate: true
}
}

How to watch child properties changes from parent component

I am using a date picker component library and i want to watch when a property of that component changes.
I have tried this:
watch: {
'$refs.picker.popupVisible': {
handler (new_value) {
console.log('executed')
},
deep: true
}
}
Also this:
computed: {
picker () {
console.log(this.$refs.picker.popupVisible)
return this.$refs.picker.popupVisible
}
}
I know that the solution will be a vue.js hack because this is not the right way.If i had access to child component i would emit en event to parent but unfortunately i don't have.
I had a similar problem using a library which had some limitations.
Unfortunately, your watcher will not work.You have to use the function watcher to make this to work.And you have to use it inside the mounted hook.
mounted() {
this.$watch(
"$refs.picker.popupVisible",
(new_value, old_value) => {
//execute your code here
}
);
}
I also have an example. Please take a look here
What you can do is create a data object in parent component and include the date field in that data object and pass that data object to child component as props
<child :dateObj="dateObj"></child>
data: {
dateObj: {
date: ""
}
}
And in child component you can use the date field of that dateObj props. This is possible because Vue doesn't watch the property of Objects passed as props and we can modify them without Vue complaining in console.
Thus the changed date field is reflected in parent as well.

vuejs pass model from parent component to children component and allow mutation

Currently I have a vue-multiselect component which requires a v-model.
I want to wrap this component so that I can build one single-select component and one multi-select component.
While working on the single select component I encountered the following warning
[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: "model"
They are right but in my case I really need to change the value from the parent (like I replace my single-select code with the vue-multiselect code) component and I also do not want this warning.
Here is the code for my component:
Vue.component('single-select', {
props: {
model: {
required: true
}
}
template: '<multiselect\n' +
' v-model="model"\n' +
...>\n' +
...
'</multiselect>'
});
One solution would be to pass a function as a model parameter and return the field from the parent but I really hope for a better solution.
Vue has a shortcut for 2 way binding called .sync modifier.
How it works in your case:
add .sync when you pass model as prop
<single-select :model.sync="..."></single-select>
emit an update:model in the child's input event
Vue.component('single-select', {
props: {
model: {
required: true
}
},
template: `<multiselect :value="model" #input="$emit('update:model', $event)"> </multiselect>`
});
Just give the internal model reference a different name, and the in the Vue component's data function map it manually:
Vue.component('single-select', {
props: {
model: {
required: true
}
},
data: function() {
return {
singleSelectModel: this.model
};
}
template: '<multiselect v-model="singleSelectModel"></multiselect>';
});
This is of course, assuming that you do not want to mutate the parent data, but simply making a copy of model and giving the child component the freedom to change it whenever it wants.
If what you want is to also update the parent data from the child, you will have to look into emitting events from the child and listening in the parent.