Skip to content

Commit

Permalink
feat: track used params
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Feb 8, 2024
1 parent f3750d4 commit b2ae763
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 17 deletions.
1 change: 1 addition & 0 deletions playground/src/pages/users/colada-loader.[id].vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const useUserData = defineColadaLoader('/users/colada-loader.[id]', {
console.log('[🍹] key', to.fullPath)
return ['loader-users', to.params.id]
},
staleTime: 10000,
})
</script>

Expand Down
2 changes: 1 addition & 1 deletion src/data-fetching_new/defineColadaLoader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ describe(
}
}

it.todo('avoids refetching fresh data when navigating', async () => {
it('avoids refetching fresh data when navigating', async () => {
const query = vi.fn().mockResolvedValue('data')
const useData = defineColadaLoader({
query,
Expand Down
69 changes: 53 additions & 16 deletions src/data-fetching_new/defineColadaLoader.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRoute, useRouter } from 'vue-router'
import { useRoute, useRouter, type LocationQuery } from 'vue-router'
import type {
_RouteLocationNormalizedLoaded,
_RouteRecordName,
Expand Down Expand Up @@ -26,7 +26,9 @@ import {
IS_CLIENT,
assign,
getCurrentContext,
isSubsetOf,
setCurrentContext,
trackRoute,
} from './utils'
import { Ref, ShallowRef, ref, shallowRef } from 'vue'
import { NavigationResult } from './navigation-guard'
Expand Down Expand Up @@ -94,6 +96,7 @@ export function defineColadaLoader<Data, isLazy extends boolean>(
DataLoaderColadaEntry<boolean, unknown>
>
const key = keyText(options.key(to))
const [trackedRoute, params, query, hash] = trackRoute(to)
if (!entries.has(loader)) {
const pendingTo = shallowRef<_RouteLocationNormalizedLoaded>(to)
entries.set(loader, {
Expand All @@ -113,6 +116,7 @@ export function defineColadaLoader<Data, isLazy extends boolean>(
// @ts-expect-error: FIXME: once pendingTo is removed from DataLoaderEntryBase
commit,

tracked: new Map(),
ext: null,

pendingTo,
Expand All @@ -133,16 +137,25 @@ export function defineColadaLoader<Data, isLazy extends boolean>(
entry.ext = useQuery({
...options,
// FIXME: type Promise<Data> instead of Promise<unknown>
query: () =>
// TODO: run within app context?
loader(entry.pendingTo.value, {
signal: entry.pendingTo.value.meta[ABORT_CONTROLLER_KEY]!.signal,
}),
query: () => {
const route = entry.pendingTo.value
const [trackedRoute, params, query, hash] = trackRoute(route)
entry.tracked.set(options.key(trackedRoute).join('|'), {
ready: false,
params,
query,
hash,
})

return loader(trackedRoute, {
signal: route.meta[ABORT_CONTROLLER_KEY]!.signal,
})
},
key: () => options.key(entry.pendingTo.value),
})
}

const { error, isLoading, data, ext } = entry
const { isLoading, data, ext } = entry

// we are rendering for the first time and we have initial data
// we need to synchronously set the value so it's available in components
Expand All @@ -163,8 +176,9 @@ export function defineColadaLoader<Data, isLazy extends boolean>(
// TODO: test
entry.pendingTo.value.meta[ABORT_CONTROLLER_KEY]!.abort()
// ensure we call refetch instead of refresh
// TODO: only if to is different from the pendintTo **consumed** properties
reload = true
// TODO: only if to is different from the pendingTo **consumed** properties
const tracked = entry.tracked.get(key.join('|'))
reload = !tracked || hasRouteChanged(to, tracked)
}

// Currently load for this loader
Expand All @@ -187,13 +201,14 @@ export function defineColadaLoader<Data, isLazy extends boolean>(

const currentLoad = ext[reload ? 'refetch' : 'refresh']()
.then((d) => {
console.log(
`✅ resolved ${key}`,
to.fullPath,
`accepted: ${
entry.pendingLoad === currentLoad
}; data:\n${JSON.stringify(d)}\n${JSON.stringify(ext.data.value)}`
)
// console.log(
// `✅ resolved ${key}`,
// to.fullPath,
// `accepted: ${
// entry.pendingLoad === currentLoad
// }; data:\n${JSON.stringify(d)}\n${JSON.stringify(ext.data.value)}`
// )
console.log(`👀 Tracked stuff`, entry.tracked)
if (entry.pendingLoad === currentLoad) {
// propagate the error
if (ext.error.value) {
Expand Down Expand Up @@ -264,6 +279,7 @@ export function defineColadaLoader<Data, isLazy extends boolean>(
to.meta[NAVIGATION_RESULTS_KEY]!.push(this.staged)
} else {
this.data.value = this.staged
this.tracked.get(key.join('|'))!.ready = true
}
}
// The navigation was changed so avoid resetting the error
Expand Down Expand Up @@ -432,12 +448,33 @@ export interface DataLoaderColadaEntry<isLazy extends boolean, Data>
pendingTo: ShallowRef<_RouteLocationNormalizedLoaded>
_pendingTo: _RouteLocationNormalizedLoaded | null

tracked: Map<string, TrackedRoute>

/**
* Extended options for pinia colada
*/
ext: UseQueryReturn<Data> | null
}

interface TrackedRoute {
ready: boolean
params: Partial<LocationQuery>
query: Partial<LocationQuery>
hash: { v: string | null }
}

function hasRouteChanged(
to: _RouteLocationNormalizedLoaded,
tracked: TrackedRoute
): boolean {
return (
!tracked.ready ||
!isSubsetOf(tracked.params, to.params) ||
!isSubsetOf(tracked.query, to.query) ||
(tracked.hash.v != null && tracked.hash.v !== to.hash)
)
}

const DEFAULT_DEFINE_LOADER_OPTIONS = {
lazy: false,
server: true,
Expand Down
77 changes: 77 additions & 0 deletions src/data-fetching_new/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { DataLoaderEntryBase, UseDataLoader } from './createDataLoader'
import { IS_USE_DATA_LOADER_KEY } from './meta-extensions'
import { type _Router } from '../type-extensions/router'
import { type _RouteLocationNormalizedLoaded } from '../type-extensions/routeLocation'
import { type LocationQuery } from 'vue-router'

/**
* Check if a value is a `DataLoader`.
Expand Down Expand Up @@ -57,3 +58,79 @@ export const assign = Object.assign
* @internal
*/
export type _MaybePromise<T> = T | Promise<T>

/**
* Track the reads of a route and its properties
* @internal
* @param route - route to track
*/
export function trackRoute(route: _RouteLocationNormalizedLoaded) {
const [params, paramReads] = trackObjectReads(route.params)
const [query, queryReads] = trackObjectReads(route.query)
let hash: { v: string | null } = { v: null }
return [
{
...route,
// track the hash
get hash() {
return (hash.v = route.hash)
},
params,
query,
},
paramReads,
queryReads,
hash,
] as const
}

/**
* Track the reads of an object (that doesn't change) and add the read properties to an object
* @internal
* @param obj - object to track
*/
function trackObjectReads<T extends Record<string, unknown>>(obj: T) {
const reads: Partial<T> = {}
return [
new Proxy(obj, {
get(target, p: Extract<keyof T, string>, receiver) {
const value = Reflect.get(target, p, receiver)
reads[p] = value
return value
},
}),
reads,
] as const
}

/**
* Returns `true` if `inner` is a subset of `outer`. Used to check if a tr
*
* @internal
* @param outer - the bigger params
* @param inner - the smaller params
*/
export function isSubsetOf(
inner: Partial<LocationQuery>,
outer: LocationQuery
): boolean {
for (const key in inner) {
const innerValue = inner[key]
const outerValue = outer[key]
if (typeof innerValue === 'string') {
if (innerValue !== outerValue) return false
} else if (!innerValue || !outerValue) {
// if one of them is undefined, we need to check if the other is undefined too
if (innerValue !== outerValue) return false
} else {
if (
!Array.isArray(outerValue) ||
outerValue.length !== innerValue.length ||
innerValue.some((value, i) => value !== outerValue[i])
)
return false
}
}

return true
}

0 comments on commit b2ae763

Please sign in to comment.