Lazy loading & loading states with vue-router, vite & vuejs 2.x - vue.js

I'm migrating an old project from vue-cli to vite. I followed the migration guide and everything worked great, but there's something it's not working, or at least, not as intended, when I was using vue-cli I tried to implement the loading states as shown in their documentation but then I saw the following pull request explaining how to achieve the wanted behavior (with the downside of losing navigation guards on those routes).
Now, after migrating I noticed that neither the loading/error components are rendered at all, even setting a timeout really small, however, I see in the networking tab that my components are being loaded, but never rendered.
Do you have any suggestions of why might this occur?.
// This is the lazyLoadView that was working before migrating to vite.
function lazyLoadView(AsyncView) {
const AsyncHandler = () => ({
component: AsyncView,
// A component to use while the component is loading.
loading: import("./components/loaders/loader.vue").default,
// A fallback component in case the timeout is exceeded
// when loading the component.
error: import("./components/loaders/error.vue").default,
// Delay before showing the loading component.
// Default: 200 (milliseconds).
delay: 1,
// Time before giving up trying to load the component.
// Default: Infinity (milliseconds).
timeout: 2,
});
return Promise.resolve({
functional: true,
render(h, { data, children }) {
// Transparently pass any props or children
// to the view component.
return h(AsyncHandler, data, children);
},
});
}
And the routes I have:
const routes = [
{
path: "/foo/",
component: () => lazyLoadView(import("./components/bar.vue")),
}
]
Let me know if you find why might this be happening.

So I figured out:
Looks like the loading & error components were also lazy loaded, they were skipped. So continued trying to obtain the main one until shown (that's why didn't render the loading besides of only showing a message).
So in order to fix it I had to import them at the top and include them in the lazyLoadView like this:
//These two imports at the top
import loaderComp from "./components/loaders/loader.vue";
import errorComp from "./components/loaders/error.vue";
function lazyLoadView(AsyncView) {
const AsyncHandler = () => ({
component: AsyncView,
// A component to use while the component is loading.
// must NOT be lazy-loaded
loading: loaderComp,
// A fallback component in case the timeout is exceeded
// when loading the component.
// must NOT be lazy-loaded
error: errorComp,
// Delay before showing the loading component.
// Default: 200 (milliseconds).
delay: 1,
// Time before giving up trying to load the component.
// Default: Infinity (milliseconds).
timeout: 2,
});
return Promise.resolve({
functional: true,
render(h, { data, children }) {
// Transparently pass any props or children
// to the view component.
return h(AsyncHandler, data, children);
},
});
}

Related

How to get SPA navigation working with external framework that uses innerHTML for content

In my Vue.js app, I am using a bootstrap-based framework that generates the html for my header and a menu nav with links, which is then inserted into the page by assigning innerHTML to a mount point.
But when I use the generated content to navigate, the entire page reloads since the links aren't using <router-link>.
One attempt at a fix:
In the Vue app, I assigned a method called goto on the window object that would perform programmatic router navigation.
I was then able to pass javascript:window.goto("myPageName"); as the href attribute, but this comes with many undesirable side-effects.
How can I cleanly make the links navigate without reloading the page?
(The framework needs jQuery as a dependency, so that is able to be used in a solution.)
I was able to use a MutationObserver that watches for subtree changes and adds a custom click handler when it detects the links being added via .innerHTML.
With this method, I specify vue-goto:myPageName as the href attribute, and then the handler will take care of making it an SPA link.
import { router } from "#/router";
import { store } from "#/store";
export const attrib = "vue-goto";
export const prefix = attrib + ":";
function onChange() {
// find all links matching our custom prefix to which we have not yet added our custom handler
const links = window.$(`a[href^='${prefix}']`).not(`[${attrib}]`);
// add custom attribute for us to grab later
links.attr(attrib, function() {
// jQuery doesn't like arrow functions
return window
.$(this)
.attr("href")
.substr(prefix.length)
.trim();
});
// Update href on the link to one that makes sense
links.attr("href", function() {
return router.resolve({
name: window.$(this).attr(attrib), // grab attribute we saved earlier
params: { lang: store.state.language }, // in our case, our pages are qualified by a language parameter
}).href;
});
// Override default click navigation behaviour to use vue-router programmatic navigation
links.click(function(e) {
e.preventDefault(); // prevent default click
const routeName = window.$(this).attr(attrib);
const goto = {
name: routeName,
lang: store.state.language,
};
router.push(goto).catch(ex => {
// add catch here so navigation promise errors aren't lost to the void causing headaches later
// eslint-disable-next-line no-console
console.error(
`Error occurred during navigation from injected [${prefix}${routeName}] link`,
"\n",
ex,
);
});
});
}
let observer;
export function init() {
if (observer) observer.unobserve(document.body);
observer = new MutationObserver(onChange);
observer.observe(document.body, {
characterData: false,
childList: true,
subtree: true, // important, we want to see all changes not just at toplevel
attributes: false,
});
}
init();

Page reload causes Vuex getter to return undefined

