Watch child properties from parent component in vue 3

Viewed 2999

I'm wondering how I can observe child properties from the parent component in Vue 3 using the composition api (I'm working with the experimental script setup).

<template>//Child.vue
  <button 
    @click="count++" 
    v-text="'count: ' + count" 
  />
</template>

<script setup>
import { ref } from 'vue'

let count = ref(1)
</script>
<template>//Parent.vue
  <p>parent: {{ count }}</p> //update me with a watcher
  <Child ref="childComponent" />
</template>


<script setup>
import Child from './Child.vue'
import { onMounted, ref, watch } from 'vue'

const childComponent = ref(null)
let count = ref(0)

onMounted(() => {
  watch(childComponent.count.value, (newVal, oldVal) => {
    console.log(newVal, oldVal);
    count.value = newVal
  })
}) 
</script>

I want to understand how I can watch changes in the child component from the parent component. My not working solution is inspired by the Vue.js 2 Solution asked here. So I don't want to emit the count.value but just watch for changes.

Thank you!

2 Answers

The Bindings inside of <script setup> are "closed by default" as you can see here.

However you can explicitly expose certain refs. For that you use useContext().expose({ ref1,ref2,ref3 })

So simply add this to Child.vue:

import { useContext } from 'vue'

useContext().expose({ count })

and then change the Watcher in Parent.vue to:

watch(() => childComponent.value.count, (newVal, oldVal) => {
    console.log(newVal, oldVal);
    count.value = newVal
  })

And it works!

I've answered the Vue 2 Solution and it works perfectly fine with Vue 3 if you don't use script setup or explicitly expose properties.

Here is the working code.

Child.vue

<template>
  <button @click="count++">Increase</button>
</template>

<script>
import { ref } from 'vue';

export default {
  setup() {
    return {
      count: ref(0),
    };
  },
};
</script>

Parent.vue

<template>
  <div id="app">
    <Child ref="childComponent" />
  </div>
</template>

<script>
import { ref, onMounted, watch } from 'vue';
import Child from './components/Child.vue';

export default {
  components: {
    Child,
  },
  setup() {
    const childComponent = ref(null);

    onMounted(() => {
      watch(
        () => childComponent.value.count,
        (newVal) => {
          console.log({ newVal }) // runs when count changes
        }
      );
    });

    return { childComponent };
  },
};
</script>

See it live on StackBlitz


Please keep reading

In the Vue 2 Solution I have described that we should use the mounted hook in order to be able to watch child properties. In Vue 3 however, that's no longer an issue/limitation since the watcher has additional options like flush: 'post' which ensures that the element has been rendered.

Make sure to read the Docs: Watching Template Refs


When using script setup, the public instance of the component it's not exposed and thus, the Vue 2 solutions will not work. In order to make it work you need to explicitly expose properties:

With script setup

import { ref } from 'vue' const a = 1 const b = ref(2) defineExpose({ a, b })

With Options API

export default {
  expose: ['publicData', 'publicMethod'],
  data() {
    return {
      publicData: 'foo',
      privateData: 'bar'
    }
  },
  methods: {
    publicMethod() {
      /* ... */
    },
    privateMethod() {
      /* ... */
    }
  }
}

Note: If you define expose in Options API then only those properties will be exposed. The rest will not be accessible from template refs or $parent chains.

Related