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

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.

Related

BeforeRouteEnter not working in production with script setup

I used the beforeRouteEnter hook in vue-router to load data from two different endpoints using axios. I used promise.all() to load the data and then passed it to the component using next(). It seems to be working in development but when it is hosted on vercel the data isn't rendered on the component.
import axios from "axios"
import NProgress from "nprogress"
export default {
name: "DetailView",
beforeRouteEnter(to, from, next) {
NProgress.start()
const getDetails = axios.get(`api/grades/${ to.params.id }/`)
const getResults =
axios.get(`api/results/`, {
params: {
'grade-id': to.params.id,
}
})
Promise.all([getDetails, getResults])
.then(([details, results]) => {
next((vm) => {
vm.details = details.data
vm.results = results.data
})
})
.finally(NProgress.done())
},
}
I used a <script setup>...</script> for the setup function with the
import { ref } from "vue"
const details = ref({})
const grades = ref({})
I'm relatively new to javascript too and still trying to understand promises and async/await very well. Thank you
Finally found a solution to the problem. Components using <script setup> in vue are closed by default, he public instance of the component, which is retrieved via template refs or $parent chains, will not expose any of the bindings declared inside <script setup>. From the vue docs.
I had to explicitly expose the properties used in the beforeRouteEnter navigation guard using the defineExpose compiler macro
defineExpose(
{
details,
results
}
)

dynamic vue component access to vuex

I'm using the following code to dynamically create vue components. I'd like them to communicate with the vuex store but it looks like they have no access to it. Does anyone know how to solve this?
let module = this.modules.find(m => m.name === name)
const ModClass = Vue.extend(module.component);
const modInstance = new ModClass({
propsData: {
client: this.client,
}
});
modInstance.$mount();
modInstance.$on('close', () => {
modInstance.$destroy(true);
(modInstance.$el).remove();
})
this.$refs.panels.appendChild(modInstance.$el);
From the docs:
In order to have an access to this.$store property in your Vue components, you need to provide the created store to Vue instance.
Vuex has a mechanism to "inject" the store into all child components from the root component with the store option.
So, in your example this will be here:
const modInstance = new ModClass({
propsData: {
client: this.client,
},
store: yourVuexInstanceHere
});
Are you making a Nuxt plugin? If so, you can reach your store instance by deconstructing it from the context.
export default ({ app: { store } }, inject) => {
// your code...
const modInstance = new ModClass({
propsData: {
client: this.client,
},
store: store
});
// your code...
})

Vue 3 - How to dispatch to Vuex store in setup

I have a project where I use Vue 3 and Vuex. This is my first time using Vue 3. I can't seem to get how to access Vuex inside the Setup method of a Vue 3 project.
I have a feature object. That is being set by a Childcomponent using the featureSelected method. Firstly in my setup I create a store constant with useStore; from import { useStore } from "vuex";. Then inside the featureSelected function I call the dispatch function on this store object store.dispatch("setPlot", { geometry: newFeature });.
I keep on getting an error telling me that the dispatch function does not exist on the store object: Uncaught TypeError: store.dispatch is not a function.
setup() {
const store = useStore;
const feature = ref();
const featureSelected = (newFeature) => {
feature.value = newFeature;
store.dispatch("setPlot", { geometry: newFeature });
};
return { feature, featureSelected };
},
useStore is a composable function which should be called using () like :
const store = useStore();

Mount vue component - Vue 3

I want to do this in Vue 3
new ComponentName({
propsData: {
title: 'hello world',
}
}).$mount();
But I'm getting this error: VueComponents_component_name__WEBPACK_IMPORTED_MODULE_1__.default is not a constructor
Currently, we are using the above approach to append VUE components in our legacy app via append
I would like to do the same on VUE 3 but I haven't found the way to do it
Thanks in advance
I found the solution to my answer, mounting a vue component in vue 3 (Outside vue projects) is different than vue 2, this is the approach :
// mount.js
import { createVNode, render } from 'vue'
export const mount = (component, { props, children, element, app } = {}) => {
let el = element
let vNode = createVNode(component, props, children)
if (app && app._context) vNode.appContext = app._context
if (el) render(vNode, el)
else if (typeof document !== 'undefined' ) render(vNode, el = document.createElement('div'))
const destroy = () => {
if (el) render(null, el)
el = null
vNode = null
}
return { vNode, destroy, el }
}
el: DOM element to be appended
vNode: Vue instance
destroy: Destroy the component
This is the way to mount vue 3 components to be appended directly to the DOM, and can be used as below:
// main.js
import { mount } from 'mount.js'
const { el, vNode, destroy } = mount(MyVueComponents,
{
props: {
fields,
labels,
options
},
app: MyVueApp
},
)
$element.html(el);
Hope it Helps, regards!
Just for future visitors to save some time, I was searching for the same answer and found a plug-in that does exactly what Luis explained en his answer at https://github.com/pearofducks/mount-vue-component
Makes it a little simpler to implement.
It is easy to create a new vue3 app and mount to a DOM directly,
const appDef = {
data() {
return {title: 'hello world'};
},
template: '<div>title is: {{title}}</div>',
}
var el = document.createElement('div');//create container for the app
const app = Vue.createApp(appDef);
app.mount(el);//mount to DOM
//el: DOM element to be appended
console.log(el.innerHTML);//title is: hello world

Vue.js: instantiate functional component programmatically

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.