VueJs - Binding bidirectionnaly parent and child data - javascript

I'm pretty new in Vuejs. I read the documentation and a few articles on the internet but I still have a question about components binds.
I read that you can pass data from the parent to the child easily with the props and that you can call the parent from the child with a sort of callback with emit.
I was wondering if there is a way to bind directly the props from both components. One change of the child's data would induce a change in the parents one, and vice-versa, without having to manage callback functions.

What you might want to consider is Vuex which is used to manage state.
As their homepage says: "It serves as a centralised store [of state] for all the components in an application..."
Any number of components can watch for changes to the state and a component can 'react' whenever there are any changes to that state — meaning that a component automatically changes according to the state.
The most important thing to remember is that state is immutable, it can be copied, changed and a new version published but there is only ever one source of truth - this helps in managing the state of your application because there is only ever one source of truth for the state of the application.
This might be a good place to get some info: freecodecamp article
Hope that helps.

Template
<div id="app">
<h1>2-way props</h1>
<p>Test of mechanics required for independent 2-way props in Vue 2</p>
<p>Updated example using a mixin.</p>
<section>
<label>1. App:</label>
<pre>data: {{ $data }} </pre>
<ui-input :value.sync="value"></ui-input>
</section>
</div>
<template id="input">
<section>
<label>2. {{ name }}:</label>
<input v-model="val" #change="onInput">
<pre>data: {{ $data }}</pre>
</section>
</template>
Script
const Field = {
props:['value'],
data () {
return { val:this.value }
},
watch: {
value (value) {
this.val = value
}
},
methods: {
onInput() {
this.$emit('update:value', this.val)
}
},
}
Vue.component('ui-input', {
template:'#input',
mixins: [Field],
data () {
return { name: 'Component' }
},
})
new Vue({
data () {
return {
value: 'foo'
}
}
}).$mount('#app')
Please refer the fiddle.
https://jsfiddle.net/RiddhiParekh/d60p75Lr/

Related

Vue Watcher not working on component created with Vue.extend

