Using v-model and refs in a slot in Vue2 - vue.js

I have a component that takes a main <slot> from a form that is generated elsewhere in my application. I'm trying to use v-model on the form inputs but my vue component just spits out a warning about the properties not being defined, when in fact they are.
I admit it's a weird way of doing things, but it seems to be the easiest way for me to do this since my form is being generated by Symfony.
html:
<my-component>
<input ref="start" v-model="start"/>
</my-component>
my component:
<script>
export default {
data() {
start: null
},
mounted() {
console.log(this.$refs) // === {}; expected {"start":{...}}
}
}
</script>
<template>
<div>
<slot/>
... other stuff here
</div>
</template>
console log:
Property or method "start" is not defined on the instance but referenced during render
I cannot use $refs or v-model in the html. Am I doing something wrong? Or is this just not possible.

If you declare v-model="start" in the parent then it belongs to the parent and needs to be declared there. It looks like instead you declare it in the component instead as null.
If you reorder things it should work as you expect:
Parent:
<parent>
<input v-model="start" :start="start"/>
</parent>
<script>
export default {
data() {
start: null // Important to define start here if it's used in this component's html
}
}
</script>
Component:
<template>
<div>
<slot/>
... other stuff here
</div>
</template>
<script>
export default {
props: ['start'], // Receive the prop from the parent
data() {
},
mounted () {
console.log(this.start) // Should echo the value of the prop from the parent
}
}
</script>

Related

Calling function inside child component without an event?

Currently trying to use a method belonging to the parent
<p class="message-date text-center">
{{ $emit('format_date_day_month_year_time', message.date) }}
</p>
However I am getting the error.
Converting circular structure to JSON
--> starting at object with constructor 'Object'
How can I call a function inside a child component that does not rely on an event? I apologize for asking such a simple question but everything I was able to find on google is using $emit and using an event.
$emit was designed to only trigger an event on the current instance of vue. Therefore, it is not possible to receive data from another component this way.
For your case, I would suggest to use Mixins especially if you need to use certain functions among multiple vue components.
Alternately, let the child component call the the parent through $emit then receive the result from the parent through a prop.
Your code could be something as follows:
Child component
<template>
<p class="message-date text-center">
{{ date }}
</p>
</template>
<script>
export default {
name: 'Child',
props: {
date: String,
},
mounted() {
this.$emit("format-date", message.date);
},
}
</script>
Parent component
<template>
<Child :date="childDate" #format-date="formatChildDate" />
</template>
<script>
import Child from '#/components/Child';
export default {
components: {
Child,
},
data: () => ({
childDate: '',
}),
methods: {
formatChildDate(date) {
this.childDate = this.formatDateDayMonthYearTime(date)
},
formatDateDayMonthYearTime(date) {
//return the formatted date
},
},
}
</script>
with $emit you call a function where the Parent can listento.
where you are using it i would suggest a computed prop of the function.
But back to your Question here is a example of emiting and listen.
//Parent
<template>
<MyComponent #childFunction="doSomethingInParent($event)"/>
</template>
//Child
<template>
<button #click="emitStuff">
</template>
.....
methods:{
emitStuff(){
this.$emit(childFunction, somedata)
}
with the event you can give Data informations to a Parentcomponent.

Vue.js: too many $emit from children to parent

I have the main component that containing other components which containing anothers components.
So, I have the click events in these components, but to handle it in my parent component, I need to make $emit call in each nested component.
How to make this process more simple, for example like in React, where I need just pass the function handler into component.
In vue 2.2.3+ you can use provide and inject to pass function from ancestor component to child, like great grandparent to child.
please refer following code
// app.vue
<template>
<div id="app">
<HelloWorld msg="button1" />
<HelloWorld msg="button2" />
<HelloWorld msg="button3" />
count: {{ count }}
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
provide() {
return {
clickHandler: this.clickHandler,
};
},
data() {
return {
count: 0,
};
},
components: {
HelloWorld,
},
methods: {
clickHandler() {
this.count += 1;
console.log("click received");
},
},
};
</script>
// HelloWorld.vue
<template>
<button #click="clickHandler">{{ msg }}</button>
</template>
<script>
export default {
name: "HelloWorld",
inject: ["clickHandler"],
props: {
msg: String,
},
};
</script>
you can see the same clickHandler function from parent is executed with modifying parents count prop on click of each children.
this clickHandler can be injected directly to any descendent at any level therefore application like
parent > child.1 > child.1.1 > child.1.1.1 > child.1.1.1.1(click)
the child.1.1.1.1 can be injected with clickHandler form parent.
try the code at codesandbox
also refer provide/inject
if you need the same value up in the hierarchy or anywhere in the current module, you should try to use the Vuex(State Management) library.

