How does the plain constant continue to be effective in the Composition API of Vue 3?

In accordance with the official documentation, it is necessary to use ref or reactive when defining the "data" variable in the new composition api setup method. This allows Vue to track any changes made to that particular variable.

While experimenting with reactivity, I observed a peculiar behavior. Consider the following component as a reference:

App.vue

<template>
  <div id="app">
    <p>{{ awesomeState.txt }}</p>
    <p>{{ myConst }}</p>
    <button @click="operate">Test it</button>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import awesomeStore from "@/AwesomeStore";

export default defineComponent({
  setup() {
    const awesomeState = awesomeStore.getState();
    const myConst = "I'm a const"; // To maintain reactivity, should be declared as `const myConst = ref('...');`
    return {
      awesomeState,
      myConst
    };
  },
  name: "App",
  methods: {
    operate() {
      this.myConst = 'Why does this update?';
      awesomeStore.changeText("yess!");
    }
  }
});
</script>

AwesomeStore.ts

import { reactive, readonly, watch } from "vue";

export abstract class Store<T extends Record<string, any>> {
  protected state: T;

  constructor() {
    const data = this.data();
    this.state = reactive(data) as T;
    watch(() => this.state, (value) => {
      console.log(value); debugger;
    }, {deep: true});
  }

  protected abstract data(): T;

  public getState(): T {
    return readonly(this.state) as T;
  }
}

interface Test extends Object {
  txt: string;
}

class AwesomeClass extends Store<Test> {
  protected data(): Test {
    return {
      txt: "init"
    };
  }
  public changeText(msg: string) {
    this.state.txt = msg;
  }
}

export default new AwesomeClass();

Upon clicking the button, the myConst property is updated. One thing to note is that methods should not be defined outside the setup() scope. The reason behind this unexpected behavior eludes me.

Any suggestions on why this might be happening?

Thank you for your assistance,

UPDATE

I failed to mention that this behavior only occurs if I try to modify the constant at the same time as invoking the awesomeStore.changeText method:

operate() {
      this.myConst = 'Why does this update?';
      //awesomeStore.changeText("yess!"); <-- If commented out, myConst remains unchanged.
    }

Answer №1

Two key factors contribute to the success of this approach

1) Using a method instead of passing a function

You have the option to utilize either the standard Vue API such as data:, methods:, etc... or the composition API, for example, setup()

Your implementation incorporates both approaches. While it is permissible to do so, it's important to note that a method passed through setup versus methods will exhibit different behavior. With a method, you can access the this object where myConst is mutable.

  methods: {
    operate() {
      this.myConst = 'Why does this update?'; // <=== ability to modify variable in `this`
      awesomeStore.changeText("Yes!");
    }
  }

If you were to follow this alternative example below, attempting to update myConst would not yield results

setup(){
  const myConst = "I'm a constant"; // This should be declared as a ref - `const myConst = ref('...');`
  // ...other code...
  return {
    awesomeState,
    myConst,
    operate: ()=> {
      this.myConst = 'Why does this update?'; // <=== won't work
      myConst = 'Why does this update?';      // <=== also won't work
      awesomeStore.changeText("Yes!");
    }
  }
}

2) Updating ref and static variables

An additional consideration is updating both the static value and the reference. Modifying the reference will trigger re-rendering, indicating that the static value has been changed at that point

By removing the store-related updates, the observed modifications should cease

  methods: {
    operate() {
      this.myConst = 'Why does this update?';
      // awesomeStore.changeText("Yes!"); // <== no change in reference, hence no update
    }
  }

Feel free to experiment with my hypothesis using the provided playground below

const {
  createApp,
  ref,
} = Vue

createApp({
  el: '#app',
  setup() {
    let mystatic = 1
    const myref = ref(1)
    return {
      mystatic,
      myref,
      updateStatic: () => {
        mystatic++
      },
      updateRef: () => {
        myref.value++
      }
    }

  },
  methods: {
    updateStaticMethod() {
      this.mystatic++
    }
  }
}).mount('#app')
<script src="https://unpkg.com/vue@next/dist/vue.global.js"></script>

<div id="app">
  <li>Static: {{ mystatic }}</li>
  <li>Ref: {{ myref }}</li>
  <li><button @click="updateStatic">Increment Static</button> remains unchanged.</li>
  <li><button @click="updateStaticMethod">Increment Static (method)</button> only updates upon external trigger</li>
  <li><button @click="updateRef">Increment Reference</button> updates reference value (and triggers redraw)</li>
  <li><button @click="()=>{updateStaticMethod();updateRef();}">Increment Both</button> updates both values</li>
</div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Retrieve selected button from loop typescript

https://i.stack.imgur.com/DS9jQ.jpgI have an array of individuals that I am looping through. It's a bit difficult for me to explain, but I want something like this: <div *ngFor="let person of persons"> {{person.name}} {{person.surname}} <but ...

Integrating external components with Vue 3 and Vite

