Vue.js: instantiate functional component programmatically - vue.js

when I instantiate functional component using this code
const Component_Constructor = Vue.extend(Component);
let component_instance = new Component_Constructor();
component_instance.$mount();
the component gets undefined context argument on the render function
how can i pass parameters (props, slots, children, ...) to the component?

the only workaround I found so far is to wrap the functional component into another normal component like this:
let AComponent = {
functional: true,
name: 'a-component',
render(h, context) {
return h('div', context.children[0].text);
}
};
let template = `<a-component>test content</a-component>`;
let WrapperComponent = Vue.extend({
components: {AComponent},
template,
});
let componentInstance = new WrapperComponent().$mount();
let content = componentInstance.$el;

As one of the vuejs core team said here https://forum.vuejs.org/t/functional-component-with-vue-extend/40752, you can not mount functional component manually.
$mount() need a vue instance, that a functional component does not have.

Related

How to set grandparent mock data for vue unit test

I am writing unit test for an existing vue component. The component has a method which runs on mount. The method uses return this.$parent.$parent.someData. I am looking for a way to pass grandparent mock data to the component while writing unit test. I know the way to pass parent component as mock, but looking for a way to pass grand parent.
I am using the following code in my test
const wrapper = shallowMount(MyComponent,{
parentComponent:ParentComponent
})
Looking for a way to pass grandparent component. I am using vue-test-utils.
PS: I cannot make changes in existing code
This is the most straight-forward way:
import Child from './path/to/Child.vue'
const GP = shalowMount({
template: '<div />',
data: () => ({
foo: 'bar' // mocked data
})
})
const wrapper = shallowMount(Child, {
parentComponent: {
created() {
this.$parent = GP.vm
}
}
})
console.log(wrapper.vm.$parent.$parent.foo) // bar
If you want to test closer to what the user gets in the browser, you could shallowMount the actual ancestors (you'll need their deps in this case: stores, third-party packages, etc...):
import GrandParent from './path/to/GrandParent.vue'
import Parent from './path/to/Parent.vue'
import Child from './path/to/Child.vue'
const GP = shallowMount(GrandParent);
/* set GP data, props, mocks or spies */
const wrapper = shallowMount(Child, {
parentComponent: shallowMount(Parent, {
parentComponent: GP.vm
}).vm
})

How to access a components $el.innerHTML in Vue 3?

In Vue 2 it was possible to access the innerHTML of a Vue component instance via someInstance.$el.innerHTML. How can the same be achieved in Vue 3?
Let's say you want to to create a Vue component and access its innerHTML. In Vue 2, this could be done like so:
const wrapper = document.createElement('div');
const someComponentInstance = new Vue({
render: h => h(SomeComponent, {
props: {
someProp: 'prop-value-123'
}
})
});
someComponentInstance.$mount(wrapper);
console.log(someComponentInstance.$el.innerHTML);
To achieve the same thing in Vue 3, we have to leverage the createApp() and mount() functions like so:
const wrapper = document.createElement('div');
const someComponentInstance = createApp(SomeComponent, {
someProp: 'prop-value-123'
});
const mounted = someComponentInstance.mount(wrapper); // returns an instance containing `$el.innerHTML`
console.log(mounted.$el.innerHTML);
A word of warning: Make sure your innerHTML is sanitized if it is user generated and you want to reuse it somewhere in your app.

Is there a way to access Vue $scopedSlot's mounted DOM element?

I have a scoped slot named myElement, I can access it via this.$scopedSlots.myElement()[0].
Usually when a vnode is mounted there is a $el containing the DOM element, but not in scoped slots apparently, there is only an undefined elm.
I also tried to put a ref on the slot in the child template and access it through .context.$refs but it lists the refs on the parent template only.
Is there any way I can access that mounted DOM element, if its id or class is unknown from the child component?
Thanks
More Details:
Parent template (Pug)
child-component
template(v-slot:myelement="{ on }")
span My element content
Child component
mounted () {
console.log(this.$scopedSlots.myelement()[0])
// From here I want to get the position of the span with
// span.getBoundingClientRect()
}
Reproduction link:
https://codepen.io/antoniandre/pen/BaogjrM?editable=true&editors=101
So something like this can be used to workaround:
mounted () {
const myEl = this.$scopedSlots.myelement()[0];
if(myEl.data && myEl.data.attrs && myEl.data.attrs.id){
const id = myEl.data.attrs.id
console.log([this.$el.querySelector('#id')])
} else {
throw new Error('slot myelement needs an id');
}
}
or forcefully apply an id:
const Child = {
render: function(h){
const myEl = this.$scopedSlots.myelement()[0]
myEl.data = {attrs: {id: 'id'}};
console.log(myEl)
return h('div', {}, [myEl])
},
mounted () {
console.log([this.$el.querySelector('#id')])
}
}
The only workaround that I've found so far is this:
First get the vnode of the scopedSlot, get its given context, crawl each vnode children of the context to find itself by the _uid key, which will contain the $el mounted element.
This is not very straightforward but it does the trick.
mounted () {
const { context } = this.$scopedSlots.activator()[0]
console.log(context.$children.find(vnode => vnode._uid === this._uid).$el)
}
I asked in Vue forum and Vue chat but I am still looking for a better way, if there is let me know.

How to get same Vue components from page by some attribute

I want to get from an Accordion vue component all others Accordion components on the page with the same props or data value.
I've tried a method this.$parent.$children, but it will not working if components are in different wrappers.
let state = this.active;
this.$parent.$children.forEach(value => {
if (value.commonCssClass === this.cssClass)
value.active = false;
});
this.active = !state;
How about register components that you need in lifecycle methods?
in beforeMount register that component in global object
and beforeDestroy to un-register it
A way to solve my problem.
beforeMount() {
if(!window.VueAccordeons)
window.VueAccordeons = {};
if(!window.VueAccordeons[this.cssClass])
window.VueAccordeons[this.cssClass] = [];
window.VueAccordeons[this.cssClass].push(this);
},
beforeDestroy() {
if(window.VueAccordeons)
delete window.VueAccordeons;
},

dynamic templating - props for child component should be reactive but are not

I started Vue.js two days ago and I already need your help.
I have a state machine component which renders a child component passed as parameter. Because I do not know in advance which component I am going to display and the props it takes, I am using render functions instead of templates for the state machine parent component.
Code excerpt goes like this :
function render(h) {
const app = this;
props.reduce((acc, key) => ((acc[key] = app[key]), acc), currentPropsObj);
console.log("child props", currentPropsObj);
return app.hasStarted
? h(
renderComponent,
{
// copy the props from the machine vue component to the render component
props: Object.assign({}, currentPropsObj)
},
[]
)
: h("div", {}, "LoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLo");
}
return Vue.component(name, {
render,
data: function() {
return initialData;
},
methods: {
set: function(stateObj) {
Object.keys(stateObj).forEach(key => (this[key] = stateObj[key]));
}
},
The issue is that the dynamic renderComponent does not seem to take into account the props it (should) receives as parameter. I checked that currentPropsObj is correctly computed. Using Vue devtool, I also checked that the Search child component (the one I am using in the demo) props are not udpated when its parent data is updated.
For a full demo and reproduction, I made a codesandbox : https://codesandbox.io/s/m5jzqw9w3y
What can I be doing wrong?