VueJS - SSR - Wait for async data in created() hook before render - vue.js

I have a VueJS microfrontend that is SSR thanks to a Ara Framework cluster.
In order to make it fully autonomous I would like it to fetch data in the created() hook before the render occurs so that I have a fully complete DOM rendered in SSR.
Logging showed the problem is created() does not wait for my axios data call to end wheter I use async/await, then() or a classic Promise. Then it either fails on DOM or renders a empty dom if I add a v-if="data"
The idea would be to have a functionnal asyncData() like in Nuxt without to have using the whole framework as I have Ara Framework already applying SSR to my Vue app.
created() {
return new Promise(resolve => {
console.log('created')
const { interfaceIn, InterfaceEnum } = this.interfaceService
console.log('1')
if (this.hydratation) {
this.article = interfaceIn(InterfaceEnum.ARTICLE, this.hydratation)
} else {
console.log('1,5')
this.utils.api
.mainGet(
axios,
this.$utils.api.getHostByContext('ctx1', 'ctx2'),
`/news/${this.slug}`,
'Article'
)
.then(data => {
console.log('2')
this.article = interfaceIn(InterfaceEnum.ARTICLE, data.article)
console.log('3')
resolve()
})
console.log('4')
// this.article = interfaceIn(InterfaceEnum.ARTICLE, article)
console.log(this.article.id)
}
})
// this.loading = false
// this.loading = true
}
console output on SSR http call
HTTP call used with ara Framework
Minimal repro is quite complex to provide as it requires the microfrontend + working ara framework cluster for SSR
Thanks

Answer was to use the serverPrefetch() lifeCycle hook designed for this.

Related

Forward basiauth to axios

I have a site which makes an Axios request. Both the backend and vuejs frontend are on the same domain, and have the same basic auth covering them.
The issue is that whilst the pages load, as soon as an Axios request is made, it asks me again for the basic auth, which doesn't even work if I fill in the details.
Now I imagine I need to pass through the basic auth details somehow, but none of the things I have tried work (and example being below).
If anyone has any tips on passing through the auth token from the parent page to the axios request, that would be great.
const requestOne = axios.get(requestUrl)
const requestTwo = axios.get(requestUrl)
axios
.all([requestOne, requestTwo])
.then(
axios.spread((...responses) => {
<some code here>
})
)
I just answered a similar question with the 3 ways to pass around data in Vue.
You might find it helpful: How to pass v-for index to other components
However, in my opinion, the best approach would be to create a Vue plugin with your Axios client and an init method.
Consider this following (untested) example:
axiosClient.js
import Vue from 'vue';
let instance;
export const getInstance = () => instance;
export const useAxios = () => {
if (instance) return instance;
instance = new Vue({
data() {
return {
client: null,
}
}
});
methods: {
init(authToken) {
this.client = axios.create({
headers: {'Authorization': authToken }
});
}
}
}
export const axiosPlugin = {
install(Vue) {
Vue.prototype.$axios = useAxios();
},
};
Vue.use(axiosPlugin);
Once installed, you can access this in your components using $axios.init(...) and $axios.client.
You can even write API methods directly onto the plugin as well and interact with Vuex through the plugin!
You may need to tweak the plugin a little (and keep in mind this is Vue2 syntax) as I wrote this directly into StackOverflow.
You can also pass any other default values or configuration options through to the axios client by providing options to the plugin and accessing them within init.
You can learn more about plugins here: https://v2.vuejs.org/v2/guide/plugins.html

Access data model in VueJS with Cypress (application actions)

