0
votes

i've a reactive object, on the save function i call toRaw in order to remove de object reactivity, but, when i change the reactive object props, also the group object is changing.... How???


const newGroup = reactive<Group>({ id: undefined, name: '', enabled: false })

const handleSave = () => {
      const group = toRaw(newGroup)
      persist(group).then(() => {
        newGroup.id = undefined
        newGroup.name = 'demo'
        console.log(isReactive(group)) //say false but the group.name value is demo
      })
     
    }

destructuring the reactive obj as const group = { ...newGroup } looks like it works, still no understand why toRaw is not working.

EDIT:

the whole problem comes from a sense of lack of control when dealing with reactive objects for example:

the following cumputed object retrieves a list from the store which is then drawn in the template as rows of a table at which click the selected element is changed of state. in the function I am forced to deconstruct records to avoid that the modification trigger the change of enabled before it is persisted Is there a better way? Should I make readonly groups?


//in setup()
const groups = computed <Group []> (() => getters ['registry / groups'])

const toggle = (record: Group) => {
    const group = { ...record } 
    group.enabled = !group.enabled
    persist(group)
}

//in template
  <a-menu-item @ click = "toggle (record)">
3
what does persist() do? I don't quite understand why it is a problem that the reactive object is updating.Daniel
persist will dispatch a store function that make an axios call to edit the row, i can't change the row status before the axios call that could fail for exampleclaud.io
so you want the store (or portion of it) to be immutable while the persist() async method is updating remote data?Daniel
yes, in this example as you can see, i've used destructuring, but don't know if this is the wayclaud.io
for the sake of what you're doing, {...record} is the way to go (instead of toRaw(record), because to raw returns the watched object, whereas destructuring just creates the copy for you. You could also do {...toRaw(record)}, but I don't think that will have any additional benefit.Daniel

3 Answers

3
votes

You have a misunderstanding of reactivity here. A reactive object connects the data with DOM, not between javascript objects, i.e. when a property of the reactive object is updated, the DOM is automatically updated.

toRaw returns the raw, original object of a reactive or readonly proxy, i.e group and newGroup are still the same object, the difference is that group would not trigger a DOM update as newGroup does.


Example, UI is updated when you update newGroup, but updating group won't trigger a DOM change. However the two objects still have the same reference, which is why group.name and newGroup.name are always the same.

Vue.createApp({
  setup() {
    const newGroup = Vue.reactive({ id: undefined, name: '', enabled: false })
    const group = Vue.toRaw(newGroup)
    return {
      newGroup,
      group
    }
  }
}).mount('#app')
<script src="https://unpkg.com/vue@next/dist/vue.global.prod.js"></script>

<div id="app">
  <button @click="newGroup.name = 'reactive update'">reactive update</button>
  newGroup Name: {{newGroup.name}}<br/>
  <button @click="group.name = 'raw update'">raw update</button>
  group Name: {{group.name}}
</div>
3
votes

This is expected behaviour, but the description may not be detailed enough and cause some confusion.

according to toRaw docs

Returns the raw, original object of a reactive or readonly proxy. This is an escape hatch that can be used to temporarily read without incurring proxy access/tracking overhead or write without triggering changes. It is not recommended to hold a persistent reference to the original object. Use with caution.

What is happening when you use toRaw, is that you're getting back the original object that you passed into the reactive function without the Proxy. That means that the object is the same object (o and g in example 👇). toRaw only allows you to "escape" the reactivity/listeners bound to the object if you use toRaw instead of the Proxy itself. Even though the objects update, the reactivity (trickling down to DOM) is not applied.

Here is an example:

let o = {name:"Ja Ja"};
let m = Vue.reactive(o);
let g = Vue.toRaw(m);

console.log(o.name, m.name, g.name);

g.name = g.name + " Ding Dong";

console.log(o.name, m.name, g.name);

console.log("Is 'm' same as 'o'? ", m == g);
console.log("Is 'g' really just 'o'? ", o == g);
<script src="https://unpkg.com/vue@next/dist/vue.global.prod.js"></script>

If you want to create an object that will not update the original object, the simplest way is to clone it.

Most of the time you can use JSON.parse(JSON.stringify(o)). The caveat is that you get into issues if you have cyclical references (ie. {a:b, b:a}).

Another option is to use destructuring const copy = {...o}. The caveat here is that it is a shallow copy. Example o = {a:{b:"I will update"}, c:"I won't"}, where copy.a is still same object as o.a

That would then look like either

let o = {name:"Ja Ja"};
let m = Vue.reactive(o);
let g0 = {...Vue.toRaw(m)}; // destructure
let g1 = JSON.parse(JSON.stringify(Vue.toRaw(m))); // clone with json

Alternatively, you can also use some library that handles cloning object for you.

0
votes

it is normal that isReactive(group) return false. Actually in your code we have const group = toRaw(newGroup)

=> group is the original object of the reactive value newGroup.

=> group is a raw value, not a reactive value

if you tried isReactive(newGroup) this will return true