Subscribe on changes!

Ability to deep unref/toRaw a reactive object

avatar
Jan 21st 2022

What problem does this feature solve?

I'm currently building a form that has some of it's fields reactive to Pinia by first creating a reacitve object then setting some keys to their respective Pinia state. This works well and updates both the reactive form object and Pinia itself.

When I'm POSTing the form I'm trying to convert my reactive form object to a plain object by using toRaw. This works for my fields that are not using the Pinia state but the ones that are keep their reactive wrapper.

const { name } = storeToRefs(userStore);

const profileData = reactive({  
  name, // Stays in sync with Pinia 
  jobTitle: '', 
});

const plainObject = toRaw(profileData); // name is still reactive but jobTitle is plain

I think the ability to have an escape hatch that will always return data as a plain JS object is very useful (just as unref and toRaw are) and should be part of the core. It would be particularly helpful when using external APIs and form data.

There is a third-party package that covers the issue: https://github.com/DanHulton/vue-deepunref

What does the proposed API look like?

const plainObject = deepUnref(reactiveObject);

or possibly

const plainObject = toRaw(reactiveObject, { deep:true });

avatar
Jan 21st 2022

What you want in the end is a clone of the object hierarchy you would get as if you recursively walked the whole reactive proxy. Which a bunch of packages have already solved, like lodash's cloneDeep

See SFC Payground

Doing a deep clone like this and covering all edge cases while doing it is actually not that easy. I'm hesitant about bringing this additional complexity into core when de-facto "standard" toolkits like lodash (as well as more specialized packages for this found on npm) can do it already.

We should however consider how to document this.

Other thoughts?

avatar
Jan 21st 2022

Thanks for the insight! I wasn't aware Lodash had the capability to work with Vue's reactivity. I agree then that given a reputable library can solve the problem then it dosen't need to be part of the core.

avatar
Jan 21st 2022

Just to be clear, lodash doesn't know how to unwrap refs - but it doesn't have to. if we clone the reactive proxy, nested refs will be unwrapped by Vue in the process.

avatar
Mar 4th 2022

What you want in the end is a clone of the object hierarchy you would get as if you recursively walked the whole reactive proxy. Which a bunch of packages have already solved, like lodash's cloneDeep

See SFC Payground

Doing a deep clone like this and covering all edge cases while doing it is actually not that easy. I'm hesitant about bringing this additional complexity into core when de-facto "standard" toolkits like lodash (as well as more specialized packages for this found on npm) can do it already.

We should however consider how to document this.

Other thoughts?

I think vue should be able do it by itself. I don't want to install a library to resolve the problem from another one. Since vue provides escape hatch methods, shouldn't they enough to use?

avatar
Apr 18th 2023

I found emahuni's answer above does not work, and doesn't have TS definitions.

I used:

npm i ramda
npm i @types/ramda

and then R.clone(state) before saving my state to IndexDB.

If you don't do this first then IndexDB will give you an DOMException: Failed to execute 'put' on 'IDBObjectStore': #<Object> could not be cloned. error because a Proxy cannot be serialised for storage. toRaw() from Vue doesn't work either because there may be nested ref() objects that can't be serialised.

avatar
May 11th 2023

@LinusBorg not sure why is closed, it is indeed a problem and as mentioned above i do not install heavy-weighted lib in order to solve another lib problem

i have a legacy project, where i cannot refactor all at once and have to refactor gradually, introducing hacks from time to time, while fixing other problems

and project data structure is a lot of random nested Proxies, for now i'll write my own function for deep unRef and toRaw, but sounds like a proper util function/option for a function

avatar
May 11th 2023

for anybody looking for a solution thats what i came up with

import {
  toRaw,
  isRef,
  isReactive,
  isProxy,
} from 'vue';

export function deepToRaw<T extends Record<string, any>>(sourceObj: T): T {
  const objectIterator = (input: any): any => {
    if (Array.isArray(input)) {
      return input.map((item) => objectIterator(item));
    } if (isRef(input) || isReactive(input) || isProxy(input)) {
      return objectIterator(toRaw(input));
    } if (input && typeof input === 'object') {
      return Object.keys(input).reduce((acc, key) => {
        acc[key as keyof typeof acc] = objectIterator(input[key]);
        return acc;
      }, {} as T);
    }
    return input;
  };

  return objectIterator(sourceObj);
}
avatar
May 16th 2023

@Hulkmaster thank you 🙏

avatar
Jun 1st 2023

Can't believe this could be a Vue issue, I would rather this is my fault

avatar
Jun 20th 2023

https://www.npmjs.com/package/vue-deepunref

This package breaks my deploys on Netlify.

the solution by @Hulkmaster works however!