Guide on how to link a prop to a ref in Vue 3.0 without modifying the prop value

When passing a prop user from the parent component to the child component, I want to create a separate copy of it without altering the original prop value.

I attempted to achieve this with the following code:

export default defineComponent({
  props: {
    apiUser: {
      required: true,
      type: Object
    }
  },
  setup(props) {
    const user = ref(props.apiUser);

    return { user };
  }
});

However, modifying the user object also ends up changing the apiUser prop. I considered using Object.assign, but then the reactivity of the ref is lost.

In Vue 2.0, I would approach it like this:

export default {
  props: {
     apiUser: {
       required: true,
       type: Object
     }
  },
  data() {
    return {
      user: {}
    }
  },
  mounted() {
    this.user = this.apiUser;
    // Now I can use this.user without altering this.apiUser's value.
  }
};

Credit goes to @butttons for providing the helpful suggestion that led to the solution.

const user = reactive({ ...props.apiUser });

Answer №1

data: {
 apiUser: {
   required: true,
   type: Object
 }
},
created() {
   const userDuplicate = Vue.reactive(this.apiUser)
}

Utilizing the composition API, we can employ the Vue.reactive method to create a duplicate of any source reactive object. By ensuring that the apiUser is a reactive object, using Vue.reactive() will prevent mutations from occurring in your data.

Answer №2

If you're searching for it, this is what you need: https://vuejs.org/guide/components/props.html#one-way-data-flow

Set up data to include the prop:

export default {
  props: ['apiUser'],
  data() {
    return {
      // user only uses this.apiUser as the initial value;
      // it is disconnected from future prop updates.
      user: this.apiUser
    }
  }
}

Alternatively, if you are using api composition:

import {ref} from "vue";
const props = defineProps(['apiUser']);
const user = ref(props.apiUser);

You may also want to explore computed methods (refer to linked documentation section above) or v-model.

Keep in mind that the solution marked here, , does not work. Trying to update user will result in a readonly error in the console. If you don't plan on modifying user, it's best to just use the prop directly.

Answer №3

When implementing this method

const copiedUser = toRef(props, 'apiUser')

the ref created will attempt to change the prop whenever there is a modification, resulting in a warning.

The proper technique for linking a ref to a prop without causing any prop mutations is as follows:

const copiedUser = ref();

watchEffect(() => (copiedUser.value = props.apiUser));

Answer №4

The optimal approach, in my opinion, involves cloning the prop when initializing the local variable.

Here is how I would tackle it:

Parent component:

<script setup>
import { ref } from 'vue';
import Child from './Child.vue';

const user = ref({
  age: 20,
});
</script>

<template>
  Parent: {{ user.age }}
  <br />
  <child :user="user" />
</template>

Child component:

<script setup>
import { ref, toRaw } from 'vue';
const props = defineProps({
  user: {
    type: Object,
    required: true,
  },
});

const localUser = ref(structuredClone(toRaw(props.user)));
localUser.value.age = 40;
</script>

<template>Child: {{ localUser.age }}</template>

Please note: simply referencing like this will not suffice:

const localUser = ref(props.user);

This method will not prevent changes in the parent component when altering the age of the local reference.

Personally, I opt for using the native structuredClone for deep clonings. Although it does have limitations with types like Proxy, utilizing toRaw overcomes that obstacle by converting the prop into a plain Object compatible with structuredClone.

Feel free to explore various deep clone methods available online.

In my observation, structuredClone outperforms alternatives like lodash in terms of speed and efficiency. It also comes built-in, eliminating the need for additional code snippets and is suitable for most scenarios.
Benchmark comparison available [here]
For more information on supported functionalities of native clone, check out this article [here] or visit the official documentation.

Avoid using the spread operator (...) or Object.assign for deep copying as they only perform shallow copies, failing to replicate nested objects accurately.

Explore a functional Stack Blitz example here: https://stackblitz.com/edit/vitejs-vite-wycfiv?file=src%2Fcomponents%2FChild.vue

Answer №5

During the discussion in the comments, I came across a Vue 2 method that I find particularly useful in these scenarios. It essentially creates a roundtrip when updating a model.

Here is how it works:

Parent

<template>
   <div class="parent-root"
      <child :apiUser="apiUser" @setUserData="setUserData" />
   </div>
</template>

export default {
   data() {
      return {
         apiUser: {
            id: 'e134',
            age: 27
         }
      };
   },

   methods: {
      setUserData(payload) {
         this.$set(this.apiUser, 'age', payload);
      }
   }
}

Child

<template>
   <div class="child-root"
      {{ apiUser }}
   </div>
</template>

export default {
   props: {
      apiUser: {
         required: true,
         type: Object
      }
   },

   data() {
      return {
         user: null
      };
   },

   watch: {
      apiUser: {
         deep: true,
         handler() {
            this.user = cloneDeep(this.apiUser);
         }
      }
   },

   mounted() {
      this.user = cloneDeep(this.apiUser);
   },

   methods: {
      setUserData(payload) {
         this.$emit('setUserData', this.user);
      }
   }
}

I apologize for any typos or mistakes made in the content above.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

