How to make a class getter reactive in Vue.js 3 with Composition API?
Asked Answered
S

3

5

I'm trying to make a class instance property reactive to display the error message if authentication fails.

userModel.ts

export class User {
  private error: string;

  set errorMessage(errorMessage: string) {
    this.error = errorMessage;
  }
  get errorMessage() {
    return this.error
  }

// other stuff...
}

LoginView.vue

import { User } from "./models/userModel";
import { ref } from 'vue';

const user = new User();
const errorMessage = ref(user.errorMessage); // <--- This is not working.
const errorMessage = ref(user.error); // <--- Neither this even if user.error property is public.

No warnings or exceptions, reactive value just remains empty. What I am missing?

Shamefaced answered 29/7, 2022 at 7:23 Comment(1)
You should probably avoid setting only some properties of an object to be reactive and instead have the whole object be reactive. const user = reactive(new User); but if you insist on making this work, I figure the error must be elsewhere, since I reacreated your example and it works just fine.Turgid
T
7

Here, you are creating new reactive variables, with the initial value being the one in your class. Your class and your ref are unrelated.

If you want your class to have reactive properties, you have to instanciate them this way:

export class User {
  private error: Ref<string> = ref('');

  public errorMessage: WritableComputedRef<string> = computed({
    get() {
      return this.error.value
    },
    set(errorMessage: string) {
      this.error.value = errorMessage;
    },
  })
}

Side note: I'd recommend to use the "composables" approach instead of using classes, because class instances are not compatible with SSR.

export function useUser () {
  const user: User = reactive({})
  // other stuff...

  const error: Ref<string> = ref('');

  const errorMessage: WritableComputedRef<string> = computed({
    get() {
      return this.error.value
    },
    set(errorMessage: string) {
      this.error.value = errorMessage;
    },
  })

  return { user, errorMessage }
}
Titan answered 29/7, 2022 at 8:5 Comment(5)
Thank you for the answer, Kapcash. Have you defined your own 'Reactive' type here? I can't find it in the framework's API.Shamefaced
Sorry, I didn't remember the correct type. I updated the code with the correct types. reactive returns a UnwrapNestedRefs which is just a fancy type to say it's a User without any Ref inside.Titan
Notice that the use of composition API inside a class is a workaround that may cause more problems than it solves. This not just about reactive types. If User instance becomes reactive, e.g as a property of reactive object like a store, , user ref property is unwrapped. There will be this.error, not this.error.value, and errorMessage implementation becomes invalidDruse
The computed way of using class property really helped. I have been trying all kind of tricks to have internal ref but nothing seemed to work.Unknown
I needed a WritableComputedRef instead of a ComputedRefSino
D
5

Accessing a primitive property by value like ref(user.error) can't be reactive, no matter it's a getter or not, in this case it doesn't differ from ref(undefined).

For own enumerable property it could be:

let user = reactive(new User());
let { error } = toRefs(user);

It won't handle property accessors, they need to be explicitly accessed with computed property:

let errorMessage = computed({
  get: () => user.errorMessage,
  set: value => { user.errorMessage = value }
});

Whether it's a getter or regular property doesn't affect how a computed works because an accessor is defined on class prototype and accesses reactive this.error (this can be a problem when non-reactive this is bound in a constructor, like is shown here and here).

Druse answered 29/7, 2022 at 8:9 Comment(0)
U
0

I did do something quite similar, but it didn't work

@vueuse provides a createsharedcomposable that allows reactivity across the entire application, using class structure won't be beneficial

Unrest answered 26/7, 2024 at 5:50 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.