'Props gotten directly from vue 3 setup are not reactive

I am writing an application in vuejs and i want to pass prop to a child component but I am getting this error:

Getting a value from the props in root scope of setup() will cause the value to lose reactivity

Parent component

<template>
  <div>
      <course-list :courseId = "id"  />
  </div>
</template>
import {useRoute} from 'vue-router';
import { ref, onMounted, reactive} from 'vue';
export default defineComponent({
  components: { NavBar, CourseList, CourseContent },
  props:{
    courseId: String
  },
  setup(){
      const route = useRoute()

      const id = route.params.id
      console.log("the course id is", id);

    return{
      id
    }
  }
}

Child Component

export default defineComponent({
  components: { CourseTopic },
  props: {
      courseId: {
        type: String
      }
    },
  setup(props) {

    const trainingCourseId = props.courseId;

    return { courses, trainingCourseId };
  },
});

How do I solve this problem?



Solution 1:[1]

Use toRefs() on props to keep the reactivity on the prop:

import { toRefs } from 'vue'

export default {
  setup(props) {
    const { courseId: trainingCourseId } = toRefs(props)
    return { trainingCourseId }
  }
}

Or toRef():

import { toRef } from 'vue'

export default {
  setup(props) {
    const trainingCourseId = toRef(props, 'courseId')
    return { trainingCourseId }
  }
}

Solution 2:[2]

const trainingCourseId = props.courseId;

All it says is that your trainingCourseId is not reactive.

I guess the code you posted is just for demonstration, because in this specific case you can actually just use courseId (in your template) directly, and it will be reactive.

However the bigger question remains - why courseId is reactive but trainingCourseId is not? Doesn't the doc say that the props is a reactive object? How exactly is reactivity destroyed here?

Just to be clear, reassigning a property to a local variable does not always remove all of the reactivity (yes, some reactivity will always be lost, but depending on the shape of the property, the loss of reactivity might not be that obvious initially).

Vue 3 uses Proxy to achieve reactivity. The idea is that for the given raw data object: { courseId: "something" }, Vue creates another Proxy object that looks just like the given data object but with all the property getters and setters intercepted. The reactivity comes from these intercepted getters and setters, and hence is associated with the object owning the getters and setters, not the property itself.

In other words:

const raw = { courseId: "something" };
const rxData = reactive(raw);

What is reactive is the rxData, not the courseId, meaning any access to the rxData properties (any property, doesnt have to be courseId) is reactive. However, when you do const trainingCourseId = rxData.courseId, trainingCourseId is not a Proxy, it is just a string (retrieved out of a proxy).

This is a little bit different when courseId is not a simple string but an object:

const raw = { courseId: { name: "something" } };
const rxData = reactive(raw);
const trainingCourseId = rxData.courseId;

When building the reactive proxy, Vue recursively converts the original raw data object. Hence rxData.courseId is actually also a Proxy in this case. If you change the courseId name by rxData.courseId.name = "something else", the change will be reflected in trainingCourseId. However, if you reassign rxData.courseId by rxData.courseId = { name: "something else" }, this reassignment will not be visible to trainingCourseId.

The toRef and toRefs method mentioned in the other answer will help you get rid of all these funny behaviors. But if you are interested you can check out this question about vue3 reactivity

Solution 3:[3]

Aditionally to toRef() and toRefs(), one can compute props to get'em reactive.

Example using typescript setup:

import { computed } from 'vue'

const props = defineProps({
  prop1: {
    type: String
  },
  prop2: {
   type: Boolean,
   default: () => false
  }
})

const prop1 = computed(() => props.prop1)
const prop2 = computed(() => props.prop2)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 tony19
Solution 2 Xinchao
Solution 3 egidiocs