Pass data from blade to vue and keep parent-child in sync?

I know that in Vue parents should update the children through props and children should update their parents through events.
Assume this is my parent component .vue file:
<template>
<div>
<my-child-component :category="category"></my-child-component>
</div>
</template>
<script>
export default {
data: {
return {
category: 'Test'
}
}
}
</script>
When I update the category data in this component, it will also update the category props in my-child-component.
Now, when I want to use Vue in Laravel, I usually use an inline template and pass the value from the blade directly to my components (as for example also suggested at https://stackoverflow.com/a/49299066/2311074).
So the above example my my-parent-component.blade.php could look like this:
#push('scripts')
<script src="/app.js"></script>
#endpush
<my-parent-component inline-template>
<my-child-component :category="{{ $category }}"></my-child-component>
</my-parent-component>
But now my-parent-component is not aware about the data of category. Basically only the child knows the category and there is no communication between parent and child about it.
How can I pass the data from blade without breaking the parent and child communication?
I just had to pass the category to the inline-template component through props like this:
#push('scripts')
<script src="/app.js"></script>
#endpush
<my-parent-component :initcategory="{$category}}" inline-template>
<my-child-component v-model="category"></my-child-component>
</my-parent-component>
In my-parent-component I had to set the props and initialize is using the create method:
export default {
props: {
initcategory: '',
},
data() {
return {
category: '',
};
},
created(){
this.category = this.initcategory;
}
}
Now my my-parent-component is fully aware of the category and it can communicate to the child using props and $emit as usual.
Your reference to this answer is different altogether from what you are looking for!
He's binding the :userId prop of the example component but not the parent component or in simple words: Any template using the example vue can either pass a string prop or bind :userId prop to a string variable. Following is similar:
<example :userId="{{ Auth::user()->id }}"></example>
OR
<example :userId="'some test string'"></example>
So you should rather assign {{ $category }} to a data variable but rather binds to a child component prop which will have no effect on the parent.
In the following snippet you're only binding the string but rather a data key:
<my-child-component :category="{{ $category }}"></my-child-component>
Update
See the following example which will change the h1 title after 3 seconds
// HelloWorld.vue
<template>
<app-name :name="appName" #appNameChanged="appName = $event"></app-name>
</template>
<script>
export default {
props: ['name'],
data() {
return {
appName: null
}
},
mounted() {
// NOTE: since Strings are immutable and thus will assign the value while objects and arrays are copied by reference
// the following is just for the purpose of understanding how binding works
this.appName = this.name;
}
}
</script>
The template which renders the app title or you can say the child component
// AppName.vue
<template>
<h1>{{ name }}</h1>
</template>
<script>
export default {
props: ['name'],
mounted() {
setTimeout(() => {
this.$emit('appNameChanged', 'Change App')
}, 3000);
}
}
</script>
And here's how it is being used in the welcome.blade.php
<div id="app">
<hello-world :name="'Laravel App'"></hello-world>
</div>

Vue js how to use props values to v-model

I have two component namely App.vue and hello.vue
In App component I import the hello component and use props to pass relevant data to the hello component.
there I bind data which are took from the App component.
In my hello component I have a input box bind to the passed value.
My final goal is pass values as props to the hello component and change it and finally
pass that edited values to the backend using the save method.
How do I achive this?
This is what I have done up to now.
App.vue
<template>
<div id="app">
<hello-world :msg="'hello good morning'"></hello-world>
</div>
</template>
<script>
import helloWorld from "./components/HelloWorld";
export default {
components: {
helloWorld
}
};
</script>
hello.vue
<template>
<div>
<input type="text" :value="msg">
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
}
};
</script>
In my hello component's input field v-model is not possible. I want something similar to the v-model.
You cannot use prop to bind to v-model. Child component is not supposed to modify prop passed by the parent component.
You will have to create a copy of prop in your child component if you wish to use prop with v-model and then watch prop like this:
<template>
<div>
<input type="text" #input="onInput" v-model="msgCopy">
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
},
data() {
return { msgCopy: '' };
},
methods: {
onInput(newInputValue) {
this.$emit('msgChange', newInputValue);
}
}
watch: {
msg(newVal) {
this.msgCopy = newVal;
}
}
};
</script>
Also, notice the use of event handler #input to pass changed prop back to the parent component via event. As a syntax sugar, you can make your Hello component work as a custom form input control by adopting to v-model lifecycle.

