2

I'm trying to figure out the best way to update propsData created on a component instance. Basically I have a signature wrapper page, and receive a bunch of html which is rendered using v-html. Then I'm creating a variable number of signature pad components within that rendered html. Since I don't know what the html is going to be, I'm forced (best I can tell) to create the components on the fly after mounting.

So I'm running the following on the parent mounted():

    initializeSignaturePads() {

        const signatureAreas = document.querySelectorAll('.signature_area');

        // dynamically create a new vue instance for each signature pad and mount onto the respective .signature_area element
        // since the html is loaded via ajax, we don't know where to render this template on load, so a new Vue must be created
        signatureAreas.forEach(element => {
            const id = element.id;
            const signatureType = element.classList.contains('initials') ? 'initials' : 'signature';

            if (this.needsCustomerSignature(id)) {
                let length = this.signatures.push({
                    fieldName: id,
                    valid: false,
                    data: null,
                    type: signatureType
                });

                const SignaturePadClass = Vue.extend(SignaturePad);
                const SignaturePadInstance = new SignaturePadClass({
                    parent: this,
                    propsData: {
                        fieldName: id,
                        editable: true,
                        signatureType: signatureType,
                        signatureIndex: length - 1,
                        signatureData: null
                    }
                });

                // add handler for signed emit
                SignaturePadInstance.$on('signed', signature => {
                    this.padSigned(signature);
                });

                // watch this for an accepted signature, then pass to each child
                this.$watch('createdSignature.accepted', function (val) {
                    let signatureData = null;

                    if (val) {
                        signatureData = signatureType == 'signature' ? this.createdSignature.signatureData : this.createdSignature.initialsData;
                    }

                    // These two lines are the problem
                    SignaturePadInstance._props.signatureData = signatureData;
                    SignaturePadInstance._props.editable = !val;
                });

                SignaturePadInstance.$mount(element);
            }
        });
    },

As far as I can tell, that propsData is now statically set on the component. But for the signatureData and editable props, I need to be able to pass that to the child components when they're updated. The watcher is working correctly and the prop is getting updated, but I'm getting the Avoid mutating a prop directly warning. Which is understandable, since I'm directly mutating the prop on the child. Is there Is there a good way to handle this?

jbwilhite
  • 153
  • 1
  • 5
  • 8
  • I don't really understand what your code is supposed to do as I don't know what `propsData` is. But the props in a component are not meant to be changed within the component. The component should listen to changes in the props when needed (when the parent changes a dynamic prop), but the component itself shouldn't change the props. – Phiter Jun 27 '18 at 23:41
  • I understand that in a normal flow, but I'm having to create the children components using the constructor, which accepts `propsData` to initialize the props. This article is partially what I used to come up with this method. https://css-tricks.com/creating-vue-js-component-instances-programmatically/ – jbwilhite Jun 27 '18 at 23:42
  • If you want to change the values coming from the props to the children, you should create a `data` object for the component and change those values instead, and bind them to the children. – Phiter Jun 27 '18 at 23:43
  • Ok, I did set it up where the parent directly updated the child's data object, and that worked, but that seems to go against Vue's props down, events up model. It seems like there should be a way to mimic the standard reactive way of passing props to components when creating programmatically. – jbwilhite Jun 28 '18 at 00:17
  • Yeah I haven't worked with dynamically created instances yet so I can't help much on that. But the thing is, if you're facing issues having to pass props too deep down, maybe you should try using a state container. In Vue's case, Vuex. – Phiter Jun 28 '18 at 00:27

2 Answers2

1

I was able to get this figured out, after I found this stackoverflow answer. When setting props on the propsData, I was using all primitive types, so they didn't have the built in reactive getters and setters. It makes sense now that I realize that, what I was doing was the equivalent of passing a string as a prop to an component element. After I did that, the prop was reactive and I didn't have to bother with manually creating watchers.

Anyways, this was the solution:

const SignaturePadInstance = new SignaturePadClass({
    parent: this,
    propsData: {
        fieldName: id, // << primitive
        editable: true, // << primitive
        signatureType: signatureType, // << primitive
        signatureIndex: length - 1,  // << primitive
        createdSignature: this.createdSignature  // << reactive object, updates to the child when changed
    }
});
jbwilhite
  • 153
  • 1
  • 5
  • 8
  • Hey, wondering if you could expand more on how this.createdSignature was made reactive? I'm trying to achieve the same thing but even when I set the value for props to this.prop, the child is not updating. – danbrellis Jun 09 '20 at 20:23
  • @danbrellis use Vue.observable. See my answer https://stackoverflow.com/a/63578995/3221253 – AndyDeveloper Aug 25 '20 at 12:37
0

I used Vue.observable (VueJS 2.6 and above) to make the property reactive. Here's a complete example:

    initializeSignaturePad() {
       const signaturePadComponent = Vue.extend(SignaturePad)
       this.instance = new signaturePadComponent()
       instance._props = Vue.observable({
          ...instance._props,
          editable: true
       })
       this.instance.$mount()
       document.body.appendChild(instance.$el)
    }
    
    onSignatureAccepted {
        this.instance.editable = false
    }
        
AndyDeveloper
  • 2,790
  • 1
  • 21
  • 23