App has already been mounted. nothing show up when open page again - vue.js

// main.js
const app = createApp(App);
app.provide('$axios', axios);
window.renderSomething = function() {
app.mount('#ticker-maintenance-host');
}
the application is developed with vuejs and blarzor. In blazor, will call it
#inject IJSRuntime JS;
#code{
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("renderSomething");
}
}
}
#page "/dashboard/page1"
<div id="ticker-maintenance-host"></div>
first open this page1, all the content can show up. If I leave it and come back to this page, nothing show up. There is a warning saying
"[Vue warn]: App has already been mounted.
If you want to remount the same app, move your app creation logic into a factory function and create fresh app instances for each mount - e.g. `const createMyApp = () => createApp(App)`".
How can I fix this warning and make my content show up without refresh when coming back.
Thanks

You are mounting your App to the ID #ticker-maintenance-host
But all I can see in your HTML is an ID container.
Mount your app to the container, or add the ID #ticker-maintenance-host to your HTML.
#Edit:
You dont need to mount your view app on every render, just when the website is loaded initially.
Something like this straight out of the docs for Vue3 is how you register a vue app:
const { createApp, ref, computed } = Vue;
const app = createApp({
setup() {
const someValue = ref(10);
const someComputed = computed(() => someValue.value * 10);
return {
someValue,
someComputed
}
}
});
app.mount("#ticker-maintenance-host");
More reading here: Vue.createApp is not working but Is working with new Vue() method

I have fixed it by moving all the statements inside function block.
// main.js
window.renderSomething = function() {
const app = createApp(App);
app.provide('$axios', axios);
app.mount('#ticker-maintenance-host');
}

Related

Rendered hook / serverPrefetch Vue3 SSR

I've been trying to make a Vue3 app work with SSR and I'm stuck in the serverPrefetch part. I want to load some data in the server, save it in the store and pass it to the client but it looks like the rendered hook referred to here: https://ssr.vuejs.org/guide/data.html#final-state-injection is not called anymore (and therefore the server store is not updated after the data fetch). Do you know what the right way to do this with Vue3 is?
This is my server-entry.js
import { _createApp } from "./app";
export default async (context) => {
const { app, router, store } = _createApp();
router.push(context.url); // push router entry
await router.isReady();
context.rendered = () => {
// this is never executed!!!
context.state = store.state; // this gets embedded in the final HTML as window.__INITAL_STATE__
};
return app;
};

Provide Inject not working properly in vue 3 composition API

I am working with Vue 3 composition api and am retrieving weather data via async/await fetch and I get a 200 response and the data in the request within the Chrome Dev Tools.
In the component receiving the data and making the call I have a provide method and then I am injecting the data into another output component. The issue is in the inject component. The value for the injected variable is always null and does not update in the Vue Dev Tools so my data is never output to the screen. I went through the docs and the code is pretty much the same but I can't get it to work. Can anyone see an obvious issue?
Receiving Component
setup () {
async function getCurrentWeather () {
const response = await fetch(`${baseWeatherApiUrl}q=${userInput.value}`);
userInput.value = null;
return weatherData.value = await response.json();
}
const returnedWeatherData = reactive(weatherData);
provide('returnedWeatherData', returnedWeatherData);
return {
getCurrentWeather,
userInput,
weatherData
}
}
output component
setup () {
//Provide default of empty object in case no results exist
const weatherData = inject('returnedWeatherData');
console.log(weatherData) //No output even when making a new request to the weather api
return {
weatherData
}
}
As a separate test I tried to provide/inject hardcoded values found in the docs but still geolocation when injected remains null.
provide('geolocation', {
longitude: 90,
latitude: 135
})
const userGeolocation = inject('geolocation')
console.log(userGeolocation) // Nothing logged
return {
weatherData,
userGeolocation
}
In my case it was importing inject from "#vue/runtime-core" instead of "vue".
Of course provide was imported from "vue".
Just leaving here, maybe it's gonna save someone an hour.
The provide-ed argument should be the ref itself (not wrapped in a reactive()):
// Parent.vue
export default {
setup () {
const weatherData = ref()
// ❌
// const returnedWeatherData = reactive(weatherData);
// provide('returnedWeatherData', returnedWeatherData);
// ✅
provide('returnedWeatherData', weatherData);
}
}
And the child component's console.log() in setup() does not automatically get invoked again. You should wrap that call with watchEffect() so that it does get called upon change to the ref:
// Child.vue
import { inject, watchEffect } from 'vue'
export default {
setup () {
const weatherData = inject('returnedWeatherData')
// ❌
//console.log('new weatherData', weatherData.value)
// ✅
watchEffect(() => {
console.log('new weatherData', weatherData.value)
})
}
}
demo