vue reload child component

I'm using vue, version 2.5.8
I want to reload child component's, or reload parent and then force children components to reload.
I was trying to use this.$forceUpdate() but this is not working.
Do You have any idea how to do this?
Use a :key for the component and reset the key.
See https://michaelnthiessen.com/force-re-render/
Add key to child component, then update the key in parent. Child component will be re-created.
<childComponent :key="childKey"/>
If the children are dynamically created by a v-for or something, you could clear the array and re-assign it, and the children would all be re-created.
To simply have existing components respond to a signal, you want to pass an event bus as a prop, then emit an event to which they will respond. The normal direction of events is up, but it is sometimes appropriate to have them go down.
new Vue({
el: '#app',
data: {
bus: new Vue()
},
components: {
child: {
template: '#child-template',
props: ['bus'],
data() {
return {
value: 0
};
},
methods: {
increment() {
this.value += 1;
},
reset() {
this.value = 0;
}
},
created() {
this.bus.$on('reset', this.reset);
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<child :bus="bus">
</child>
<child :bus="bus">
</child>
<child :bus="bus">
</child>
<button #click="() => bus.$emit('reset')">Reset</button>
</div>
<template id="child-template">
<div>
{{value}} <button #click="increment">More!</button>
</div>
</template>
I'm using directive v-if which is responsible for conditional rendering. It only affects reloading HTML <template> part. Sections created(), computed are not reloaded. As I understand after framework load components reloading it is not possible. We can only re render a <template>.
Rerender example.
I have a Child.vue component code:
<template>
<div v-if="show">
Child content to render
{{ callDuringReRender() }}
</div>
</template>
<script>
export default {
data() {
return {
show: true
}
}
,
methods: {
showComponent() {
this.show = true
},
hideComponent() {
this.show = false
},
callDuringReRender() {
console.log("function recall during rendering")
}
}
}
</script>
In my Parent.vue component I can call child methods and using it's v-if to force the child rerender
<template>
<div>
<input type="button"
value="ReRender the child"
#click="reRenderChildComponent"/>
<child ref="childComponent"></child>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
methods: {
reRenderChildComponent(){
this.$refs.childComponent.hideComponent();
this.$refs.childComponent.showComponent()
}
},
components: {
Child
}
}
</script>
After clicking a button in console You will notice message "function recall during rendering" informing You that component was rerendered.
This example is from the link that #sureshvv shared
import Vue from 'vue';
Vue.forceUpdate();
// Using the component instance
export default {
methods: {
methodThatForcesUpdate() {
// ...
this.$forceUpdate(); // Notice we have to use a $ here
// ...
}
}
}
I've found that when you want the child component to refresh you either need the passed property to be output in the template somewhere or be accessed by a computed property.
<!-- ParentComponent -->
<template>
<child-component :user="userFromParent"></child-component>
</template>
<!-- ChildComponent -->
<template>
<!-- 'updated' fires if property 'user' is used in the child template -->
<p>user: {{ user.name }}
</template>
<script>
export default {
props: {'user'},
data() { return {}; }
computed: {
// Or use a computed property if only accessing it in the script
getUser() {
return this.user;
}
}
}
</script>