Search code examples
javascriptvue.jscomponentsvue-componentref

Vue2 use $ref to load data from child component and put into parent component's data


In Vue2 I'm trying to access child components' data and then put into parent component's data without triggering an event. In the following example I want to save count:20 into parent component, please tell me if there's any mistake, thanks!

Child Component

<template>
  <div></div>
</template> 
<script>
export default {
  data() {
    return {
      count: 20,
    };
  },
};
</script>

Parent Component

<template>
  <div>
    <child ref="child1"></child>
    {{count}}
</div>
</template> 

<script> import child from './child.vue' 
export default { 
  components: {
    child
  }, 
  data() {
    return{
      count:this.$refs.child1.count
    }
  },
} 
</script>

warn message in VScode

Property 'count' does not exist on type 'Vue | Element | Vue[] | Element[]'. Property 'count' does not exist on type 'Vue'.

warn message in browser

[Vue warn]: Error in data(): "TypeError: undefined is not an object (evaluating 'this.$refs.child1')"


Solution

  • Let me preface with I would recommend using the Vue framework as intended. So passing data from a child to the parent should be done with $emit or using a vuex store for centralized state management.

    With that out of the way you will want to wait until the parent component is mounted to set the count data attribute.

    Child

    <template>
      <div></div>
    </template> 
    <script>
    export default {
      data() {
        return {
          count: 20,
        };
      },
    };
    </script>
    

    Parent

    <template>
      <div>
        <child ref="child1"></child>
        {{ count }}
      </div>
    </template>
    
    <script>
    import Child from "./components/Child";
    
    export default {
      components: {
        Child
      },
      data() {
        return{
          count: 0
        }
      },
      mounted () {
        this.count = this.$refs.child1.count
      }
    };
    </script>
    

    This will work, however it WILL NOT BE reactive. This can all be greatly simplified AND made reactive with the following changes:

    Child

    <template>
      <div></div>
    </template> 
    <script>
    export default {
      data() {
        return {
          count: 20,
        };
      },
      watch: {
        count (currentValue) {
          this.$emit('update', currentValue);
        }
      },
      beforeMount () {
        this.$emit('update', this.count)
      }
    };
    </script>
    

    Parent

    <template>
      <div>
        <child @update="count = $event"></child>
        {{ count }}
      </div>
    </template>
    
    <script>
    import Child from "./components/Child";
    
    export default {
      components: {
        Child
      },
      data() {
        return{
          count: 0
        }
      }
    };
    </script>
    

    Quick link to show a working example: https://codesandbox.io/s/interesting-kalam-et0b3?file=/src/App.vue