Access slot component data? - vue.js

I have the following setup:
CustomForm.vue
<template>
<div>
<input v-model="field1" >
<input v-model="field2" >
</div>
</template>
<script>
export default {
data () {
return {
field1: '',
field2: '',
}
}
}
</script>
Parent.vue
<template>
<div>
<child>
<template>
<custom-form />
</template>
</child>
</div>
</template>
<script>
import Child from ...
import CustomForm from ...
</script>
Child.vue
<template>
<div>
<button #click="click" />
<grand-child>
<template>
<slot></slot>
</template>
</grand-child>
</div>
</template>
<script>
import GrandChild from...
export default {
methods: {
click: function () {
var data = ... // get form data
// do something with data then $emit
this.$emit('custom-click', data)
}
}
}
}
</script>
GrandChild.vue
<template>
<div v-for="(item, index) in list" :key="index" >
<input ...>
<input ...>
<slot></slot>
</div>
</template>
Basically I have a CustomForm, I want to pass the form to GrandChild.vue from Parent.vue, but the issue is I don't know how do I retrieve CustomForm data (field1, field2) in Child.vue ie how do I get CustomForm value from click method in Child.vue? Thanks

Instead of trying to extract data from a slot, there are other approaches. One solution is to use Vue's provide/inject feature to inject the form's data.
First, setup CustomForm to allow v-model to capture the form data in Parent:
Upon submitting the form in CustomForm, emit the input event with the form's data.
Add a value prop, as required for `v-model.
CustomForm.vue:
<template>
<form #submit.prevent="submit"> <!-- (1) -->
</form>
</template>
<script>
export default {
props: ['value'], // <-- (2)
methods: {
submit(e) { // <-- (1)
const form = e.target
const formData = {}
for (const [key, value] of new FormData(form).entries()) {
formData[key] = value
}
this.$emit('input', formData)
}
}
}
</script>
Then Parent can bind the form data:
Define parentForm in Parent's data as an object with a subproperty (e.g., parentForm.data).
Bind that subproperty to CustomForm's v-model.
Parent.vue:
<template>
<child>
<custom-form v-model="parentForm.data" /> <!-- (4) -->
</child>
</template>
<script>
export default {
data() {
return {
parentForm: { // <-- (3)
data: {}
}
};
}
}
</script>
Now, Parent can provide form:
Declare a provide method that returns an object with a form subproperty.
Set that property's value to the parentForm property previously declared in (3).
Parent.vue:
export default {
provide() { // <-- (5)
return {
form: this.parentForm // <-- (6)
}
}
}
...and Child or GrandChild can inject form:
Declare an inject property, whose value is a string array containing form (subproperty name from (5)).
GrandChild.vue:
export default {
inject: ['form'] // <-- (7)
}
demo

Related

Parent variable not updated when updating trough child component

I am trying to create a few custom form fields for my page and i learned that i cannot use props to do so so i am trying to find a way to update my parent component variable when i use my child component. Whe i check the parent variable it is always empty.
Here is my component:
<template>
<input
v-model="value"
:placeholder="placeHolder"
class="form-field"
>
</template>
<script>
export default {
props: ['placeHolder'],
data() {
return {
value: ''
}
},
methods: {
updateValue(){
this.$emit("update-text", this.value);
}
},
watch: {
value: function(){
this.updateValue
}
}
}
</script>
And this is how i use the component:
<TextField placeholder="Nome" :update-text="name = value"/>
what exactly am i doing wrong?
I am using vue.js with nuxt.js
I think a simpler approach in this case might be emitting an input event from your custom text field and binding the component to the variable using v-model.
TextField.vue
<template>
<input
#input="$emit('input', $event.target.value)"
:placeholder="placeHolder"
class="form-field"
>
</template>
<script>
export default {
props: ['placeHolder']
}
</script>
Usage
<template>
<TextField placeholder="Nome" v-model="name"/>
</template>
<script>
export default {
data: () => ({
name: '',
}),
}
</script>
Read more about using v-model on custom components here.

Vue 2 pass value from child to parent element (make custom styled input)

let say if i have a custom input
<template>
<input v-model="value"/>
</template>
<script>
export default {
name: "my-input",
props: {
value: String
}
}
</script>
how i can pass it value to parent element
<template>
<my-input v-model="test"/>
<button #click="check">see value</button>
</template>
<script>
export default {
methods: {
check() {
console.log(this.test);
}
}
}
</script>
when i press the button it shows undefined. i expected it to returns my-input value.
how i could pass value from it? i tried v-model and :value, both shows undefined.
Bind the input value to prop value then add #input event that emits the input value to parent component :
<template>
<input :value="value" #input="$emit('input', $event.target.value)"/>
</template>
<script>
export default {
name: "my-input",
props: {
value: String
}
}
</script>

Modify Data From Child To Parent

I am trying to modify a property routing of a parent component from a child as follows:
//- Parent
<template>
<First #toggleContent="routing = !routing" />
</template>
<script>
export default {
data() {
return { routing: true }
}
}
</script>
//- Child (First component)
<template>
<div>
<i class="bx bx-shape-triangle" #click="toggleContent()"></i>
{{routing}}
</div>
</template>
<script>
export default {
props: {
routing: Boolean
},
methods: {
toggleContent() {
this.$emit('toggleContent')
}
}
}
</script>
on the console does not mark me any error.
Your child component requires a prop routing that needs to passed in from parent component, also keep in mind always using kebab case for your events.
<First #toggle-content="routing = !routing" :routing="routing"/>
Modify your event name in emit as well:
toggleContent() {
this.$emit('toggle-content')
}

vuejs passing method to child dynamically

I have a 3rd party component <third-party-component /> which accepts following event:
onAdd, onRemove, onUpdate
I want to create a wrapper component around it and want to pass these events dynamically so that can handle the response in wrapper component, something like
wrapper.js
<template>
<div class="my-wrapper">
<third-party-component />
</div>
<template>
using-wrapper.js
<template>
<div>
...
<wrapper #onAdd="add" #onRemove="remove"></wrapper>
...
</div>
</template>
<script>
export default {
methods: {
onAdd() {
console.log('on add in using-wrapper.js');
},
onRemove() {
console.log('on remove in using-wrapper.js');
}
}
}
</script>
You can pass all attributes and listeners by binding them using v-bind and v-on
You also need to set inheritAttrs to false
https://v2.vuejs.org/v2/api/#inheritAttrs
<template>
<div class="my-wrapper">
<third-party-component v-bind="$attrs" v-on="$listeners"/>
</div>
<template>
using-wrapper.js
<template>
<div>
...
<wrapper #onAdd="add" #onRemove="remove"></wrapper>
...
</div>
</template>
<script>
export default {
inheritAttrs: false,
methods: {
onAdd() {
console.log('on add in using-wrapper.js');
},
onRemove() {
console.log('on remove in using-wrapper.js');
}
}
}
</script>
Well on "wrapper" you will need to create 3 methods that listen and get triggered on "third-party-component".
<template>
<div class="my-wrapper">
<third-party-component #onAdd="wrapperAdd" #onRemove="wrapperRemove" #onUpdate="wrapperUpdate" />
</div>
<script>
export default {
methods:{
wrapperAdd(){
this.$emit("onAdd",{obj that will get to the parent});
}
}
}
I made only 1 method because the other 2 are similar.

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>