Skip to content

Commit

Permalink
refactor: Move infinite query logic into queryCache
Browse files Browse the repository at this point in the history
  • Loading branch information
tannerlinsley committed Jun 27, 2020
1 parent 5a5ad74 commit 7cd1af1
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 156 deletions.
Binary file modified media/logo.sketch
Binary file not shown.
134 changes: 123 additions & 11 deletions src/core/queryCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
statusIdle,
Console,
isObject,
getStatusBools,
} from './utils'

import { defaultConfigRef } from './config'
Expand Down Expand Up @@ -153,6 +154,23 @@ export function makeQueryCache({ frozen = isServer, defaultConfig } = {}) {
config,
})

if (config.infinite) {
if (
typeof query.state.canFetchMore === 'undefined' &&
typeof query.state.data !== 'undefined'
) {
query.state.canFetchMore = config.getFetchMore(
query.state.data[query.state.data.length - 1],
query.state.data
)
}

// Here we seed the pageVariabes for the query
if (!query.pageVariables) {
query.pageVariables = [[...query.queryKey]]
}
}

// If the query started with data, schedule
// a stale timeout
if (!isServer && query.state.data) {
Expand Down Expand Up @@ -310,6 +328,14 @@ export function makeQueryCache({ frozen = isServer, defaultConfig } = {}) {
)
}

query.refetch = async () => {
try {
await query.fetch()
} catch (error) {
Console.error(error)
}
}

query.heal = () => {
// Stop the query from being garbage collected
clearTimeout(query.cacheTimeout)
Expand Down Expand Up @@ -507,10 +533,98 @@ export function makeQueryCache({ frozen = isServer, defaultConfig } = {}) {
}
}

