6

Following this tutorial, I'm trying to programmatically create instances of a component on my page.

The main snippet is this:

import Button from 'Button.vue'
import Vue from 'vue'
var ComponentClass = Vue.extend(Button)
var instance = new ComponentClass()
instance.$mount()
this.$refs.container.appendChild(instance.$el)

However I get two errors:

  1. The component I'm trying to instantiate contains references to the store, and these don't work: "TypeError: Cannot read property 'state' of undefined".

  2. For the last line of the snippet (this.$refs.container.appendChild(instance.$el)) I get this error: "Uncaught TypeError: Cannot read property 'container' of undefined"

I'm really not sure how to troubleshoot this, if anyone strong in Vue.js could give me some hint as to why I'm getting these errors and to solve them that would be terrific.

9
  • do you have an element with reference container ? <div ref="container"></div> Commented Nov 9, 2018 at 21:28
  • Good catch, unfortunately after adding <div ref="container"></div> inside my template, I still get the same two errors. Commented Nov 9, 2018 at 21:54
  • can you publish a running example? Commented Nov 9, 2018 at 22:02
  • Well my whole code is exposed. Plus I don't know how to upload a full Webpack/Vue project on sites like Jsfiddle, is that even possible? Commented Nov 9, 2018 at 22:21
  • I think dom is not created yet when you call $refs. instead, put it inside a method in the export module like the tutorial you cited. codesandbox Commented Nov 10, 2018 at 7:23

4 Answers 4

12
+50

1) Since you're manually instantiating that component and it doesn't belong to your main app's component tree, the store won't be automatically injected into it from your root component. You'll have to manually provide the store to the constructor when you instantiate the component ..

import ProjectRow from "./ProjectRow.vue";
import Vue from "vue";
import store from "../store";

let ProjectRowClass = Vue.extend(ProjectRow);
let ProjectRowInstance = new ProjectRowClass({ store });

2) In a Vue Single File Component (SFC), outside of the default export this doesn't refer to the Vue instance, so you don't have access to $refs or any other Vue instance property/method. To gain access to the Vue instance you'll need to move this line this.$refs.container.appendChild(instance.$el) somewhere inside the default export, for example in the mounted hook or inside one of your methods.

See this CodeSandbox for an example of how you may go about this.

Sign up to request clarification or add additional context in comments.

2 Comments

This seems to work! Any idea how I could inject the component to a different component than the one we're in? So instead of this.$refs.container.appendChild(ProjectRowInstance.$el);, something like anotherComponent.$refs.container.appendChild(ProjectRowInstance.$el);. Is there a way to achieve that?
@drake035 You should be able to traverse your component tree up by using $root and $parent, and down by using $refs as explained in the docs. See this jsfiddle for example.
2

This is another way to instantiate a component in Vue.js, you can use two different root elements.

// Instantiate you main app
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

//
// Then instantiate your component dynamically
//

// Create a component or import it.
const Hello = {
  props: ['text'],
  template: '<div class="hello">{{ text }}</div>',
};

// Create a componentClass by Vue.
const HelloCtor = Vue.extend(Hello);

// Use componentClass to instantiate your component.
const vm = new HelloCtor({
  propsData: {
    text: 'HI :)'
  }
})
// then mount it to an element.
.$mount('#mount');


Comments

1

It works by assigning "this" to the property "parent". By setting the parent you also have access to the $store in the new instance. (Provided that "this" is another Vue instance/Component and already has access to the store, of course)

new (Vue.extend(YourNewComponent))({
    parent: this,
    propsData: {
        whatever: 'some value',
    },
}).$mount(el.querySelector('.some-id'))

If you don't need the reference to the parent, you can just leave "parent: this," out.

Important note: When mounting many (like 500+) items on the page this way you will get a huge performance hit. It is better to only give the new Component the necessary stuff via props instead of giving it the entire "this" object.

5 Comments

When i assign, parent:this instead of assigning store, router, i18n separately i get this vue warn , "Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders." ... eventhough i use computed property to use the information from the prop inside... do you have any idea what could be causing this ?
Just like it says, you are "setting" the value probably somewhere (overwriting it). And you must actually update the outside value (the source), not the one you can access from inside. For the inside value it's for using/viewing/showing it, not to update it. So update it in the source or if that's not gonna work you might clone the value and update that? Depends on what you want to achieve
i know , but i'm not setting it anywhere , it's only for display ... i also use computed property that returns the value of the prop, so basicly i'm not using it anywhere but in that property ... the thing is that the warn appears only after i send the parent ... if i send store, router etc.. separately the warn doesn't appear :/
I don't know your code but sometimes even while "computing" the value you are actually modifying it. First thing to do is maybe "clone" the incoming data in our computed function into a real new. Furthermore I updated my answer because the previous one was old and I found a better way already. So try that maybe?
I just abandoned the idea of sending the parent for now, i just send what i need separately , i have tasks with higher prio then that, i'll come back to it later ... but thanks for your help ...
0

I went down this path, following all the examples above, and even this one: https://css-tricks.com/creating-vue-js-component-instances-programmatically/

While I got far, and it works (I made a lot of components this way), at least for my case, it came with drawbacks. For example I'm using Vuetify at the same time, and the dynamically added components didn't belong to the outer form, which meant that while local (per component) validation worked, the form didn't receive the overall status. Another thing that did not work was to disable the form. With more work, passing the form as parent property, some of that got working, but what about removing components. That didn't go well. While they were invisible, they were not really removed (memory leak).

So I changed to use render functions. It is actually much easier, well documented (both Vue 2 and Vue 3), and everything just works. I also had good help from this project: https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/

Basically, to add a function dynamically, just implement the render() function instead of using a template. Works a bit like React. You can implement any logic in here to choose the tag, the options, everything. Just return that, and Vue will build the shadow-DOM and keep the real DOM up to date.

The methods in here seems to manipulate the DOM directly, which I'm glad I no longer have to do.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.