1

I'm creating a form component, and it needs to keep track of if the form has changed (if it's different to it's original value or not) - and to do this I need to store the initial prop value to compare to. This will be a reusable form component, so the value of the prop is unknown except for it being an object.

There has been a similar question for Vue 2 in the past, however the first method comes with a warning and doesn't seem to work anymore, the second is not applicable in Vue 3 and the third one is kind of what i'm doing now, however..

I'm deep cloning the prop, but to do so - I need to get rid of the proxy that Vue adds to the props, which is what i'm using `toRaw` for, however in the documentation for `toRaw()` it specifically says "It is not recommended to hold a persistent reference to the original object. Use with caution.", which is exactly what I want to do - keep a persistent reference to the original object.

const originalValue = structuredClone(toRaw(props.modelValue));

So my question is - what is the recommended way to go about storing initial values from props in cases like these? Is toRaw() the right pick in this case?

Edit:

Another method that works is using JSON, but this could be unreliable for a flexible form if there are any circular dependencies in props.modelValue.

const originalValue = JSON.parse(JSON.stringify(props.modelValue));
Norah
  • 41
  • 5
  • 1
    `{ ...props.modelValue }` shouldn't return a proxy object – yoduh May 16 '23 at 22:46
  • @yoduh it seems it does, as whenever I try to just spread it, structuredClone errors out: `Uncaught DOMException: Proxy object could not be cloned.`. – Norah May 17 '23 at 05:13
  • See https://stackoverflow.com/questions/72632173/unable-to-use-structuredclone-on-value-of-ref-variable . You don't have to use structuredClone, you can use a more simple clone that doesn't have this limitation. If it works the way you did then that's it, that's how it's done in this case. It's unknown what data looks like, the question needs to mention it. In a more sophisticated case you need a helper that will process nested refs when converting to plain object. – Estus Flask May 17 '23 at 07:48
  • @EstusFlask I didn't include what value the of `props.modelValue` is, as it is a form component - it will vary for every use, and can be any object. I tried looking at other cloning packages, like for example from lodash - but the ones I could find have limitations with proxies. Do you know any deep cloning packages that work with Vue refs to recommend? – Norah May 17 '23 at 07:50
  • @EstusFlask and do you have any articles or resources on making a helper like that? This is supposed to be a form component that can be very flexible, I do want the solution to be sophisticated. – Norah May 17 '23 at 08:10
  • JSON.parse+stringify. It won't cause an error at least. It won't handle nested refs correctly because ref `value` property is non-enumerable, you need to create your own helper that recursively traverses object properties and handles refs differently. No articles, just common js knowledge. See https://stackoverflow.com/questions/15690706 . You'd need something like `if (isObj(val) && val._v_isRef) val = val._value` to process refs. Can't help with ready to use solution atm, sorry – Estus Flask May 17 '23 at 08:15
  • Alright, I do know how to loop through an object, thought you meant something else, my question is about the method used to clone it (even while looping). I already mentioned the issue with the JSON method, and am looking for alternatives that are more robust - or a confirmation that `toRaw()` is valid in this case. – Norah May 17 '23 at 08:27

1 Answers1

0

you don't need to track the form with cloning skill.

use Watch function (or/and with Emit)

const yourForm = ref({... ;

watch(()=>yourFrom.value, 
   (newV)=> {
      // anything you want to do

      // if you need to propagate the 'change', use Emit too
      emit("iamchanging", newV); 
   }
);


saw your comment, then how about

const OriginalValue = {...yourcode...};
const formValue = ref({...OriginalValue});
const resetToOrigin = () => formValue.value = {...OriginalValue};
const isDifferent = computed(() => compareValues()); // with your function

LyuMir
  • 84
  • 3
  • 1
    Using watch in this case doesn't really solve my issue, as it won't give me access to the initial value. It gives access to the current value and the _previous_ value. I want to keep the original value specifically. The values could change and then be changed back to what the original value was - in which case this won't detect that it's no longer different from the original value. – Norah May 17 '23 at 05:17
  • Saw your update - unfortunately just spreading the prop will still make it change whenever the prop changes. – Norah May 17 '23 at 05:58
  • that is strange.. are you using proxy property in Object like {"propertyName":ref( or Object in Object ? did you try using lodash cloneDeep or JSON.parse(JSON.stringify( yourOriginValue)) ? – LyuMir May 17 '23 at 07:50
  • 1
    there are no nested proxy objects in my test values so far, the prop passed is a ref in the parent component, but nothing nested. I tried lodash, but that also needs the use of `toRaw()` to work. `JSON` does work, however would cause issues if there are any circular references in the value so maybe not reliable long term, but definitely an alternative for simple forms! – Norah May 17 '23 at 08:02