Skip to content

Commit

Permalink
wip: handling params update
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Aug 2, 2022
1 parent 30f2c6f commit 71d5a21
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 40 deletions.
11 changes: 7 additions & 4 deletions src/data-fetching/dataCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,17 @@ export function isDataCacheEntryLazy<T = unknown>(

export function isCacheExpired(
entry: DataLoaderCacheEntry,
{ cacheTime }: Required<DefineLoaderOptions>
) {
// TODO: check entry.loaders
options: Required<DefineLoaderOptions>
): boolean {
const { cacheTime } = options
return (
// cacheTime == 0 means no cache
!cacheTime ||
// did we hit the expiration time
Date.now() - entry.when >= cacheTime
Date.now() - entry.when >= cacheTime ||
Array.from(entry.loaders).some((childEntry) =>
isCacheExpired(childEntry, options)
)
)
}

Expand Down
92 changes: 70 additions & 22 deletions src/data-fetching/defineLoader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,44 +155,92 @@ describe('defineLoader', () => {
})

it('can call nested loaders', async () => {
const spyUser = vi.fn().mockResolvedValue({ name: 'edu' })
const spy = vi
.fn<any[], Promise<{ user: { name: string } }>>()
.mockResolvedValue({ user: { name: 'edu' } })
const useOne = defineLoader(spy)
const useLoader = defineLoader(async () => {
const { user } = await useOne()
// FIXME: can we just return the ref?
return { user: user.value, local: user.value.name }
return { user, local: user.value.name }
})
expect(spy).not.toHaveBeenCalled()
await useLoader._.load(route, router)
expect(spy).toHaveBeenCalledTimes(1)
const { user } = useLoader()
// even though we returned a ref
expectType<{ name: string }>(user.value)
expect(user.value).toEqual({ name: 'edu' })
})

it.todo(
'invalidated nested loaders invalidate a loader (by route params)',
async () => {
const spyUser = vi.fn().mockResolvedValue({ name: 'edu' })
const spy = vi
.fn<any[], Promise<{ user: { name: string } }>>()
.mockResolvedValue({ user: { name: 'edu' } })
const useOne = defineLoader(spy)
const useLoader = defineLoader(async () => {
it('can call deeply nested loaders', async () => {
const one = vi
.fn<any[], Promise<{ user: { name: string } }>>()
.mockResolvedValue({ user: { name: 'edu' } })
const useOne = defineLoader(one)
const two = vi
.fn<any[], Promise<{ user: { name: string }; local: string }>>()
.mockImplementation(async () => {
const { user } = await useOne()
return { user, local: user.value.name }
// force the type for the mock
return {
user: user as unknown as { name: string },
local: user.value.name,
}
})
await useLoader._.load(route, router)
const { user, refresh } = useLoader()
const { invalidate } = useOne()
expect(spy).toHaveBeenCalledTimes(1)
invalidate() // the child
await refresh() // the parent
expect(spy).toHaveBeenCalledTimes(2)
}
)
const useTwo = defineLoader(two)
const useLoader = defineLoader(async () => {
const { user } = await useOne()
const { local } = await useTwo()
return { user, local, when: Date.now() }
})

expect(one).not.toHaveBeenCalled()
expect(two).not.toHaveBeenCalled()
await useLoader._.load(route, router)
expect(one).toHaveBeenCalledTimes(1)
expect(two).toHaveBeenCalledTimes(1)
const { user } = useLoader()
expect(user.value).toEqual({ name: 'edu' })
})

it('invalidated nested loaders invalidate a loader (by cache)', async () => {
const spy = vi
.fn<any[], Promise<{ user: { name: string } }>>()
.mockResolvedValue({ user: { name: 'edu' } })
const useOne = defineLoader(spy)
const useLoader = defineLoader(async () => {
const { user } = await useOne()
return { user, local: user.value.name }
})
await useLoader._.load(route, router)
const { user, refresh } = useLoader()
const { invalidate } = useOne()
expect(spy).toHaveBeenCalledTimes(1)
invalidate() // the child
await refresh() // the parent
expect(spy).toHaveBeenCalledTimes(2)
})

it('invalidated nested loaders invalidate a loader (by route params)', async () => {
const spy = vi
.fn<any[], Promise<{ user: { name: string } }>>()
.mockImplementation(async (route: RouteLocationNormalizedLoaded) => ({
user: { name: route.params.id as string },
}))
const useOne = defineLoader(spy)
const useLoader = defineLoader(async () => {
const { user } = await useOne()
return { user, local: user.value.name }
})
await useLoader._.load(setRoute({ params: { id: 'edu' } }), router)
expect(spy).toHaveBeenCalledTimes(1)
// same id
await useLoader._.load(setRoute({ params: { id: 'edu' } }), router)
expect(spy).toHaveBeenCalledTimes(1)
// same id
await useLoader._.load(setRoute({ params: { id: 'bob' } }), router)
expect(spy).toHaveBeenCalledTimes(2)
})

it('nested loaders changes propagate to parent', async () => {
const spy = vi
Expand Down
29 changes: 15 additions & 14 deletions src/data-fetching/defineLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,12 @@ export function defineLoader<P extends Promise<any>, isLazy extends boolean>(
// throw new Error('noooooo')
// }

if (!cache.has(router)) {
if (
// no cache: we need to load
!cache.has(router) ||
// invoked by the parent, we should load again
parentEntry
) {
load(route, router, parentEntry)
}

Expand All @@ -113,17 +118,11 @@ export function defineLoader<P extends Promise<any>, isLazy extends boolean>(
// }
// }

const promise = Promise.resolve(pendingPromise)
.then(() => {
// we need to get the data property once again because it has been updated
const { data } = entry
return Object.assign(commonData, isRef(data) ? { data } : data)
})
.finally(() => {
// TODO: restore current parent
// parent?.dependOn(me)
setCurrentContext(parentEntry && [parentEntry, router, route])
})
const promise = Promise.resolve(pendingPromise).then(() => {
// we need to get the data property once again because it has been updated
const { data } = entry
return Object.assign(commonData, isRef(data) ? { data } : data)
})

// entry exists because it's created synchronously in `load()`
const { data, pending, error } = entry
Expand Down Expand Up @@ -178,6 +177,8 @@ export function defineLoader<P extends Promise<any>, isLazy extends boolean>(
const entry = cache.get(router)!
const { lazy } = options

const isExpired = isCacheExpired(entry, options)

// the request was already made before, let's try to reuse it
if (
pendingPromise &&
Expand All @@ -188,7 +189,7 @@ export function defineLoader<P extends Promise<any>, isLazy extends boolean>(
// `needsNewLoad`
currentNavigation === route &&
// if we are not ready but we have a pendingPromise, we are already fetching so we can reuse it
(!entry.isReady || !isCacheExpired(entry, options))
(!entry.isReady || !isExpired)
) {
return lazy ? Promise.resolve() : pendingPromise
}
Expand All @@ -200,7 +201,7 @@ export function defineLoader<P extends Promise<any>, isLazy extends boolean>(
// if we never finished loading we cannot rely on needsNewLoad
(!entry.isReady && currentNavigation !== route) ||
// we did a load but the cache expired
(entry.isReady && isCacheExpired(entry, options))
(entry.isReady && isExpired)
) {
entry.pending.value = true
entry.error.value = null
Expand Down

0 comments on commit 71d5a21

Please sign in to comment.