I recently came across this blog post: Stop using Page Objects and Start using App Actions. It describes an approach where the application exposes its model so that Cypress can access it in order to setup certain states for testing.
Example code from the link:
// app.jsx code
var model = new app.TodoModel('react-todos');
if (window.Cypress) {
window.model = model
}
I'd like to try this approach in my VueJS application but I'm struggling with how to expose "the model".
I'm aware that it's possible to expose the Vuex store as described here: Exposing vuex store to Cypress but I'd need access to the component's data().
So, how could I expose e.g. HelloWorld.data.message for being accessible from Cypress?
Demo application on codesandbox.io
Would it be possible via Options/Data API?
Vue is pretty good at providing it's internals for plugins, etc. Just console.log() to discover where the data sits at runtime.
For example, to read internal Vue data,
either from the app level (main.js)
const Vue = new Vue({...
if (window.Cypress) {
window.Vue = Vue;
}
then in the test
cy.window().then(win => {
const message = win.Vue.$children[0].$children[0].message;
}
or from the component level
mounted() {
if (window.Cypress) {
window.HelloWorld = this;
}
}
then in the test
cy.window().then(win => {
const message = win.HelloWorld.message;
}
But actions in the referenced article implies setting data, and in Vue that means you should use Vue.set() to maintain observability.
Since Vue is exposed on this.$root,
cy.window().then(win => {
const component = win.HelloWorld;
const Vue = component.$root;
Vue.$set(component, 'message', newValue);
}
P.S. The need to use Vue.set() may go away in v3, since they are implementing observability via proxies - you may just be able to assign the value.
Experimental App Action for Vue HelloWorld component.
You could expose a setter within the Vue component in the mounted hook
mounted() {
this.$root.setHelloWorldMessage = this.setMessage;
},
methods: {
setMessage: function (newValue) {
this.message = newValue;
}
}
But now we are looking at a situation where the Cypress test is looking like another component of the app that needs access to state of the HelloWorld.
In this case the Vuex approach you referenced seems the cleaner way to handle things.

In vue.js, where can I place "app initialization" code?

Specifically, code that runs before the app actually loads. I'm using vuex and the first thing I want to do (regardless of what route the user is on) is to dispatch a getUser action to get currently user details from the API (or alternatively, redirect if not authenticated).
If I place it in my App.vue mounted component, I believe it might be too late? Don't children components load before parents?
If I get it right you want to do something before the application initialize. For that you can just perform async method in app initialization. Something like that as an example:
function initializeApp (vueCreated) {
return new Promise((resolve, reject) => {
switch (vueCreated) {
case false: // "prevue" initialization steps
console.log('vue not yet created, prevue steps happens')
// ...
setTimeout(_ => resolve(), 3500) // async call
break;
case true: // we can continue/prepare data for Vue
console.log('vue created, but waiting for next initialization steps and data')
// ...
setTimeout(_ => resolve('Mounted / shown when app ready'), 3500) // async call
}
})
}
initializeApp(false).then(_ => {
new Vue({
template: '#app',
data: {
content: null
},
async created () {
this.content = await initializeApp(true)
this.$mount('#app')
console.log('all inicialization steps done, data arrived, vue mounted')
}
})
})
I have found some article related to your question may be this help you out. Link
If you are using vue-router you can use beforeEach to prevent some routes of unauthenticated users.
You can read more here.
If you get stuck here provide code what you tried with router.
Also good example of using navigation guards.
Good luck!

Share code in asyncData and mounted using Nuxt

I want to share same code block to get data in asyncData and mounted using Nuxt.
For example in asyncData
async asyncData({ $api, $auth, route, error, store }) {
if( !process.server ) return null;
let res = {};
let stockData = await $api.stocks.getStock(route.params.name);
if( stockData ) { res.stockData = stockData.data; }
return res;
},
And in mounted()
if(ObjectHelper().isEmpty( this.stockData )) {
this.$api.stocks.getStock(route.params.name).then(res => {
this.stockData = res.data;
})
}
Those two code blocks are all getting data from server-side. How to write a common function to reuse it but not write twice? In nuxt documents, You do NOT have access of the component instance through this inside asyncData because it is called before initiating the component.
Regarding your comment to have the API call server and client-side, which is the reason for your question - it is not necessary to duplicate or share it within mounted as asyncDatagets called server and client-side. You will find following in the nuxt docs:
asyncData is called every time before loading the page component. It
will be called server-side once (on the first request to the Nuxt app)
and client-side when navigating to further routes.
That means - lets say you have your asyncData on page A and a user enters your site using page B and navigates client-side via a nuxt-link from page B to page A it will fire asyncData client-side too before initializing the page component.

how to make a component to get async data and to render server side?

We know that only router component may request asyncData in a ssr environment. i have one main component(not router component), i need some async data to render server side. because it is not router component, so i can't use asyncData for server side rendering.
so i have used created hook for calling async api, but component hook is synchronous and don't wait for promise. what to do for getting async data on server side?
App.vue - Main Component
export default {
components: {
footer: Footer,
header: Header,
selector: selector
},
beforeMount() {
// need preload metadata here
},
created () {
// it return preload metadata as response.
return this.$store.dispatch('GET_PRELOAD_META_DATA');
}
}
Action.js
GET_PRELOAD_META_DATA: ({ commit }) => {
return axios.get("api/preload").then(({ data }) => {
commit('SET_PRELOAD_DATA', data);
});
},
As of Vue 2.6, there is a new SSR feature that may accomplish what you’re trying to do. ServerPrefetch is now a hook that can resolve promises to get async data and can interact with a global store.
Check out more in their documentation. https://ssr.vuejs.org/api/#serverprefetch
(I know this an old post, but I stumbled upon it while googling and thought I might be able to help someone else googling too)