It seems like I'm encountering difficulties with loading an external UMD component in Vue. I am attempting to achieve something similar to what is discussed in Vue 3 external component/plugin loading in runtime This method worked well for me with V ...

Sorting Columns in PrimeVue DataTable by Date and Time

I am using a PrimeVue DataTable () with the following structure: <DataTable :rows = "5" :value = "apiItems" > <Column v-for="data in columns" :field="data.field" :header="data.header&q ...

The missing properties in the TS Type are as follows:

Currently working with Angular 7.x.x and TypeScript version 3.2.4. I have defined two TypeScript interfaces where one extends the other: Main interface: export interface Result { var1: string; var2: number; var3: boolean; } The second ...

Exploring TypeScript's Index Types: Introduction to Enforcing Constraints on T[K]

In typescript, we can utilize index types to perform operations on specific properties: interface Sample { title: string; creationDate: Date; } function manipulateProperty<T, K extends keyof T>(obj: T, propName: K): void { obj[propName] ...

What is the most efficient way to convert a JSON object into a URL search parameter using as few characters as possible?

Challenge: On my web app, users can adjust settings to create or edit generative artworks and then share their creations via a shortened link. The objective is to store all the data needed to replicate the artwork in the URL within 280 characters. Initia ...

Quasar - q-scroll-area : Allow child to fill the available space

Within my q-page, I have a q-card that consists of a q-scroll-area. There are two scenarios to consider: If the child is smaller than the parent (horizontally, vertically, or both), I want the child to expand and fill all available space. If the child is ...

How to handle form-data in NestJS Guards?

I've been trying to access form-data in my NestJS Guards, but I'm experiencing some difficulties. Despite following the tutorial provided here, I am unable to see the request body for my form-data input within the Guard itself. However, once I ac ...

get a duplicate of an object

Is this the proper method for creating a duplicate of an object? class ObjectWrapper { private _obj; /*** * Copy object passed as argument to this._obj */ constructor (_obj: Object) { this._obj = _obj; } /** Return copy of this._ ...

Error: The property 'process' cannot be read because it is not defined

Seeking help with a code issue Any advice on best practices would be greatly appreciated. Thank you! An error has occurred: TypeError: Cannot read property 'process' of undefined myComponent.ts ProcessInfo: any | false; showSaveItems = ...

Using Vuex in a .NET Core application with VueJs

I am currently working on an ASP .NET Core project with a Vue front end that was created using the following commands: dotnet new — install Microsoft.AspNetCore.SpaTemplates::* dotnet new vue Everything builds and runs smoothly, but I encountered ...

Tips for updating the icon based on the active or inactive status in ag-grid within an angular application

HTML* <ng-template #actionButtons let-data="data"> <div class="cell-actions"> <a href="javascript:;" (click)="assign()"> <i nz-icon nzType="user-add" nzTheme= ...

The functionality of ngModel seems to be malfunctioning when used within select options that are generated inside

I'm currently working on dynamically adding options to an HTML select element within a for loop. I am using [(ngModel)] to get the selected option, but initially no option is pre-selected. Here's a snippet of the code: <table align="center"& ...

Adding the gear icon, often representing settings, to a video.js player allows for easy customization of

I am currently working on an older vue2 project that utilizes video.js, specifically either v5 or v6 (not entirely certain). The player functions properly, but I have noticed that the gear icon for settings is missing from the control bar. I believe that t ...

Guide on how to bundle a TypeScript project into a single JavaScript file for smooth browser integration

I am currently working on a small project that requires me to write a JavaScript SDK. My initial plan was to create a TypeScript project and compile it into a single JavaScript file, allowing users of my SDK to easily inject that file into their web pages. ...

TypeScript - Variable is inferred to have type 'any' in certain locations where its type cannot be accurately determined

I'm attempting to generate an MD5 hash from the content of an uploaded file. I am trying to set a global declaration to be used within functions, but I encounter an error when trying to utilize it: The error message states - Variable 'hasher&apos ...

a search-enabled dropdown menu within an Angular input field

I can't seem to get the input box to display in my code below. Can you help me figure out why? component.html <form [formGroup]="formGroup" (validSubmit)="onSubmit()"> <div class="form-group mb-3"> ...

Error Message: Unknown Custom Element <b-nav-brand> in Bootstrap-Vue.JS

I'm currently developing a Vue application and I've integrated the bootstrap-vue navbar component from the official example into my project. However, when I run my application, Vue keeps throwing a warning in the console about an unknown custom e ...

A step-by-step guide on programmatically installing typings

Is it feasible to install typings from my build.js file using a similar method? When installing my bower dependencies, I typically utilize the following code: var bower = require("bower"); bower.commands.install(); Can this same approach be applied to i ...

Put Jest to the test by testing the appendFileSync function

I am currently working on creating a test for appendfilesync function. When using a logger, I noticed that one line of code is not covered in my tests. Below is the code snippet I am referring to (please note that I am using tslog for logging purposes): ex ...