Dally
Dally

Reputation: 1427

Why is reactive variable changing when it is not assigned to v-model

I'm fairly new to the composition API but it's been somewhat straight forward so far.

I'm making an Axios call that is returning some data;

const transaction = ref({});
let pbl = reactive({});

const getPayByLinkTransaction = () => {
    axios({
        method: "get",
        url: "pay-by-link",
        params: {
            merchantUuid: import.meta.env.VITE_MERCHANT_UUID,
            uuid: route.params.uuid,
        },
    })
        .then((res) => {
            transaction.value = res.data;
            pbl = res.data;            
        })
        .catch((e) => {
            console.log(e);
        });
}

getPayByLinkTransaction();

Then I have the following textfield:

<v-text-field v-model="transaction.reference" variant="solo" class="mb-1"
:rules="config?.fields?.reference?.required ? required : []"></v-text-field>

PBL - {{ pbl.reference }} <br>
Transaction - {{ transaction.reference }}

The reference key contains John Doe to start off with.

What's strange is that when I start typing into the textfield, it's changing reference in both the transaction and pbl object.

As the v-model is attached to transaction.reference, should it not only change the variable in the transaction object?

Why is the pbl object changing too?

What I'm after are two objects where one contains the original data and the other contains modified data if the user were to amend any details.

enter image description here

Upvotes: 2

Views: 498

Answers (3)

Tolbxela
Tolbxela

Reputation: 5183

The main problem here is the understanding of how the Objects are threated in JavaScript.

const obj1 = { data : { value: 1 } }
const obj2 = obj1.data;
console.log( obj1.data === obj2 ); // --> true

Here is another very simple playground to demonstrate it.

const obj1 = { data : { value: 1 } }
console.log(`obj1.data.value = ${obj1.data.value}`);
// --> obj1.data.value = 1

const obj2 = obj1.data;
obj2.value++;
console.log(`obj1.data.value = ${obj1.data.value}`);
// --> obj1.data.value = 2

console.log(`obj1.data == obj2 : ${obj1.data == obj2}`);
// --> true 

// even the JavaScript object destructuring assignment 
// doesn't break the connection to original data Object
const { data } = obj1;
data.value++;
console.log(`obj1.data.value = ${obj1.data.value}`);
// --> obj1.data.value = 3

// this works, since 'value' is passed by value, not reference
const obj3 = { value: obj1.data.value }
obj3.value++;
console.log(`obj3.value = ${obj3.value}`);
// --> obj3.value = 4
console.log(`obj1.data.value = ${obj1.data.value}`);
// --> obj1.data.value = 3

// the other way is using the Spread Syntax
const obj4 = {...obj1.data}
obj4.value++;
console.log(`obj4.value = ${obj4.value}`);
// --> obj4.value = 4
console.log(`obj1.data.value = ${obj1.data.value}`);
// --> obj1.data.value = 3

Upvotes: 1

Tolbxela
Tolbxela

Reputation: 5183

I was not able to reproduce the problem using Composition API in both Vue 2 and Vue 2.

So, here is my assumption about what's going on.

You are assigning the same object from res.data to transaction and to pbl. Since it is the same object, the change of reference over transaction.reference changes also pbl.reference

Here is the simple Vue 2 playground using Options API to understand the problem.

const App = { 
  el: '#app',
  data() {
    return {
      myObj: { id: 1, counter: 0 },
      myObjCopy: {}
    }
  },
  methods: {
     replaceObj() {
      let obj = { id: this.myObj.id + 1, counter: 0 };
      this.myObj = obj;
      this.myObjCopy = obj;
    },
    plus() {
      this.myObj.counter++; 
    }
  }
}
const app = new Vue(App);
#app { line-height: 2; }
[v-cloak] { display: none; }
<div id="app">
<button type="button" @click="replaceObj()">Replace My Object</button><hr/>
My Object Id: {{myObj.id}}<br/>
Counter: {{myObj.counter}}
<button type="button" @click="plus()">+1</button><br/>
<hr/>
My Object Copy Id: {{myObjCopy.id}}<br/>
Counter: {{myObjCopy.counter}}
</div>
<script type="text/javascript" src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>

The interesting this is, the reactivity in Vue 3 behaves different.

// true in 2.7, false in 3.x
reactive(foo) === foo;

The Vue 3 reactive() function create a Proxy object. Check the Behavior Differences from Vue 3

const { createApp, ref, reactive } = Vue;
const App = { 
  setup() {    
    let obj = { id: 1, counter: 0 };
    const myObj = ref(obj);
    let myObjCopy = reactive(obj);    
    const plus = () => {
      myObj.value.counter++; 
    }    
    const replaceObj = () => {
      let obj = { id: myObj.value.id + 1, counter: 0 };
      myObj.value = obj;
      myObjCopy = obj;
    }
    return { myObj, myObjCopy, plus, replaceObj}
  }
}
const app = createApp(App)
app.mount('#app')
<div id="app">
<button type="button" @click="replaceObj()">Replace My Object</button><br/><br/>
My Object Id: {{myObj.id}}<br/>
Counter: {{myObj.counter}}
<button type="button" @click="plus()">+1</button><br/>
<hr/>
My Object Copy Id: {{myObjCopy.id}}<br/>
Counter: {{myObjCopy.counter}}
</div>
<script type="text/javascript" src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>

Upvotes: 2

Nikola Pavicevic
Nikola Pavicevic

Reputation: 23490

Try to copy object by value, not reference:

const {ref, reactive} = Vue
const app = Vue.createApp({
  setup() {
    const data1 = ref({})
    let data2 = reactive({})
    const byReference = {ref: 'reference'}
    data1.value = byReference
    data2 = byReference
    
    const byValue = {ref: 'value'}
    const data3 = ref({})
    let data4 = reactive({})
    data3.value = byValue
    data4 = {...byValue}
    
    return {
      data1, data2, data3, data4
    };
  },
})
app.mount('#demo')
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id="demo">
  <input v-model="data1.ref" />
  {{data1.ref}}
  {{data2.ref}}
  <input v-model="data3.ref" />
  {{data3.ref}}
  {{data4.ref}}
</div>

Upvotes: 2

Related Questions