Using Vue.js (Vuetify for FE).
A page reload causes the getter in Vuex to fail with pulling required data from the store. The getter returns undefined. The code can be found on GitHub at: https://github.com/tineich/timmyskittys/tree/master/src
Please see the full details on this issue at timmyskittys.netlify.com/stage1. This page has complete info on the issue and instructions on how to view the issue.
Note, there is mention of www.timmyskittys.com in the issue description. This is the main site. timmyskittys.netlify.com is my test site. So, they are the same for all intents and purposes. But, my demo of this issue is at the Netlify site.
I read the complete issue in the website you mentioned. It's a generic case.
Say, for cat details page url: www.timmyskittys.com/stage2/:id.
Now in Per-Route Guard beforeEnter() you can set the cat-id in store. Then from your component call the api using the cat-id (read from getters)
I found the solution to my issue:
I had to move the call of the action which calls the mutation that loads the .json file (dbdata.json) into a computed() within App.vue. This was originally done in Stage1.vue.
Thanks all for responding.
I had the same issue and my "fix" if it can be called that was to make a timer, so to give the store time to get things right, like so:
<v-treeview
:items="items"
:load-children="setChildren"
/>
</template>
<script>
import { mapGetters } from 'vuex'
const pause = ms => new Promise(resolve => setTimeout(resolve, ms))
export default {
data () {
return {
children: []
}
},
computed: {
...mapGetters('app', ['services']),
items () {
return [{
id: 0,
name: 'Services',
children: this.children
}]
}
},
methods: {
async setChildren () {
await pause(1000)
this.children.push(...this.services)
}
}
}
</script>
Even though this is far from ideal, it works.

How to watch on Route changes with Nuxt and asyncData

Hi everybody i'm trying to watch on route changes in my nuxt js app.
Here my middleware:
export default function ({ route }) {
return route; but i don't know what to write here
}
index.vue File
middleware: [routeReact]
i'm trying to write this:
app.context.route = route
but it says to me that app.context doesn't exist
Here's the point of my question i'm trying to update my data that gets from my api with axios on page if route changing
like this
this the page
i'm clicking link to next page :
but when i'm route to next page, nothing happens all data is the same:
here my asyncData code:
asyncData({ app }) {
return app.$axios.$get('apps/' + app.context.route.fullPath.replace(/\/categories\/?/, ''))
.then(res => {
return {
info: res.results,
nextPage: res.next,
prevPage: res.prev
};
})
}
Thanks for your help
First thing, context.route or it's alias this.$route is immutable object and should not be assigned a value.
Instead, we should use this.$router and it's methods for programmatic navigation or <nuxt-link> and <router-link>.
As I understand, you need to render the same route, but trigger asyncData hook in order to update component's data. Only route query is changed.
Correct way to navigate to the same page but with different data is to use link of such format:
<nuxt-link :to="{ name: 'index', query: { start: 420 }}"
Then you can use nuxt provided option watchQuery on page component and access that query inside asyncData as follows:
watchQuery: true,
asyncData ({ query, app }) {
const { start } = query
const queryString = start ? `?start=${start}` : ''
return app.$axios.$get(`apps/${queryString}`)
.then(res => {
return {
info: res.results,
nextPage: res.next,
prevPage: res.prev
}
})
},
This option does not require usage of middleware. If you want to stick to using middleware functions, you can add a key to layout or page view that is used. Here is an example of adding a key to default layout:
<nuxt :key="$route.fullPath" />
This will force nuxt to re-render the page, thus calling middlewares and hooks. It is also useful for triggering transitions when switching dynamic routes of the same page component.

Vue: same route same component, but different params

I'm creating a SPA where after each HTTP response, the app goes to the same component with the same route, but the props passed to the component will be different based on the response. I understand the reuse feature in Vue and it won't reload. But I need the newly returned data every time I trigger the router to go to the component. My app.vue:
upload(formData).then(x => {
router.push({
name: 'Chart',
params: {
chartData: x
}
});
});
Then the target Chart.vue:
props: ['chartData'],
data: function() {
// do some processing using props
return {...}
}
My problem is that since for my router, the route doesn't change, hence other approaches like using :key=$route.fullPath or the beforeRouteUpdate don't work. Then how can I make the Chart component recompute the data? I've checked thru computed() and watch as well but it's not working.

VueJS - requiring and rendering a component programmatically

I have a bit of a specific usecase here. I have a large Vue app with lots of pages. Each page has a page-id to identify it on the server and load it's content. These ids are in the router path (parent-id/child-id/another-child-page-id). On the page components themselves I display a page title. Unfortunately, there is no correlation between the translation key of the page title to the page-id.
Now, instead of creating a mapping between page-ids and page title translations that I will have to maintain by hand, I figured I could lookup the route in the router config and require the component:
{
path: 'the-page',
name: 'ThePage',
component: resolve => require.ensure([], () => resolve(require('#/pages/ParentPage/ChildPage/ThePage')), 'parent-page')
}
This works by recursively walking through the routes to find the page-id, then loading the component in a promise:
// find the route definition
const route = findRouteByPath(file.areaName)
// load the route component
new Promise(resolve => { route.component(resolve) })
.then(component => {
console.log(component)
})
This gives me the component like so:
Object {
mixins: Array(1),
staticRenderFns: Array(0), __file: "/the/file/path.vue",
beforeCreate: Array(1),
beforeDestroy: Array(1),
components: Object,
mixins: Array(1),
render: function (),
// etc.
}
If I call the render function, I get an error:
Uncaught (in promise) TypeError: Cannot read property '_c' of undefined
What I want to do is programmatically render the component and grab the content of the slot page-title from the component.
Can anyone help with this?
Update 1
Vue.extend() then mounting seems to do what I want. The problem is, I get all kinds of errors because the pages depend on certain things being around, like the current route. I'm also a bit worried to trigger certain created() hooks that would trigger API calls and such. My current state is trying to get rid of mixins and hooks like so:
const strippedComponent = Object.assign(component, {
mixins: [],
components: {},
beforeCreate: []
})
const Constructor = Vue.extend(strippedComponent)
const vm = new Constructor({})
But I'm not quite there yet.
Update 2
I ended up abandoning this and bit the bullet by adding a meta: {} object to each route where I store things like the page title translation key and other good stuff.