How to add a component to VueJs3 app before mounting - vue.js

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();

Related

Auto Refresh for Vuex

I would like to implement a auto refresh feature for my VueX store.
Everything the user refresh their browser, an actions in VueX store will be triggered to load the user profile from API call.
Is't possible to achieve that?
import apiService from "#/services/apiService";
import apiUrls from "#/services/apiUrls";
import { getToken } from "#/services/jwtService";
// Code to run actions when user refresh
getToken() !== null ? this.actions.getUserProfile() : "";
const state = {
userProfile: {},
};
const getters = {
userProfile: (state) => state.userProfile,
};
const actions = {
async getUserProfile({ commit }) {
console.log("here");
try {
let response = await apiService.get(apiUrls.PROFILE);
commit("setUserProfile", response.data.data);
} catch (error) {
console.log(error);
}
},
};
Thank you.
A user refresh means that the application will be re-executed. So basically main.js will be re-executed, App.vue re-created, etc.
That means just have to call your code in main.js or in a created lifecycle hook of any top-level component.
By top-level component I means any component which is created early in the 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

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

// 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');
}

Async lifecycle function in Vue Test Utils

When I tried to test the component which has mounted method like this:
mounted(){
this.search()
}
methods:{
async search(){
try{
await axios.something
console.log("not executed only when shallowMount")
}catch{}
}
}
I checked it returned Promise<pending> without await.
I wrote the test like this:
wrapper = await shallowMount(Component, {
localVue
});
await wrapper.vm.search()// this works perfectly
However, only the shallowMount apparently skips awaited function while the next line works perfectly.
I have no idea about this behavior.
How can I fix it?
Edit:
I also use Mirage.js for mocking response.
function deviceServer() {
return createServer({
environment: "test",
serializers: {
device: deviceListSerializer()
},
models: {
device: Model
},
fixtures: {
devices: devices
},
routes() {
this.namespace = "/api/v1/";
this.resource("device");
},
seeds(server) {
server.loadFixtures("devices");
}
});
}
shallowMount is not returning Promise and that's why there is nothing to await. To await promises in tests try to use flush-promises library. With flushPromises your test will look like this:
import flushPromises from 'flush-promises'
wrapper = shallowMount(Component, {
localVue
});
await flushPromises(); // we wait until all promises in created() hook are resolved
// expect...

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