2

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

Dally
  • 1,281
  • 4
  • 18
  • 37

3 Answers3

2

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/vue@2.7.14/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>
Tolbxela
  • 4,767
  • 3
  • 21
  • 42
  • Hi, I'm sorry but I'm failing to see what the problem is here. I've done this in Vue2 using the options API without a problem. I fetch the data once, assign it to 2 different variables so that I have an original version. The textfield v-model is connected to transaction.reference so surely only the transaction object should change? Isn't what I'm doing essentially is creating 2 different variables that contain the same data but aren't linked in any way? – Dally Feb 13 '23 at 17:49
  • What version of Vue do you use now with `Composition API`, Vue 2 or Vue 3? – Tolbxela Feb 13 '23 at 17:55
  • 1
    "Isn't what I'm doing essentially is creating 2 different variables that contain the same data but aren't linked in any way" - No. Please read Docs about Objects in JavaScript. – Tolbxela Feb 13 '23 at 18:20
  • 1
    Hi Tolbxela, I don't understand why the Reactivity in Vue 3 behaves in this manner. Nikola's suggestion of using the spread operator works but I don't actually understand the logic here. I'm making an API call and storing the response in 2 different variables. Are you saying that the ```transaction``` and ```pbl``` objects are both just pointers to ```res.data```? If this is the case, is using the spread operator the solution for this problem moving forward? – Dally Feb 13 '23 at 19:55
  • 1
    Exactly: transaction and `pbl` objects are both just pointers to `res.data` Object, since Objects in JavaScript are passed by Reference (a pointer to the Object) – Tolbxela Feb 13 '23 at 20:02
2

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>
Nikola Pavicevic
  • 21,952
  • 9
  • 25
  • 46
  • So it seems by using the spread operator, it solves the problem but I'm still not understanding why this problem exists. I'm making an API call and storing the response in 2 different variables. Surely when I'm storing the API response it is done via value and not reference? – Dally Feb 13 '23 at 19:50
1

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
Tolbxela
  • 4,767
  • 3
  • 21
  • 42