Search code examples
vue.jsvuejs3vue-componentvuetify.js

How to manage snackbar wrapper state


I want to create a simple wrapper for v-snackbar but there is a problem that I cannot use a prop value as argument inv-model. I am trying to pass a boolean as a prop to the wrapper and to change the inner state but the problem is that it works only once because the code to open the snackbar sets the ref to true and the watch function does not recognize that as a change. What is the proper way to create a wrapper for a snackbar in vue?

        <v-btn @click="showSnackbar = true">
            Open Snackbar
        </v-btn>
        <Snackbar :options="{ showSnackbar: showSnackbar }"> Warning </Snackbar>

wrapper code:

<template>
    <v-snackbar v-model="isShowing">
        <slot></slot>
        <template #actions>
            <v-btn variant="text" @click="isShowing = false">
                Close
            </v-btn>
        </template>
    </v-snackbar>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';

const props = withDefaults(defineProps<{
    options: SnackbarOptions
}>(), {

})
const isShowing = ref(false);
watch(() => props.options.showSnackbar, () => {
    isShowing.value = true
})
</script>

Solution

  • After the snackbar is opened the first time showSnackbar in your parent component is never set back to false. If it's never set back to false, then it can never be "watched" as being set to true again. Since there's no two-way binding between the components this is expected.

    You could easily just use v-model with the showSnackbar property, or if you want to only do this using the options prop and no v-model, you can use a computed property instead of a watcher, but you'll still need to at least add an event listener:

    Parent

    <Snackbar
      :options="{ showSnackbar: showSnackbar }"
      @close="showSnackbar = false"
    >
      Warning
    </Snackbar>
    

    Wrapper

    <script setup>
      import { computed } from 'vue'
      const props = defineProps(['options'])
      const emit = defineEmits(['close'])
    
      const showSnackbar = computed({
        get() {
          return props.options.showSnackbar
        },
        set() {
          emit('close')
        },
      })
    </script>
    
    <template>
      <v-snackbar v-model="showSnackbar">
        <slot></slot>
        <template #actions>
          <v-btn variant="text" @click="showSnackbar = false"> Close </v-btn>
        </template>
      </v-snackbar>
    </template>
    

    demo

    Solution with v-model

    Parent

    <Snackbar v-model="showSnackbar" :options="otherOptions"> Warning </Snackbar>
    

    Wrapper

    <script setup>
    const props = defineProps(['options'])
    const showSnackbar = defineModel()
    </script>
    
    <template>
      <v-snackbar v-model="showSnackbar">
        <slot></slot>
        <template #actions>
          <v-btn variant="text" @click="showSnackbar = false"> Close </v-btn>
        </template>
      </v-snackbar>
    </template>