Search code examples
vue.jsvuejs3quasar-frameworkvue-composition-apivue-script-setup

How to control the visibility of a child component from the parent using a prop?


I'm trying to control the visibility of a child component from the parent using a prop.

My child component has a visible prop. To avoid mutating the visible prop I assigned it to a local isVisible ref, and I'm using that to conditionally show and hide a form.

But when I try to hide the form using a button with @click="isVisible = false" nothing happens, and I get a console error saying Set operation on key "visible" failed: target is readonly.

It's also confusing why the error messsage is referring to the visible prop because I am using the local isVisible variable instead.

This is the ChildForm.vue (child component):

<template>
  <q-form v-if="isVisible">
    <q-input v-model="modelValue" />
    <q-btn label="Hide The Form" @click="isVisible = false" />
  </q-form>
</template>

<script setup lang="ts">
import { ref, toRef } from 'vue';
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
    default: false,
  },
});
const isVisible = toRef(props, 'visible');
const modelValue = ref('');
</script>

This is the ParentPage.vue (parent component):

<template>
  <child-form :visible="showForm" />
  <q-btn label="Show the Form" @click="showForm = true" />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import ChildForm from 'src/components/ChildForm.vue';
const showForm = ref(false);
</script>

Solution

  • toRef() retains the reactive connection to the original source, so modifying isVisible effectively modifies the original prop, which is supposed to be readonly, leading to the warning you observed.

    But I think you're actually trying to keep the child's visible prop in sync with the parent's showForm prop, such that updates in the child are automatically reflected in the parent. v-model is the tool for that problem.

    In the parent, bind showForm to v-model:visible:

    <child-form v-model:visible="showForm">
    

    In the child, emit an update:visible event with the desired value whenever you want to update the parent:

    <template>
      <q-form v-if="visible">
        <q-input v-model="modelValue" />                  👇
        <q-btn label="Hide The Form" @click="$emit('update:visible', false)" />
      </q-form>
    </template>
    
    <script setup lang="ts">
    import { ref } from 'vue'
    const props = defineProps({
      visible: {
        type: Boolean,
        required: true,
        default: false,
      },
    })
    const modelValue = ref('')
    </script>
    

    demo