How to add a component to VueJs3 app before mounting

I have set up chartApp as my application like so:
const chartApp = {
data(){
...
},
methods:{
async init() {
//await async menthod
}
}
}
const app = Vue.createApp(chartApp).mount('#app');
app.init();
Which works.
I can go into the browser console, type app. and see the init function
I'm wanting to register a component. I've tried to set the app like this:
const chartApp = {
data(){
...
},
methods:{
async init() {
//await async menthod
}
}
}
const app = Vue.createApp(chartApp);
app.component('my-component', {
template:'<div>hello</div>'
});
app.mount('#app');
app.init();
But I receive an error stating
app.init is not a function
this time when I look at my browser console, and type app. ...I see an option to mount / unmount, but not init function.
I thought, maybe the app had failed to mount, so I tried:
app.mount('#app')
in the console but received the following warning:
App has already been mounted.
How am I able to register a component please? VueJs3 version is 3.2.16.
In the 1st case you are calling your init method on the result of the mount function.
But in the second case on the result of createApp....which is a very different object
Do this:
const app = Vue.createApp(chartApp);
app.component('my-component', {
template:'<div>hello</div>'
});
const mountedApp = app.mount('#app');
mountedApp.init();

Electron Ipc render emit event to Vuex

Hi guys I'm building an app with multiple windows,
My App is developed in Vue + Electron
The main feature I'm trying to accomplish is when the user for example wants to do some action in electron open popup for example, and next I want to send action to Vuex store to display message to user
So How can I call Vuex action from electron?
Code Sample:
Vue component:
import { remote, ipcRenderer } from 'electron';
ipcRenderer.send('openPopup', id);
Electron Code:
import { ipcMain, app } from 'electron';
ipcMain.on('openPopup', (event, args) => {
console.log('do some action');
// now how can I call vuex action from here
});
I tried calling it with:
this.$store
but its undefined
You could simply return the desired data back to the renderer process in the ipcMain.on event.
I would also recommend using ipcRenderer.invoke / ipcMain.handle (Electron Doc - ipcMain)
// Main process
ipcMain.handle('openPopup', async (event, ...args) => {
const result = await somePromise(...args)
return result
})
// Renderer process
async () => {
const result = await ipcRenderer.invoke('open-popup', id)
// this.$store.dispatch(....) etc etc
}

Vue test-utils how to test a router.push()

In my component , I have a method which will execute a router.push()
import router from "#/router";
// ...
export default {
// ...
methods: {
closeAlert: function() {
if (this.msgTypeContactForm == "success") {
router.push("/home");
} else {
return;
}
},
// ....
}
}
I want to test it...
I wrote the following specs..
it("should ... go to home page", async () => {
// given
const $route = {
name: "home"
},
options = {
...
mocks: {
$route
}
};
wrapper = mount(ContactForm, options);
const closeBtn = wrapper.find(".v-alert__dismissible");
closeBtn.trigger("click");
await wrapper.vm.$nextTick();
expect(alert.attributes().style).toBe("display: none;")
// router path '/home' to be called ?
});
1 - I get an error
console.error node_modules/#vue/test-utils/dist/vue-test-utils.js:15
[vue-test-utils]: could not overwrite property $route, this is usually caused by a plugin that has added the property asa read-only value
2 - How I should write the expect() to be sure that this /home route has been called
thanks for feedback
You are doing something that happens to work, but I believe is wrong, and also is causing you problems to test the router. You're importing the router in your component:
import router from "#/router";
Then calling its push right away:
router.push("/home");
I don't know how exactly you're installing the router, but usually you do something like:
new Vue({
router,
store,
i18n,
}).$mount('#app');
To install Vue plugins. I bet you're already doing this (in fact, is this mechanism that expose $route to your component). In the example, a vuex store and a reference to vue-i18n are also being installed.
This will expose a $router member in all your components. Instead of importing the router and calling its push directly, you could call it from this as $router:
this.$router.push("/home");
Now, thise makes testing easier, because you can pass a fake router to your component, when testing, via the mocks property, just as you're doing with $route already:
const push = jest.fn();
const $router = {
push: jest.fn(),
}
...
mocks: {
$route,
$router,
}
And then, in your test, you assert against push having been called:
expect(push).toHaveBeenCalledWith('/the-desired-path');
Assuming that you have setup the pre-requisities correctly and similar to this
Just use
it("should ... go to home page", async () => {
const $route = {
name: "home"
}
...
// router path '/home' to be called ?
expect(wrapper.vm.$route.name).toBe($route.name)
});