16

I have some form data which I share with children components through props. Now I want to clone the prop object and make it non-reactive. In my case I want the user to be able to modify the props value without actually changing the cloned value. The cloned value should only be there to show the user what the form was when editing. Below code shows this:

    <template>
        <div>
            <div v-if="computedFormData">
                original prop title: {{orgData.title}}

                new title:
                <input type="text" v-model="formData.title"/> 
                //changing data here will also change orgData.title
            </div>

        </div>
    </template>

    <script>
        export default {
            props: ['formData'],
            data() {
                return {
                    orgData: [],
                }
            },
            computed: {
                computedFormData: function () {
                    this.orgData = this.formData;
                    return this.orgData;
                },
            },
            methods: {
            },
        }
    </script>

I have tried with Object.freeze(testData); but it doesnt work, both testData and orgData are reactive. Note also that using mounted or created property does not render orgData so I'm forced to use the computed property.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
A.C
  • 423
  • 2
  • 4
  • 13
  • 3
    So with the one way data flow, just assign `this.orgData` to `testData: []` like so: `data() { return {testData: this.orgData}; },` – Derek Pollard Jan 07 '19 at 16:51
  • I don't get it your aim , do you want to get a value via input then show it on this component (by changing default value) but not to send to child component ? – Caner Sezgin Jan 07 '19 at 17:04
  • Using the data property does not show the original value since its not being rendered. I updated my question so hopefully its more clear now. – A.C Jan 07 '19 at 19:36

3 Answers3

14

Try copying the prop values with Object.assign. No more issue with reactivity since the new, assigned values are just the copy instead of the reference to the source.

If your data object is a lot more complex, I'd recommend deepmerge in place of Object.assign.

Vue.component('FormData', {
  template: `
    <div>
      <div v-if="testData">
        <p>Original prop title: <strong>{{orgData.title}}</strong></p> 
        <p>Cloned prop title:</p>
        <input type="text" v-model="testData.title" />
      </div>
    </div>
  `,

  props: ['orgData'],

  data() {
    return {
      testData: Object.assign({}, this.orgData)
    }
  }
});

const vm = new Vue({
  el: '#app',

  data() {
    return {
      dummyForm: {
        title: 'Some title'
      }
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <form-data :org-data="dummyForm"></form-data>
</div>
Yom T.
  • 8,760
  • 2
  • 32
  • 49
  • The problem with this solution is that if the parent changes, which it can, especially if it is edited, the child will not change. So you could find yourself saving over old data or displaying the wrong data entirely – Derek Pollard Jan 07 '19 at 17:39
  • I guess that will take the OP to clarify if he/she actually needs the data as **initial value** (some sort of preset) only, or the other way around. – Yom T. Jan 07 '19 at 17:45
  • 1
    Sorry for not being clear enough. It should be the other way around. In other words, I want the user to be able to modify the props value without actually changing the cloned value. I couldnt get it to work with the code above. – A.C Jan 07 '19 at 19:23
7

Object.assign is merely a shallow copy. If you have a copy consists that of only primitive data types (string, number, bigint, boolean, undefined, symbol, and null) it's ok. It to remove its reactivity. But, if you have a copy that has reference types you can’t shallow clone it to remove its reactivity.

For depping clone you can use the JSON.parse(JSON.stringify()) pattern. But keep in mind that is going to work if your data consists of supported JSON data types.

  props: ['orgData'],

  data() {
    return {
      cloneOrgData: JSON.parse(JSON.stringify(this.orgData))
    }
  }
Victor
  • 151
  • 1
  • 12
1

Not entirely sure why but using Object.assign on a computed property did not work for me. I solved it by using a watch property for the props value:

    watch:{
        formData(){
            this.orgData = Object.assign({}, this.formData)
        }
    },
A.C
  • 423
  • 2
  • 4
  • 13