491

In the VueJs 2.0 docs I can't find any hooks that would listen on props changes.

Does VueJs have such hooks like onPropsUpdated() or similar?

Update

As @wostex suggested, I tried to watch my property but nothing changed. Then I realized that I've got a special case:

<template>
    <child :my-prop="myProp"></child>
</template>

<script>
   export default {
      props: ['myProp']
   }
</script>

I am passing myProp that the parent component receives to the child component. Then the watch: {myProp: ...} is not working.

tony19
  • 125,647
  • 18
  • 229
  • 307
Amio.io
  • 20,677
  • 15
  • 82
  • 117
  • 1
    Possible duplicate of [VueJS 2.0 - Can't hook a component on props update](https://stackoverflow.com/questions/44584078/vuejs-2-0-cant-hook-a-component-on-props-update) – Bert Jun 16 '17 at 09:04
  • @wostex, here is the [CodePen](https://codepen.io/Nobo/pen/ZyBVvo?editors=1010#0). The bad thing is that in the pen it's working... – Amio.io Jun 16 '17 at 09:43

21 Answers21

600

You can watch props to execute some code upon props changes:

new Vue({
  el: '#app',
  data: {
    text: 'Hello'
  },
  components: {
    'child' : {
      template: `<p>{{ myprop }}</p>`,
      props: ['myprop'],
      watch: { 
        myprop: function(newVal, oldVal) { // watch it
          console.log('Prop changed: ', newVal, ' | was: ', oldVal)
        }
      }
    }
  }
});
<script src="https://unpkg.com/vue@2/dist/vue.min.js"></script>

<div id="app">
  <child :myprop="text"></child>
  <button @click="text = 'Another text'">Change text</button>
</div>
zcoop98
  • 2,590
  • 1
  • 18
  • 31
Egor Stambakio
  • 17,836
  • 5
  • 33
  • 35
  • 1
    I know watch is discouraged because it's usually better to use a computed property, but are there any performance problems with using watch? – SethWhite Mar 14 '18 at 20:54
  • 1
    @SethWhite From what I understand of how [reactivity is handled in Vue](https://vuejs.org/v2/guide/reactivity.html) using the observer pattern, watchers are not necessarily less efficient than computed or other reactive data. In cases where a property's value depends on many values, a computed prop will run a single function vs a separate watch callback for each of the values the result depends on. I think in most common use-cases, a computed property would have the desired result. – plong0 Oct 10 '18 at 20:56
  • 1
    For anyone looking to do this using a class (e.g. in typescript), check out [this answer](https://stackoverflow.com/a/51892249/2966288) – AvahW Aug 27 '20 at 17:22
  • 4
    Shouldn't what ever uses props re-render if the prop changes vs using a watcher? – Robert Feb 13 '21 at 17:37
  • I saw a notatioin without key in watch section when watching a props. Only a function. Is there any docs on this notaton (vue 2)? – Ivan Sudos Jul 19 '21 at 22:31
  • @sonrad10 There are cases where custom code needs to be ran. For instance, a chartjs chart needs to be upated manually when the dataset has changed to reflect newer updates. – TheRealChx101 Sep 08 '22 at 02:03
170

Have you tried this ?

watch: {
  myProp: {
    // the callback will be called immediately after the start of the observation
    immediate: true, 
    handler (val, oldVal) {
      // do your stuff
    }
  }
}

https://v2.vuejs.org/v2/api/#watch

tony19
  • 125,647
  • 18
  • 229
  • 307
BearInBox
  • 1,821
  • 1
  • 10
  • 9
  • 7
    This works for me but it'd be helpful if there was an explanation for why it works. – Andy Preston Jun 29 '21 at 12:02
  • @AndyPreston correct me if I am wrong with terminologies, but this works, especially for prop changes upon component re-render, its because of the immediate property. otherwise, watch will only observe changes AFTER component is rendered, forgetting about the initial change that parent component passed in DURING the re-render. – 0x5929 May 19 '23 at 23:08
91

In my case I needed a solution where anytime any props would change, I needed to parse my data again. I was tired of making separated watcher for all my props, so I used this:

  watch: {
    $props: {
      handler() {
        this.parseData();
      },
      deep: true,
      immediate: true,
    },
  },

Key point to take away from this example is to use deep: true so it not only watches $props but also it's nested values like e.g. props.myProp

You can learn more about this extended watch options here: https://v2.vuejs.org/v2/api/#vm-watch

tony19
  • 125,647
  • 18
  • 229
  • 307
JoeSchr
  • 1,067
  • 1
  • 9
  • 14
27

You need to understand, the component hierarchy you are having and how you are passing props, definitely your case is special and not usually encountered by the devs.

Parent Component -myProp-> Child Component -myProp-> Grandchild Component

If myProp is changed in parent component it will be reflected in the child component too.

And if myProp is changed in child component it will be reflected in grandchild component too.

So if myProp is changed in parent component then it will be reflected in grandchild component. (so far so good).

Therefore down the hierarchy you don't have to do anything props will be inherently reactive.

Now talking about going up in hierarchy

If myProp is changed in grandChild component it won't be reflected in the child component. You have to use .sync modifier in child and emit event from the grandChild component.

If myProp is changed in child component it won't be reflected in the parent component. You have to use .sync modifier in parent and emit event from the child component.

If myProp is changed in grandChild component it won't be reflected in the parent component (obviously). You have to use .sync modifier child and emit event from the grandchild component, then watch the prop in child component and emit an event on change which is being listened by parent component using .sync modifier.

Let's see some code to avoid confusion

Parent.vue

<template>
    <div>
    <child :myProp.sync="myProp"></child>
    <input v-model="myProp"/>
    <p>{{myProp}}</p>
</div>
</template>

<script>

    import child from './Child.vue'

    export default{
        data(){
            return{
                myProp:"hello"
            }
        },
        components:{
            child
        }
    }
</script>

<style scoped>
</style>

Child.vue

<template>
<div>   <grand-child :myProp.sync="myProp"></grand-child>
    <p>{{myProp}}</p>
</div>

</template>

<script>
    import grandChild from './Grandchild.vue'

    export default{
        components:{
            grandChild
        },
        props:['myProp'],
        watch:{
            'myProp'(){
                this.$emit('update:myProp',this.myProp)

            }
        }
    }
</script>

<style>

</style>

Grandchild.vue

<template>
    <div><p>{{myProp}}</p>
    <input v-model="myProp" @input="changed"/>
    </div>
</template>

<script>
    export default{
        props:['myProp'],
        methods:{
            changed(event){
                this.$emit('update:myProp',this.myProp)
            }
        }
    }
</script>

<style>

</style>

But after this you wont help notice the screaming warnings of vue saying

'Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders.'

Again as I mentioned earlier most of the devs don't encounter this issue, because it's an anti pattern. That's why you get this warning.

But in order to solve your issue (according to your design). I believe you have to do the above work around(hack to be honest). I still recommend you should rethink your design and make is less prone to bugs.

I hope it helps.

Ankit Kumar Ojha
  • 2,296
  • 2
  • 22
  • 30
15

for two way binding you have to use .sync modifier

<child :myprop.sync="text"></child>

more details...

and you have to use watch property in child component to listen and update any changes

  props: ['myprop'],
  watch: { 
    myprop: function(newVal, oldVal) { // watch it
      console.log('Prop changed: ', newVal, ' | was: ', oldVal)
    }
  }
Ismail H
  • 4,226
  • 2
  • 38
  • 61
Mahamudul Hasan
  • 2,745
  • 2
  • 17
  • 26
9

Not sure if you have resolved it (and if I understand correctly), but here's my idea:

If parent receives myProp, and you want it to pass to child and watch it in child, then parent has to have copy of myProp (not reference).

Try this:

new Vue({
  el: '#app',
  data: {
    text: 'Hello'
  },
  components: {
    'parent': {
      props: ['myProp'],
      computed: {
        myInnerProp() { return myProp.clone(); } //eg. myProp.slice() for array
      }
    },
    'child': {
      props: ['myProp'],
      watch: {
        myProp(val, oldval) { now val will differ from oldval }
      }
    }
  }
}

and in html:

<child :my-prop="myInnerProp"></child>

actually you have to be very careful when working on complex collections in such situations (passing down few times)

m.cichacz
  • 749
  • 9
  • 21
6

I work with a computed property like:

    items:{
        get(){
            return this.resources;
        },
        set(v){
            this.$emit("update:resources", v)
        }
    },

Resources is in this case a property:

props: [ 'resources' ]
Wouter Schoofs
  • 966
  • 9
  • 13
4

Props and v-model handling. How to pass values from parent to child and child to parent.

Watch is not required! Also mutating props in Vue is an anti-pattern, so you should never change the prop value in the child or component. Use $emit to change the value and Vue will work as expected always.

/* COMPONENT - CHILD */
Vue.component('props-change-component', {
  props: ['value', 'atext', 'anumber'],
  mounted() {
    var _this = this
    
    this.$emit("update:anumber", 6)
    
    setTimeout(function () {
      // Update the parent binded variable to 'atext'
      _this.$emit("update:atext", "4s delay update from child!!")
    }, 4000)
    
    setTimeout(function () {
      // Update the parent binded v-model value
      _this.$emit("input", "6s delay update v-model value from child!!")
    }, 6000)
  },
  template: '<div> \
    v-model value: {{ value }} <br> \
    atext: {{ atext }} <br> \
    anumber: {{ anumber }} <br> \
    </div>'
})

/* MAIN - PARENT */
const app = new Vue({
  el: '#app',
  data() {
    return {
      myvalue: 7,
      mynumber: 99,
      mytext: "My own text",
    }
  },
  mounted() {
    var _this = this
    
    // Update our variable directly
    setTimeout(function () {
      _this.mytext = "2s delay update mytext from parent!!"
    }, 2000)
  },
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">

  <props-change-component
    v-model='myvalue'
    :atext.sync='mytext'
    :anumber.sync='mynumber'>
    
  </props-change-component>
  
</div>
Heroselohim
  • 1,241
  • 1
  • 18
  • 23
  • 1
    the line: this.$emit("anumber", 6) should be: this.$emit("update:anumber", 6) ; other than that, thank you for this very useful answer; I especially value how you show watching is not required! :) – keykey76 Dec 09 '21 at 12:10
  • What happens in Vue3, where there is no global emit? – ritwick_ _D Dec 13 '22 at 09:54
4

My below answer is applicable if someone using Vue 2 with composition API. So setup function will be

setup: (props: any) => {
  watch(() => (props.myProp), (updatedProps: any) => {
    // you will get the latest props into updatedProp
  })
}

However, you will need to import the watch function from the composition API.

Kiran Mahale
  • 143
  • 1
  • 1
  • 8
3

For me this is a polite solution to get one specific prop(s) changes and create logic with it

I would use props and variables computed properties to create logic after to receive the changes

export default {
name: 'getObjectDetail',
filters: {},
components: {},
props: {
  objectDetail: { // <--- we could access to this value with this.objectDetail
    type: Object,
    required: true
  }
},
computed: {
  _objectDetail: {
    let value = false
    // ...
    // if || do || while -- whatever logic
    // insert validation logic with this.objectDetail (prop value)
    value = true
    // ...
    return value 
  }
}

So, we could use _objectDetail on html render

<span>
  {{ _objectDetail }}
</span>

or in some method:

literallySomeMethod: function() {
   if (this._objectDetail) {
   ....
   }
}
Manuel Alanis
  • 346
  • 1
  • 8
3

I think that in most cases Vue updates component's DOM on a prop change.

If this is your case then you can use beforeUpdate() or updated() hooks (docs) to watch props.

You can do it if you're only interested in newVal and don't need oldVal

new Vue({
  el: '#app',
  data: {
    text: ''
  },
  components: {
    'child': {
      template: `<p>{{ myprop }}</p>`,
      props: ['myprop'],
      beforeUpdate() {
        console.log(this.myprop)
      },
      updated() {
        console.log(this.myprop)
      }
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <child :myprop="text"></child>
  <input v-model="text" placeholder="Type here to view prop changes" style="width:20em">
</div>
Ilyich
  • 4,966
  • 3
  • 39
  • 27
1

I use props and variables computed properties if I need create logic after to receive the changes

export default {
name: 'getObjectDetail',
filters: {},
components: {},
props: {
    objectDetail: {
      type: Object,
      required: true
    }
},
computed: {
    _objectDetail: {
        let value = false
        ...

        if (someValidation)
        ...
    }
}

Manuel Alanis
  • 346
  • 1
  • 8
1

Interesting observation for some use cases.

If you watch a data item from your store via a prop and you change the data item multiple times in the same store mutation it will not be watched.

However if you separate the data item changes into multiple calls of the same mutation it will be watched.

  • This code will NOT trigger the watcher:

    // Somewhere in the code:
    this.$store.commit('changeWatchedDataItem');
    
    // In the 'changeWatchedDataItem' mutation:
    state.dataItem = false;
    state.dataItem = true;
    
  • This code WILL trigger the watcher at each mutation:

    // Somewhere in the code:
    this.$store.commit('changeWatchedDataItem', true);
    this.$store.commit('changeWatchedDataItem', false);
    
    // In the 'changeWatchedDataItem' mutation:
    changeWatchedDataItem(state, newValue) {
        state.dataItem = newValue;
    }
    
Valentine Shi
  • 6,604
  • 4
  • 46
  • 46
1

By default props in the component are reactive and you can setup watch on the props within the component which will help you to modify functionality according to your need. Here is a simple code snippet to show how it works

setup(props) {
 watch(
  () => props.propName,
  (oldValue, newValue) => {
    //Here you can add you functionality 
    // as described in the name you will get old and new value of watched property
  },
  { deep: true },
  { immediate: true } //if you need to run callback as soon as prop changes
)
}

Hope this helps you to get the result you want out of this. Have a great day.

kshitij
  • 642
  • 9
  • 17
0

You can use the watch mode to detect changes:

Do everything at atomic level. So first check if watch method itself is getting called or not by consoling something inside. Once it has been established that watch is getting called, smash it out with your business logic.

watch: { 
  myProp: function() {
   console.log('Prop changed')
  }
}
Rahul Sharma
  • 225
  • 1
  • 4
  • 15
0

@JoeSchr has an answer. Here is another way to do if you don't want deep: true

 mounted() {
    this.yourMethod();
    // re-render any time a prop changes
    Object.keys(this.$options.props).forEach(key => {
      this.$watch(key, this.yourMethod);
    });
  },
Alvin Smith
  • 547
  • 5
  • 9
0

If your prop myProp has nested items, those nested won't be reactive, so you'll need to use something like lodash deepClone :

<child :myProp.sync="_.deepClone(myProp)"></child>

That's it, no need for watchers or anything else.

Max S.
  • 1,383
  • 14
  • 25
0

in my case i didnt get any information and cant retrive the info. make sure use try and catch in body of watchs

my case

setup(props, { emit, attrs, slots, expose }) {
...
....
..... 
}
.....
  watch: {
    isModalActive: function () {
      console.log('action:: ', this.props.isModalActive) // here cause error undefined and no error information on my inspect element dunno why
    }
  },

so when i tried to console for other

  watch: {
    isModalActive: function () {
      console.log('action:: ') // this work and printed
      console.log('action:: ', this.props.isModalActive)
    }
  },
Yogi Arif Widodo
  • 563
  • 6
  • 22
-1

The watch function should place in Child component. Not parent.

Cong Nguyen
  • 3,199
  • 1
  • 23
  • 22
-2

props will be change if you add

<template>
<child :my-prop="myProp"/>
</template>

<script>
export default {
   props: 'myProp'
}
</script>
user229044
  • 232,980
  • 40
  • 330
  • 338
-3

if myProp is an object, it may not be changed in usual. so, watch will never be triggered. the reason of why myProp not be changed is that you just set some keys of myProp in most cases. the myProp itself is still the one. try to watch props of myProp, like "myProp.a",it should work.