I have a Parent component with a select input which is bound through v-model to a variable in data.
Besides, I create child components dynamically using Vue.extend, which i pass the propsData which also includes the value of the select.
This components have a watcher for the prop that is related to the select input.
When i create the component it receives the props succesfully, The problem comes when I update the value of the select input that doesn't trigger the watcher on the child component.
I've been looking for similar situations but have not found something that helps me solve this problem, i don't know why it doesn't trigger the watcher on the child component when the select input changes.
Any help would be very preciated.
Here i create the component dynamically:
let PresupuestoFormularioVue = Vue.extend(PresupuestoFormulario)
let instance = new PresupuestoFormularioVue({
propsData: {
//The prop related to select input
seguro: this.seguro,
}
})
instance.$mount()
this.$refs.formularioContenedor.appendChild(instance.$el)
And this is the watcher in the component which isn't working:
watch:{
seguro:{
handler: function( newVal ){
console.log(newVal)
},
},
},
It's not the watch that doesn't work. It's the bindings. You're assigning the current value of this.seguro, not the reactive object itself. However, a new Vue() can add this binding for you.
As a sidenote, whether PresupuestoFormulario is a Vue.extend() doesn't matter. It can be any valid VueConstructor: a Vue.extend(), Vue.component() or a valid SFC (with name and template): export default {...}.
Here's how to do it:
methods: {
addPresupuestoFormulario() {
const div = document.createElement('div');
this.$el.appendChild(div);
new Vue({
components: { PresupuestoFormulario },
render: h => h("presupuesto-formulario", {
props: {
seguro: this.seguro
}
})
}).$mount(div)
}
}
The <div> initially appended to the parent will get replaced upon mounting with the actual template of PresupuestoFormulario and the bindings will be set, exactly as if you had <presupuesto-formulario :seguro="seguro" /> in the parent template from the start.
The really cool part about it is that the parent component doesn't need to have PresupuestoFormulario declared in its components.
Here's a working example:
const Test = Vue.component('test', {
template: `<div>message: {{message}}</div>`,
props: ['message'],
watch: {
message: console.log
}
})
new Vue({
el: '#app',
data: () => ({
msg: "¯\\_(ツ)_/¯"
}),
methods: {
addComponent() {
const div = document.createElement("div");
this.$el.appendChild(div);
new Vue({
components: {
Test
},
render: h => h("test", {
props: {
message: this.msg
}
})
}).$mount(div);
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2"></script>
<div id="app">
<input v-model="msg">
<button #click="addComponent">Add dynamic child</button>
</div>
A separate note, about using this.$el.appendChild(). While this works when you're using a root Vue instance (a so-called Vue app), it will likely fail when using a normal Vue component, as Vue2 components are limited to having only 1 root element.
It's probably a good idea to have an empty container (e.g: <div ref="container" />) in the parent, and use this.$refs.container.appendChild() instead.
All of props that you want check in watcher, should be a function. If you want read more about this go to vue document codegrepper.
watch: {
// whenever seguro changes, this function will run
seguro: function (newValue, oldValue) {
console.log(newValue,oldValue)
}
}

trigger function in child.vue after clicking button in parent.vue

I'm working with BootstrapVue.
I have a method in my parent.vue where I pass a value (this.propsIndex) to my child.vue.
Now I want to use this value each time it will be clicked in a method of my child.vue - but how can I trigger my function and make it working?
Thank You very much!
If it's possible I want to avoid using watch
my parent.vue
<template>
<div v-for="(id, index) in inputs" :key="index">
<b-button #click="deleteViaIndex(index)">Delete</b-button>
<child :indexProps="indexProps" />
</div>
<div>
<b-button #click="addInput()">Add Input</b-button>
</div>
</template>
<script>
methods: {
deleteViaIndex(index) {
this.propsIndex= index;
},
addInput() {
this.inputs.push({})
},
},
data() {
return {
inputs: [{}],
propsIndex: '',
}
}
</script>
my child.vue (script)
props: ["propsIndex"],
methods: {
deleteViaParentIndex() {
//HERE I WANT TO USE IT EVERY TIME IT WILL BE CLICKED IN MY PARENT.VUE
//BUT FOR NOW IT'S NOT DOING ANYTHING WHEN I CONSOLE.LOG(this.propsIndex)
}
}
Aside from the naming mismatch mentioned by Marcin, you can access a child component from a parent component using a ref in the template:
<child ref="childrenComponents" :props-index="propsIndex" />
Since you have multiple of the child components inside a v-for, this makes the childrenComponents in $refs an array of components. To call deleteViaParentIndex on all of them, you need to iterate through them:
deleteViaIndex(index) {
this.propsIndex = index;
this.$refs.childrenComponents.forEach(child => child.deleteViaParentIndex());
}
There's one more optimization you can make.
Since you're using propsIndex only to pass an index that the child component uses, and you already have the index as a param in deleteViaIndex, you can simply pass that to the child as a param during the deleteViaParentIndex function call, thus removing the need for the propsIndex data altogether:
in parent.vue:
deleteViaIndex(index) {
this.$refs.childrenComponents.forEach(child => child.deleteViaParentIndex(index));
}
in child.vue:
deleteViaParentIndex(index) {
// operations using index
}
Looks like a naming mismatch. In a child component, you have a prop propsIndex, yet in a parent template you are passing indexProps.
When passing props, you have to remember, that prop name is always in the first part, and the value you are passing should go next. https://v2.vuejs.org/v2/guide/components-props.html#Passing-Static-or-Dynamic-Props
Furthermore, since HTML attribute names are case-insensitive, you should pass a prop this way:
<child :props-index="indexProps" />

How to get the props value and use in lifehooks cycle of Vue JS

I'm searching a way to get the props value through some lifehooks like mounted or updated and trying to save the value with my v-model with some string. But I can't get it.
Though I tried :value on the input element with the props value and some string and I was able to get it, but it seems like I can't access it without v-model, as I researched v-model and :value can't be together.
The purpose is to get the value(with from props and some string) of a input tags.
Parent Component
<invite :user_token="user_token"/>
Child Component
export default {
props: ['user_token'],
data() {
return {
link: ''
}
},
mounted() {
console.log(this.user_token);
this.link = `"http://localhost/johndoe-vue/public/#/invite${this.user_token}"`;
},
updated() {
console.log(this.user_token);
this.link = `"http://localhost/johndoe-vue/public/#/invite${this.user_token}"`;
}
}
Welcome to SO Nigel!
Are you looking for something like this, perhaps?
ParentComponent.vue
<template>
<div id="wrapper">
<invite :userToken="userToken"></invite>
</div>
</div>
<script>
import Invite from "#/Invite.vue";
export default {
components: {
Invite
},
data() {
return {
userToken: "fooBar",
};
}
}
</script>
ChildComponent.vue
<template>
<div id="wrapper">
<p v-if="inviteLink != ''">{{ inviteLink }}</p>
</div>
</template>
export default {
props: {
userToken: {
type: String,
}
},
data() {
return {
inviteLink: ""
}
},
created() {
if(this.userToken != "") {
this.inviteLink == "/your-link-here/"+this.userToken;
}
}
}
Also, you should check out the Vue.js Style Guide. They've marked multi-word component names as essential. Your Invite component should be renamed to BaseInvite or something like that.
Have you tried to $emit this.link
Props is accessible through the $props property of your component. You would reference it like: this.$props.[property name]. $props is called an instance property; there are many of them and they are each accessible this way. See https://v2.vuejs.org/v2/api/#Instance-Properties
Keep in mind that the Vue life cycle methods are somewhat inconsistent. Which instance properties are accessible depends on the method (ie: you can't reference $el in created(...).
https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram

Why doesn't v-show displayed when I go back to the previous step?

I have an SAP in VUE that consists of a form in 4 steps.
When submit in the first step button and redirect to second step, if I return to first step the last component is stopped showing, in spite of being shown before changing the component.
this is my template
<input
type="text"
id="animalName"
ref="animalName"
v-model="animalFormInfo.name"
#keypress="onChangeName($event)"
#keyup="onChangeName($event)"
/>
<div class="loader-spinner" v-if="loading">
<app-loader>
</div>
</div>
</div>
</div>
<div v-show="isNameCompleted">
<div class="from-group">
<h1 class="title">
For
<span>{{this.formInfo.name}}</span>, you have:
</h1>
<div class="list-services">
<div
class="column-service"
v-for="estimation in filteredEstimation"
v-bind:key="estimation.name"
>
<div>{{estimation.name}}</div>
<div>{{estimation.description}}</div>
<div>{{estimation.price.toString().replace(".", ",")}}</div>
</div>
</div>
</div>
</div>
<div class="button-line" v-show="isLoaderFinished"></div>
<div class="forward" v-show="isLoaderFinished">
<div class="forward-button">
<button
ref="submit"
v-bind:disabled="!isFormCompleted"
type="submit"
#click="navigateToCreate"
>
SEND INFO
</button>
</div>
</div>
and below the data and the method in which I control the load of the components
data() {
return {
step: 1,
isFormCompleted: false,
isBreedSelected: false,
isNameCompleted: false,
loading: false,
isLoaderFinished: false,
myTimeout: 0
};
},
methods:{
onChangeName(event) {
if (this.myTimeout >= 0 && event) {
clearTimeout(this.myTimeout);
this.loading = true;
this.isNameCompleted = false;
}
this.myTimeout = window.setTimeout(() => {
this.isNameCompleted = true;
const typedAnimalName = event.target._value;
this.capitalizeAnimalName(typedAnimalName);
this.loading = false;
this.isLoaderFinished = true;
this.isFormCompleted = true;
setTimeout(() => (this.$refs.scrolled_3.focus()), 10);
}, 2200);
},
capitalizeAnimalName(typedAnimalName) {
var splitAnimalName = typedAnimalName.toLowerCase().split(' ');
for (var i = 0; i < splitAnimalName.length; i++) {
splitAnimalName[i] = splitAnimalName[i].charAt(0).toUpperCase() + splitAnimalName[i].substring(1);
}
return this.animalName = splitAnimalName.join(' ');
},
},
Once I reach step 2 and return to step 1 only the info is shown until the input with id "animalName".
What am I doing wrong?
Is there any reason why when loading the first component it follows the logic and shows them and once I go to the next one they disappear?
thank you all for your time and help in advance
The problem is that the state of the component is not persistent.
There are multiple solutions for this.
1. Manage state globally with Vuex
This is probably the default, or most common way of handling this kind of situation. Instead of storing the data in the component, you use this library to manage the state. Most likely you can just implement vuex in your project and not read on. It's a solid choice with too many upsides to list.
2. Encapsulate form state in the parent component.
Instead of managing the state within the step component, you can create the state for the relevant data inside the parent object.
example:
<Step2
v-if="step === 2"
:formData="formData"
:updateFormData="updateFormData"
:nextStep="() => gotoStep(3)"
></Step2>
The idea is that the Step2 component would have the data passed in through formData prop. And any time that data changes, updateFormData function is called with the new data. You could even simplify it by utilizing the v-model.
3. DIY store
If you're using Vue js 2 (>= 2.6, < 3), you can do this with observable
Vue 2.6+
create a store.js file
import Vue from "vue";
export const store = Vue.observable({
step: 1,
isFormCompleted: false,
isBreedSelected: false,
isNameCompleted: false,
loading: false,
isLoaderFinished: false,
});
Then you can call your store.js from any component, and if you assign it to this in the beforeCreate hook, you can use it just like data (this.store.isFormCompleted in script, store.isFormCompleted in template)
import { store} from 'store.js';
//...
beforeCreate(){
this.store = store;
}
if you want to skip the extra store., you could even destructure the store object.
Vue 3
Similar to the other one there, but use a reactive() (or multiple ref()). Let me know if you want me to expand on this.
If each step is its own component, you can use the keep-alive component to cache the components and their state, so that when you switch between them, their state is retained. I would personally prefer that over using Vuex in this case, since it's much simpler.
<keep-alive>
<step-1-component v-if="step === 1"></step-1-component>
<step-2-component v-if="step === 2"></step-2-component>
<step-3-component v-if="step === 3"></step-3-component>
</keep-alive>
See: https://v2.vuejs.org/v2/api/#keep-alive
It is so because the component is re-rendering when you go back to the first page, so all the default states will take effect. Try saving your states with Vuex so that all components will be controlled by the state from the store.

Can you force Vue.js to reload/re-render?

Just a quick question.
Can you force Vue.js to reload/recalculate everything? If so, how?
Try this magic spell:
vm.$forceUpdate();
//or in file components
this.$forceUpdate();
No need to create any hanging vars :)
Update: I found this solution when I only started working with VueJS. However further exploration proved this approach as a crutch. As far as I recall, in a while I got rid of it simply putting all the properties that failed to refresh automatically (mostly nested ones) into computed properties.
More info here: https://v2.vuejs.org/v2/guide/computed.html
This seems like a pretty clean solution from matthiasg on this issue:
you can also use :key="someVariableUnderYourControl" and change the key when you want to component to be completely rebuilt
For my use case, I was feeding a Vuex getter into a component as a prop. Somehow Vuex would fetch the data but the reactivity wouldn't reliably kick in to rerender the component. In my case, setting the component key to some attribute on the prop guaranteed a refresh when the getters (and the attribute) finally resolved.
Please read this
http://michaelnthiessen.com/force-re-render/
The horrible way: reloading the entire page The terrible way:
using the v-if hack The better way: using Vue’s built-in
forceUpdate method The best way: key-changing on your
component
<template>
<component-to-re-render :key="componentKey" />
</template>
<script>
export default {
data() {
return {
componentKey: 0,
};
},
methods: {
forceRerender() {
this.componentKey += 1;
}
}
}
</script>
I also use watch: in some situations.
Try to use this.$router.go(0); to manually reload the current page.
Why?
...do you need to force an update?
Perhaps you are not exploring Vue at its best:
To have Vue automatically react to value changes, the objects must be initially declared in data. Or, if not, they must be added using Vue.set().
See comments in the demo below. Or open the same demo in a JSFiddle here.
new Vue({
el: '#app',
data: {
person: {
name: 'Edson'
}
},
methods: {
changeName() {
// because name is declared in data, whenever it
// changes, Vue automatically updates
this.person.name = 'Arantes';
},
changeNickname() {
// because nickname is NOT declared in data, when it
// changes, Vue will NOT automatically update
this.person.nickname = 'Pele';
// although if anything else updates, this change will be seen
},
changeNicknameProperly() {
// when some property is NOT INITIALLY declared in data, the correct way
// to add it is using Vue.set or this.$set
Vue.set(this.person, 'address', '123th avenue.');
// subsequent changes can be done directly now and it will auto update
this.person.address = '345th avenue.';
}
}
})
/* CSS just for the demo, it is not necessary at all! */
span:nth-of-type(1),button:nth-of-type(1) { color: blue; }
span:nth-of-type(2),button:nth-of-type(2) { color: red; }
span:nth-of-type(3),button:nth-of-type(3) { color: green; }
span { font-family: monospace }
<script src="https://unpkg.com/vue"></script>
<div id="app">
<span>person.name: {{ person.name }}</span><br>
<span>person.nickname: {{ person.nickname }}</span><br>
<span>person.address: {{ person.address }}</span><br>
<br>
<button #click="changeName">this.person.name = 'Arantes'; (will auto update because `name` was in `data`)</button><br>
<button #click="changeNickname">this.person.nickname = 'Pele'; (will NOT auto update because `nickname` was not in `data`)</button><br>
<button #click="changeNicknameProperly">Vue.set(this.person, 'address', '99th st.'); (WILL auto update even though `address` was not in `data`)</button>
<br>
<br>
For more info, read the comments in the code. Or check the docs on <b>Reactivity</b> (link below).
</div>
To master this part of Vue, check the Official Docs on Reactivity - Change Detection Caveats. It is a must read!
Use vm.$set('varName', value).
Look for details into Change_Detection_Caveats
Sure .. you can simply use the key attribute to force re-render (recreation) at any time.
<mycomponent :key="somevalueunderyourcontrol"></mycomponent>
See https://jsfiddle.net/mgoetzke/epqy1xgf/ for an example
It was also discussed here: https://github.com/vuejs/Discussion/issues/356#issuecomment-336060875
<my-component :key="uniqueKey" />
along with it use this.$set(obj,'obj_key',value)
and update uniqueKey for every update in object (obj) value
for every update this.uniqueKey++
it worked for me this way
So there's two way you can do this,
You can use $forceUpdate() inside your method handler i.e
<your-component #click="reRender()"></your-component>
<script>
export default {
methods: {
reRender(){
this.$forceUpdate()
}
}
}
</script>
You can give a :key attribute to your component and increment when want to rerender
<your-component :key="index" #click="reRender()"></your-component>
<script>
export default {
data() {
return {
index: 1
}
},
methods: {
reRender(){
this.index++
}
}
}
</script>
In order to reload/re-render/refresh component, stop the long codings. There is a Vue.JS way of doing that.
Just use :key attribute.
For example:
<my-component :key="unique" />
I am using that one in BS Vue Table Slot. Telling that I will do something for this component so make it unique.
Using v-if directive
<div v-if="trulyvalue">
<component-here />
</div>
So simply by changing the value of trulyvalue from false to true will cause the component between the div to rerender again
Dec, 2021 Update:
You can force-reload components by adding :key="$route.fullPath".
For Child Component:
<Child :key="$route.fullPath" />
For router-view tag:
<router-view :key="$route.fullPath" />
However, :key="$route.fullPath" only can force-reload the components of the different route but not the components of the same route. To be able to force-reload the components of the same route as well, we need to add "value" with an array to :key="$route.fullPath" and change "value". So it becomes :key="[$route.fullPath, value]" and we need to change "value".
*We can assign Array to :key=.
<template>
<Child
:key="[$route.fullPath, value]" // Can assign "Array" to ":key="
#childReload="reload" // Call #click="$emit('childReload')" in
/> // Child Component to increment the value.
</template>
OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR
<template>
<router-view
:key="[$route.fullPath, value]" // Can assign "Array" to ":key="
#routerViewReload="reload" // Call #click="$emit('routerViewReload')"
/> // in Child Component to increment the value.
</template>
<script>
export default {
name: "Parent", components: { Child, },
data() {
return {
value: 0,
};
},
methods: {
reload() {
this.value++;
}
}
}
</script>
However, to keep using both "$route.fullPath" and "value" causes some error sometimes so only when some event like Click happens, we use both "$route.fullPath" and "value". Except when some event like Click happens, we always need to use only "$route.fullPath".
This is the final code:
<template>
<Child
:key="state ? $route.fullPath : [$route.fullPath, value]"
#childReload="reload" // Call #click="$emit('childReload')" in
/> // Child Component to increment the value.
</template>
OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR OR
<template>
<router-view
:key="state ? $route.fullPath : [$route.fullPath, value]"
#routerViewReload="reload" // Call #click="$emit('routerViewReload')" in
/> // Child Component to increment the value.
</template>
<script>
export default {
name: "Parent", components: { Child, },
data() {
return {
state: true,
value: 0,
};
},
methods: {
reload() {
this.state = false;
this.value++;
this.$nextTick(() => this.state = true);
}
}
}
</script>
Unfortunately, there are no simple ways to force-reload components properly in Vue. That's the problem of Vue for now.
This has worked for me.
created() {
EventBus.$on('refresh-stores-list', () => {
this.$forceUpdate();
});
},
The other component fires the refresh-stores-list event will cause the current component to rerender
<router-view :key="$route.params.slug" />
Just use key with your any params its auto reload children..
I found a way. It's a bit hacky but works.
vm.$set("x",0);
vm.$delete("x");
Where vm is your view-model object, and x is a non-existent variable.
Vue.js will complain about this in the console log but it does trigger a refresh for all data. Tested with version 1.0.26.
Worked for me
data () {
return {
userInfo: null,
offers: null
}
},
watch: {
'$route'() {
this.userInfo = null
this.offers = null
this.loadUserInfo()
this.getUserOffers()
}
}
The approach of adding :key to the vue-router lib's router-view component cause's fickers for me, so I went vue-router's 'in-component guard' to intercept updates and refresh the entire page accordingly when there's an update of the path on the same route (as $router.go, $router.push, $router.replace weren't any help). The only caveat with this is that we're for a second breaking the singe-page app behavior, by refreshing the page.
beforeRouteUpdate(to, from, next) {
if (to.path !== from.path) {
window.location = to.path;
}
},
Except page reload method(flickering), none of them works for me (:key didn't worked).
and I found this method from old vue.js forum which is works for me:
https://github.com/vuejs/Discussion/issues/356
<template>
<div v-if="show">
<button #click="rerender">re-render</button>
</div>
</template>
<script>
export default {
data(){
return {show:true}
},
methods:{
rerender(){
this.show = false
this.$nextTick(() => {
this.show = true
console.log('re-render start')
this.$nextTick(() => {
console.log('re-render end')
})
})
}
}
}
</script>
Add this code:
this.$forceUpdate()
For anyone still looking around, there's a package for this now.
https://github.com/gabrielmbmb/vuex-multi-tab-state
All I had to do was install it and add it to my plugins in main.ts (as it shows on that page) and it did exactly what I wanted.
If your URL changes as well when if the component is loaded you can just use it in the :key attribute. This works especially well if you use it on the router-view tag directly. And this commes with the added benedit of the key being a value that is actually tied to the content of the page instead of just some random number.
<router-view :key="this.$route.path"></router-view>
If you are using router-view or Vue Router, you can directly use the key feature
<router-view :key="$route.path"></router-view>
This will tell the router view to re-render the page every time the path is changed.

Categories

Resources