2
votes

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?

2
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
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. css-tricks.com/…jbwilhite
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
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
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

2 Answers

1
votes

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
    }
});
0
votes

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
    }