vue.jsvue-propsvue-reactivity

Prop in Vue component is not reactive


Either I have a basic misunderstanding of Vue.js or I have found a bug. I am able to reproduce this bug (?) in the minimal example below.

The component App.vue has a reactive message variable, which is passed down to the child Child.vue as a prop. This message can be changed with a button. This works fine. However, when you alert the message in the Child, you will always get the first message - it is not updated at all. It seems that the message is only updated in the markup, not in the JavaScript. I am really confused by this behavior.

App.vue

<script setup>
import { ref, computed } from "vue";
import Child from "./components/Child.vue";

let index = ref(0);
let messages = ["hi", "there", "this", "is", "strange"];

let message = computed(() => messages[index.value]);

function change_message() {
    index.value++;
    if (index.value === messages.length) {
        index.value = 0;
    }
}
</script>

<template>
    <Child :message="message"></Child>
    <div>
        <button @click="change_message">Change message</button>
    </div>
</template>

Child.vue

<script setup>
const { message } = defineProps(["message"]);
function alert_message() {
    window.alert(message);
}
</script>

<template>
    <h1>
        {{ message }}
    </h1>
    <button @click="alert_message">Alert message</button>
</template>

Demo

demo of the bug

There is a workaround: Do not destructure the props in the Child. So write const props = defineProps(["message"]); and then use window.alert(props.message);. However, in a real-world Vue application, this will result in quite bloated code. I do not want to carry the props. around all the time.


Solution

  • When you destructure the props, you are loosing reactivity, For your case one way could be use toRefs:

    Child.vue

    <script setup>
    import { toRefs } from "vue";
    
    const props = defineProps(["message"]);
    const { message } = toRefs(props);
    
    function alert_message() {
      window.alert(message.value);
    }
    </script>
    
    
    <template>
      <h1>
        {{ message }}
      </h1>
      <button @click="alert_message()">Alert message</button>
    </template>
    

    https://codesandbox.io/s/nameless-water-jwhw90?file=/src/components/Child.vue

    Anyway is an standard to use props.<your_prop> when it's required, even for in a real-world Vue apps