Vue Router Data Loaders #460
Replies: 16 comments 33 replies
-
Great proposal! Love it! I would like to know in SSR, how to define "Do wait for loader on initial load (SSR) but do it lazy on client navigation (SPA)". The idea is on the initial load (initial page visit), I want it to do SSR. But, if the user visits the page via client side navigation (home page -> users page), then I want it to be lazy. This is a bit confusing on Nuxt as well. So for example, can we have option like "lazy on client"? export const useUserData = defineLoader(
async (route) => {
const user = await getUserById(route.params.id)
return user
},
// This means `lazy: false` on SSR, but `lazy: true` on client navigation.
{ lazy: 'client' }
)
I would day go with Loader. It's simple. And technically, it't about loading data. If we use the word "fetch", it kinda feels like it has to perform http request. But that's not requirement right? We could have async but local data loading so I think loader sounds perfect 👍 So I think |
Beta Was this translation helpful? Give feedback.
-
"Motivation" section doesn't discuss the alternative where pages are simply async components and you have a <router-view v-slot="{ Component, route }">
<app-loading v-if="navigating" />
<!-- rootLoaded and route.matched[0] shenanigans so that the component is not removed when navigating child routes -->
<suspense v-if="rootLoaded" :key="route.matched[0]?.path ?? route.path">
<component :is="Component" v-anim="{ enter: 'zoomIn', leave: 'zoomOut' }" />
<template #fallback>
<app-loading />
</template>
</suspense>
</router-view> Data loading is a tricky problem that is central to almost all applications, so I applaud the initiative to design a solution for Vue projects. With so many parts, it's likely that some users will want different conventions, have their own way of loading data, or maybe don't want/need any of this at all. Recently we've seen efforts to innovate in the data loading space and I think it's too early to close down the topic into an official built-in data loader design. Also there's a lot to be debated in this design and it wouldn't be surprising that it takes several iterations to nail it down. I think it'd be good to identify (and add) the extensibility points that the core router needs so that such solutions can easily be built in a separate package; then create an official |
Beta Was this translation helpful? Give feedback.
-
The RFC is very insistent on this (which sounds a bit weird) but is a bit unclear/contradictory at places. From Setup section I understand this is not actually a hard requirement of the router, as long as we put the right stuff in the route If I'm correct, it's a requirement for But then later in section Usage outside of page components the RFC is again very strict about this requirement without mentionning |
Beta Was this translation helpful? Give feedback.
This comment was marked as off-topic.
This comment was marked as off-topic.
-
This seems like a great plan - I'm excited to try it out as soon as I can make time! My current use case for this is trying to make my components more functional and testable - i.e. don't fetch the data inside the component, do it in the router and pass it in as a prop. I'm also currently using This rfc seems very quiet, although |
Beta Was this translation helpful? Give feedback.
-
I'm overall a fan of this pattern, great job on this RFC. My biggest wish for this, is that it should improve the existing solutions and there should be a path for users/libraries authors to integrate these changes to make data-fetching better in all applications. As you mentioned, I also feel like the most important will be Nuxt, Vue-query and Apollo. For Nuxt, there's might be room for simplification : here's a list of all the data fetching composable that are currently available :
All of these are somehow connected to Suspense and use $fetch so that in SSR the internal requests become functions calls. It would be great if Nuxt and this RFC would line-up to integrate and simplify all of this. Regarding 3rd party libraries, the one that I'm the most familiar with is vue-query, and I'm not sure how it would work. As a user, I would definitely want to leverage all Something like this : <script lang="ts">
import { getUserById } from '../api'
export const useUserData = defineLoader(
async (route) => {
const user = await getUserById(route.params.id)
return user
},
{ lazy: true }
)
</script>
<script setup>
useQuery({ queryKey: ['todos', todoId], queryFn: () => useUserData(todoId) })
</script> But this wouldn't work, as there's a need for a way to "connect" both, so that the fetching is triggered by the navigation and as well as useQuery (for example for the background fetching. Note that vue-query can be used with SSR/Nuxt and Supense. It would be great if there was a straightforward path for library authors/maintainers to integrate with these new capabilities, instead of having them compete against each other. Edit : I just saw your notes here and it looks great. Lastly, you mentioned that data loaders and suspense are not mutually exclusive and that they can be used together, could you provide an example of how to do so ? Is it recommended, what would be the benefits, etc. |
Beta Was this translation helpful? Give feedback.
-
To give an update: I'm revisiting a lot of this and have a version that is working without the cache features. The goal is to also have a lower level API oriented to lib maintainers to create their own loaders. There are a few things that are outdated in the RFC like the inability to use |
Beta Was this translation helpful? Give feedback.
-
I see how requiring Why not just checking if a given export name (or component definition key, for non-lazy components) exist on the loaded components? When is it not a good idea? As far as I know, it's how other frameworks implement Couldn't we just look for something like this? const useApi = defineLoader({ … })
export const loaders = [useApi] Or using a compiler macro (we could use a less confusing pair of names): const useApi = defineLoader({ … })
defineLoaders([useApi]) |
Beta Was this translation helpful? Give feedback.
-
Looks awesome! |
Beta Was this translation helpful? Give feedback.
-
I don't understand why is it up to the developer to create keys. Unique string id's can be created cheaply. |
Beta Was this translation helpful? Give feedback.
-
I think is a good idea to pass in navigation result as a second param. // import { NavigationResult } from 'vue-router'
export const useUserData = defineLoader(
async ({ params, path ,query, hash }, createNavigationResult) => {
try {
const user = await getUserById(params.id)
return user
} catch (error) {
if (error.status === 404) {
return createNavigationResult({ name: 'not-found', params: { pathMatch: } }
)
} else {
throw error // aborts the vue router navigation
}
}
}
) |
Beta Was this translation helpful? Give feedback.
This comment was marked as off-topic.
This comment was marked as off-topic.
-
I rewrote the RFC and published it as docs at https://uvr.esm.is/rfcs/data-loaders/. The list of changes is not short and can be found at https://github.com/posva/unplugin-vue-router/blob/main/CHANGELOG.md#breaking-changes. I think I addressed many of the points people made (again, thanks a lot for the feedback 🙏 ) and if I forgot anything or if you have new ideas, please let me know! |
Beta Was this translation helpful? Give feedback.
-
To be honest the extra <script setup lang="ts">
import { ref } from "vue";
const inheritProps = ref(true);
defineOptions({ inheritProps });
</script>
|
Beta Was this translation helpful? Give feedback.
-
Wanted to test it out and maybe use it in our project but I'm stuck. |
Beta Was this translation helpful? Give feedback.
-
Basic example
📖 See the full rendered proposal at https://uvr.esm.is/data-loaders/
Beta Was this translation helpful? Give feedback.
All reactions