It seems that React JS with Redux may not be triggering a re-render

I am currently delving into the world of React JS with Redux and have what I believe is a straightforward query. Here's the code snippet I'm working with: import React from 'react'; import ReactDOM from 'react-dom'; import { ...

Unable to display array values (using jQuery)

Help! I am having trouble rendering all my values. I have names (buttons) and when I click on them, I want to render the array behind it. Using .append() works, but the issue is that when I click again, it doesn't refresh and gets stacked. I am thinki ...

Group of objects containing an inner group of objects

Here is an array of objects that I am working with: let prova: ActiveRoute[] = [ { path: '/Root', method: 'GET', children: [ { path: '/Son', method: 'GET', chi ...

Quasar Select always prioritizes filtering the first selection

I'm currently working with the Quasar Framework and utilizing the Q-Select component with a filter. I want the first option in the filtered list to always be pre-selected, so that if I press enter, it will automatically choose the first one. ...

Inadvertent scroll actions causing unexpected value changes in Material UI sliders

I am currently working on a React application that utilizes Material UI, with slider components integrated throughout the interface. However, I have encountered an issue when using a touch screen device - unintentional changes to the slider values occur wh ...

What is the best way to save a beloved pet for a user who is signed in?

Is there a way to store a favorite pet for a logged-in user using React and Express? I am currently retrieving pets from an API and using PostgreSQL as the database. const favPetDetails = { id, name, media, breeds, location, dista ...

Launch a VueJS component in a separate window

I am working on a VueJS application that currently consists of only one page and does not utilize vue-router for routing. My goal is to create a button that, when clicked, will trigger the window.open() function to display content from one of my Vue Compo ...

Refresh information periodically within a Node.js application

Recently, I developed a web application using nodejs to fetch data from Amazon. My goal is to have the app update at regular intervals for different purposes: - Every 15 minutes for real-time inventory monitoring - Once daily for less frequently changing d ...

Obtain the ID of element 1 by clicking on element 2 using JQuery

In the world of javascript/jquery, When button1 is clicked, we can get its id like this: var button1id = $(this).attr("id"); If button2 is clicked, how do we retrieve button1's id? This brings us to the question: How does button2 access the i ...

Sending a JavaScript array to an MVC controller in .NET 5.0 via a POST request

Trying to send data from a JavaScript array to an MVC controller. While debugging, I end up in the method public void GetJSArray(List<SelectorModel> selectorModels) However, the selectorModels is currently showing as null. Below is the model being ...

Using inline C# in the browser as an alternative to Javascript: a step-by-step guide

Recently, I came across Microsoft's Blazor, a tool that enables running C# code in the browser. After checking out some tutorials, it appears that an ASP.NET Core backend server is required for it to function properly. It would be interesting if we co ...

The error "map is not a function" occurs when trying to update the

I've encountered an issue with my links rendering on a page. I wrote a function that toggles a specific property from false to true based on whether the link is active or not, triggered by a click event. Upon inspecting the updated set of links, I no ...

The (window).keyup() function fails to trigger after launching a video within an iframe

Here is a code snippet that I am using: $(window).keyup(function(event) { console.log('hello'); }); The above code works perfectly on the page. However, when I try to open a full view in a video iframe from the same page, the ke ...

Encountering this issue: Unable to access the property 'length' of an undefined variable

I'm currently developing an app using nuxt, vuetify 2, and typescript. Within the app, I have radio buttons (referred to as b1 and b2) and text fields (referred to as t1, t2, t3). When a user clicks on b1, it displays t1 and t3. On the other hand, w ...

Slider Adjusts Properly on Chrome, but Not on Firefox

Visit this URL This link will take you to the QA version of a homepage I've been developing. While testing, I noticed that the slider on the page works perfectly in Chrome and IE, but has layout issues in Firefox where it gets cutoff and moved to th ...

Using Javascript's .replace() method to preserve HTML elements

This is a JavaScript function I wrote function replaceCharacters(text_input){ return text_input .replace(/O/g, 0) .replace(/I/g, 1) .replace(/o/g, 0) .replace(/i/g, 1) .replace(/t/g, 4) .replace(/d/g, 9) ...

Showing both SubTotal and Total simultaneously in a JavaScript powered interface

Recently diving into Javasript, I came across the code snippet below from a different source that I tweaked to suit my needs. My goal is to show the subtotal in the third column for each benefit while displaying the total of the entire form. However, I&apo ...

Is it possible to incorporate @ngrx into a Vue project?

Looking for a state management solution to connect our desktop Angular app and mobile Vue app, both sharing APIs and functionalities. As a newcomer to RxJS and state management tools like Ngrx, I'm curious if there are any hindrances in directing Vue ...

Which is better: Array of Objects or Nested Object structures?

I have a simple programming query that I'm hoping you can help clarify. Currently, I am dealing with numerous objects and I am contemplating whether it's more efficient to search for content within an array of objects or within a nested object s ...

The AngularJS templates' use of the ternary operator

Is there a way to implement a ternary operation in AngularJS templates? I am looking for a way to apply conditionals directly in HTML attributes such as classes and styles, without having to create a separate function in the controller. Any suggestions wo ...