query.fetch = async ({ queryFn = query.config.queryFn } = {}) => {
query.fetch = async ({ fetchMore } = {}) => {
let queryFn = query.config.queryFn

if (!queryFn) {
return
}

if (query.config.infinite) {
const originalQueryFn = queryFn

queryFn = async () => {
const data = []
const pageVariables = [...query.pageVariables]
const rebuiltPageVariables = []

do {
const args = pageVariables.shift()

if (!data.length) {
// the first page query doesn't need to be rebuilt
data.push(await originalQueryFn(...args))
rebuiltPageVariables.push(args)
} else {
// get an up-to-date cursor based on the previous data set

const nextCursor = query.config.getFetchMore(
data[data.length - 1],
data
)

// break early if there's no next cursor
// otherwise we'll start from the beginning
// which will cause unwanted duplication
if (!nextCursor) {
break
}

const pageArgs = [
// remove the last argument (the previously saved cursor)
...args.slice(0, -1),
nextCursor,
]

data.push(await originalQueryFn(...pageArgs))
rebuiltPageVariables.push(pageArgs)
}
} while (pageVariables.length)

query.state.canFetchMore = query.config.getFetchMore(
data[data.length - 1],
data
)
query.pageVariables = rebuiltPageVariables

return data
}

if (fetchMore) {
queryFn = async (...args) => {
const { fetchMoreInfo, previous } = fetchMore
try {
query.setState(old => ({
...old,
isFetchingMore: previous ? 'previous' : 'next',
}))

const newArgs = [...args, fetchMoreInfo]

query.pageVariables[previous ? 'unshift' : 'push'](newArgs)

const newData = await originalQueryFn(...newArgs)

const data = previous
? [newData, ...query.state.data]
: [...query.state.data, newData]

query.state.canFetchMore = query.config.getFetchMore(
newData,
data
)

return data
} finally {
query.setState(old => ({
...old,
isFetchingMore: false,
}))
}
}
}
}

// Create a new promise for the query cache if necessary
if (!query.promise) {
query.promise = (async () => {
Expand Down Expand Up @@ -582,6 +696,13 @@ export function makeQueryCache({ frozen = isServer, defaultConfig } = {}) {
return query.promise
}

if (query.config.infinite) {
query.fetchMore = (
fetchMoreInfo = query.state.canFetchMore,
{ previous = false } = {}
) => query.fetch({ fetchMore: { fetchMoreInfo, previous } })
}

return query
}

Expand All @@ -591,14 +712,7 @@ export function makeQueryCache({ frozen = isServer, defaultConfig } = {}) {
export function queryReducer(state, action) {
const newState = switchActions(state, action)

Object.assign(newState, {
isLoading: newState.status === statusLoading,
isSuccess: newState.status === statusSuccess,
isError: newState.status === statusError,
isIdle: newState.status === statusIdle,
})

return newState
return Object.assign(newState, getStatusBools(newState.status))
}

function switchActions(state, action) {
Expand All @@ -608,7 +722,6 @@ function switchActions(state, action) {
status: action.initialStatus,
error: null,
isFetching: action.initialStatus === 'loading',
canFetchMore: false,
failureCount: 0,
isStale: action.isStale,
markedForGarbageCollection: false,
Expand Down Expand Up @@ -647,7 +760,6 @@ function switchActions(state, action) {
error: null,
isStale: false,
isFetching: false,
canFetchMore: action.canFetchMore,
updatedAt: Date.now(),
failureCount: 0,
}
Expand Down
9 changes: 9 additions & 0 deletions src/core/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,12 @@ export function deepEqual(a, b) {
// eslint-disable-next-line no-self-compare
return a !== a && b !== b
}

export function getStatusBools(status) {
return {
isLoading: status === statusLoading,
isSuccess: status === statusSuccess,
isError: status === statusError,
isIdle: status === statusIdle,
}
}
13 changes: 2 additions & 11 deletions src/react/useBaseQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import React from 'react'

import { useQueryCache } from './ReactQueryCacheProvider'
import { useMountedCallback } from './utils'
import { Console } from '../core/utils'

export function useBaseQuery(queryKey, config = {}) {
// Make a rerender function
Expand Down Expand Up @@ -40,17 +39,9 @@ export function useBaseQuery(queryKey, config = {}) {
instanceRef.current.run()
}, [config.enabled, query])

const refetch = React.useCallback(async () => {
try {
await query.fetch()
} catch (error) {
Console.error(error)
}
}, [query])

return {
query,
refetch,
...query,
...query.state,
query,
}
}
128 changes: 3 additions & 125 deletions src/react/useInfiniteQuery.js
Original file line number Diff line number Diff line change
@@ -1,138 +1,16 @@
import React from 'react'

//

import { useBaseQuery } from './useBaseQuery'
import { useQueryArgs, useGetLatest, handleSuspense } from './utils'
import { useQueryArgs, handleSuspense } from './utils'

export function useInfiniteQuery(...args) {
const queryInfoRef = React.useRef()
let [queryKey, config] = useQueryArgs(args)

const { getFetchMore } = config
const getGetFetchMore = useGetLatest(getFetchMore)

// The default queryFn will query all pages and map them together
const originalQueryFn = config.queryFn

config.queryFn = async () => {
const data = []
const pageVariables = [...queryInfoRef.current.query.pageVariables]
const rebuiltPageVariables = []

do {
const args = pageVariables.shift()

if (!data.length) {
// the first page query doesn't need to be rebuilt
data.push(await originalQueryFn(...args))
rebuiltPageVariables.push(args)
} else {
// get an up-to-date cursor based on the previous data set
const nextCursor = getGetFetchMore()(data[data.length - 1], data)

// break early if there's no next cursor
// otherwise we'll start from the beginning
// which will cause unwanted duplication
if (!nextCursor) {
break
}

const pageArgs = [
// remove the last argument (the previously saved cursor)
...args.slice(0, -1),
nextCursor,
]

data.push(await originalQueryFn(...pageArgs))
rebuiltPageVariables.push(pageArgs)
}
} while (pageVariables.length)

queryInfoRef.current.query.canFetchMore = getGetFetchMore()(
data[data.length - 1],
data
)
queryInfoRef.current.query.pageVariables = rebuiltPageVariables

return data
}
config.infinite = true

const queryInfo = useBaseQuery(queryKey, config)

if (
typeof queryInfo.query.canFetchMore === 'undefined' &&
typeof queryInfo.data !== 'undefined'
) {
queryInfo.query.canFetchMore = getGetFetchMore()(
queryInfo.data[queryInfo.data.length - 1],
queryInfo.data
)
}

queryInfoRef.current = queryInfo

let {
data = [],
query: { canFetchMore },
} = queryInfo

// Here we seed the pageVariabes for the query
if (!queryInfo.query.pageVariables) {
queryInfo.query.pageVariables = [[...queryInfo.query.queryKey]]
}

const fetchMore = React.useCallback(
(
fetchMoreInfo = queryInfoRef.current.query.canFetchMore,
{ previous = false } = {}
) =>
queryInfoRef.current.query.canFetchMore
? queryInfoRef.current.query.fetch({
queryFn: async (...args) => {
try {
queryInfoRef.current.query.setState(old => ({
...old,
isFetchingMore: previous ? 'previous' : 'next',
}))

const newArgs = previous
? [fetchMoreInfo, ...args]
: [...args, fetchMoreInfo]
queryInfoRef.current.query.pageVariables[
previous ? 'unshift' : 'push'
](newArgs)

const newData = await originalQueryFn(...newArgs)

const data = previous
? [newData, ...queryInfoRef.current.data]
: [...queryInfoRef.current.data, newData]

queryInfoRef.current.query.canFetchMore = getGetFetchMore()(
newData,
data
)

return data
} finally {
queryInfoRef.current.query.setState(old => ({
...old,
isFetchingMore: false,
}))
}
},
})
: void 0,
[getGetFetchMore, originalQueryFn]
)

handleSuspense(queryInfo)

return {
...queryInfo,
data,
canFetchMore,
fetchMore,
}
return queryInfo
}
Loading

0 comments on commit 7cd1af1

Please sign in to comment.