1
0
mirror of https://github.com/tooot-app/app synced 2025-04-15 10:47:46 +02:00

Simplify and improve pagination

This commit is contained in:
xmflsct 2023-01-07 18:01:08 +01:00
parent 44f8900902
commit 7db8b26dd9
12 changed files with 92 additions and 112 deletions

View File

@ -16,16 +16,7 @@ export interface Props {
const TimelineFooter: React.FC<Props> = ({ queryKey, disableInfinity }) => {
const { hasNextPage } = useTimelineQuery({
...queryKey[1],
options: {
enabled: !disableInfinity,
notifyOnChangeProps: ['hasNextPage'],
getNextPageParam: lastPage =>
lastPage?.links?.next && {
...(lastPage.links.next.isOffset
? { offset: lastPage.links.next.id }
: { max_id: lastPage.links.next.id })
}
}
options: { enabled: !disableInfinity, notifyOnChangeProps: ['hasNextPage'] }
})
const { colors } = useTheme()

View File

@ -148,11 +148,7 @@ const TimelineRefresh: React.FC<Props> = ({
await queryFunctionTimeline({
queryKey,
pageParam: firstPage?.links?.prev && {
...(firstPage.links.prev.isOffset
? { offset: firstPage.links.prev.id }
: { min_id: firstPage.links.prev.id })
},
pageParam: firstPage?.links?.prev,
meta: {}
})
.then(res => {

View File

@ -52,13 +52,7 @@ const Timeline: React.FC<Props> = ({
notifyOnChangeProps: Platform.select({
ios: ['dataUpdatedAt', 'isFetching'],
android: ['dataUpdatedAt', 'isFetching', 'isLoading']
}),
getNextPageParam: lastPage =>
lastPage?.links?.next && {
...(lastPage.links.next.isOffset
? { offset: lastPage.links.next.id }
: { max_id: lastPage.links.next.id })
}
})
}
})

View File

@ -23,17 +23,7 @@ const TabMeListAccounts: React.FC<TabMeStackScreenProps<'Tab-Me-List-Accounts'>>
const { t } = useTranslation(['common', 'screenTabs'])
const queryKey: QueryKeyListAccounts = ['ListAccounts', { id: params.id }]
const { data, refetch, fetchNextPage, hasNextPage } = useListAccountsQuery({
...queryKey[1],
options: {
getNextPageParam: lastPage =>
lastPage?.links?.next && {
...(lastPage.links.next.isOffset
? { offset: lastPage.links.next.id }
: { max_id: lastPage.links.next.id })
}
}
})
const { data, refetch, fetchNextPage, hasNextPage } = useListAccountsQuery({ ...queryKey[1] })
const mutation = useListAccountsMutation({
onSuccess: () => {

View File

@ -33,12 +33,7 @@ const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> =
const queryKey: QueryKeyUsers = ['Users', params]
const { data, isFetching, hasNextPage, fetchNextPage, isFetchingNextPage } = useUsersQuery({
...queryKey[1],
options: {
getPreviousPageParam: firstPage =>
firstPage.links?.prev?.id && { min_id: firstPage.links.prev.id },
getNextPageParam: lastPage => lastPage.links?.next?.id && { max_id: lastPage.links.next.id }
}
...queryKey[1]
})
const [isSearching, setIsSearching] = useState(false)

View File

@ -1,5 +1,5 @@
import axios from 'axios'
import { ctx, handleError, PagedResponse, userAgent } from './helpers'
import { ctx, handleError, PagedResponse, parseHeaderLinks, userAgent } from './helpers'
export type Params = {
method: 'get' | 'post' | 'put' | 'delete'
@ -49,29 +49,7 @@ const apiGeneral = async <T = unknown>({
? (body as (FormData & { _parts: [][] }) | undefined)?._parts?.length
: Object.keys(body).length) && { data: body })
})
.then(response => {
let links: {
prev?: { id: string; isOffset: boolean }
next?: { id: string; isOffset: boolean }
} = {}
if (response.headers?.link) {
const linksParsed = response.headers.link.matchAll(
new RegExp('[?&](.*?_id|offset)=(.*?)>; *rel="(.*?)"', 'gi')
)
for (const link of linksParsed) {
switch (link[3]) {
case 'prev':
links.prev = { id: link[2], isOffset: link[1].includes('offset') }
break
case 'next':
links.next = { id: link[2], isOffset: link[1].includes('offset') }
break
}
}
}
return Promise.resolve({ body: response.data, links })
})
.then(response => ({ body: response.data, links: parseHeaderLinks(response.headers.link) }))
.catch(handleError())
}

View File

@ -2,6 +2,7 @@ import * as Sentry from '@sentry/react-native'
import chalk from 'chalk'
import Constants from 'expo-constants'
import { Platform } from 'react-native'
import parse from 'url-parse'
const userAgent = {
'User-Agent': `tooot/${Constants.expoConfig?.version} ${Platform.OS}/${Platform.Version}`
@ -64,10 +65,42 @@ const handleError =
}
}
export const parseHeaderLinks = (headerLink?: string): PagedResponse['links'] => {
if (!headerLink) return undefined
const links: PagedResponse['links'] = {}
const linkParsed = [...headerLink.matchAll(/<(\S+?)>; *rel="(next|prev)"/gi)]
for (const link of linkParsed) {
const queries = parse(link[1], true).query
const isOffset = !!queries.offset?.length
switch (link[2]) {
case 'prev':
const prevId = isOffset ? queries.offset : queries.min_id
if (prevId) links.prev = isOffset ? { offset: prevId } : { min_id: prevId }
break
case 'next':
const nextId = isOffset ? queries.offset : queries.max_id
if (nextId) links.next = isOffset ? { offset: nextId } : { max_id: nextId }
break
}
}
if (links.prev || links.next) {
return links
} else {
return undefined
}
}
type LinkFormat = { id: string; isOffset: boolean }
export type PagedResponse<T = unknown> = {
body: T
links?: { prev?: LinkFormat; next?: LinkFormat }
links?: {
prev?: { min_id: string } | { offset: string }
next?: { max_id: string } | { offset: string }
}
}
export { ctx, handleError, userAgent }

View File

@ -1,6 +1,6 @@
import { getAccountDetails } from '@utils/storage/actions'
import axios, { AxiosRequestConfig } from 'axios'
import { ctx, handleError, PagedResponse, userAgent } from './helpers'
import { ctx, handleError, PagedResponse, parseHeaderLinks, userAgent } from './helpers'
export type Params = {
method: 'get' | 'post' | 'put' | 'delete' | 'patch'
@ -57,29 +57,7 @@ const apiInstance = async <T = unknown>({
...((body as (FormData & { _parts: [][] }) | undefined)?._parts.length && { data: body }),
...extras
})
.then(response => {
let links: {
prev?: { id: string; isOffset: boolean }
next?: { id: string; isOffset: boolean }
} = {}
if (response.headers?.link) {
const linksParsed = response.headers.link.matchAll(
new RegExp('[?&](.*?_id|offset)=(.*?)>; *rel="(.*?)"', 'gi')
)
for (const link of linksParsed) {
switch (link[3]) {
case 'prev':
links.prev = { id: link[2], isOffset: link[1].includes('offset') }
break
case 'next':
links.next = { id: link[2], isOffset: link[1].includes('offset') }
break
}
}
}
return Promise.resolve({ body: response.data, links })
})
.then(response => ({ body: response.data, links: parseHeaderLinks(response.headers.link) }))
.catch(handleError())
}

View File

@ -1,15 +1,16 @@
import {
QueryFunctionContext,
useInfiniteQuery,
UseInfiniteQueryOptions,
useMutation,
UseMutationOptions,
useQuery,
UseQueryOptions
QueryFunctionContext,
useInfiniteQuery,
UseInfiniteQueryOptions,
useMutation,
UseMutationOptions,
useQuery,
UseQueryOptions
} from '@tanstack/react-query'
import { PagedResponse } from '@utils/api/helpers'
import apiInstance from '@utils/api/instance'
import { AxiosError } from 'axios'
import { infinitePageParams } from './utils'
export type QueryKeyLists = ['Lists']
@ -98,10 +99,16 @@ const useListAccountsQuery = ({
options,
...queryKeyParams
}: QueryKeyListAccounts[1] & {
options?: UseInfiniteQueryOptions<PagedResponse<Mastodon.Account[]>, AxiosError>
options?: Omit<
UseInfiniteQueryOptions<PagedResponse<Mastodon.Account[]>, AxiosError>,
'getPreviousPageParam' | 'getNextPageParam'
>
}) => {
const queryKey: QueryKeyListAccounts = ['ListAccounts', queryKeyParams]
return useInfiniteQuery(queryKey, accountsQueryFunction, options)
return useInfiniteQuery(queryKey, accountsQueryFunction, {
...options,
...infinitePageParams
})
}
type AccountsMutationVarsLists = {

View File

@ -11,14 +11,14 @@ import apiInstance from '@utils/api/instance'
import { featureCheck } from '@utils/helpers/featureCheck'
import { useNavState } from '@utils/navigation/navigators'
import { queryClient } from '@utils/queryHooks'
import { StorageAccount } from '@utils/storage/account'
import { getAccountStorage, setAccountStorage } from '@utils/storage/actions'
import { getAccountStorage } from '@utils/storage/actions'
import { AxiosError } from 'axios'
import { uniqBy } from 'lodash'
import { searchLocalStatus } from './search'
import deleteItem from './timeline/deleteItem'
import editItem from './timeline/editItem'
import updateStatusProperty from './timeline/updateStatusProperty'
import { infinitePageParams } from './utils'
export type QueryKeyTimeline = [
'Timeline',
@ -156,7 +156,16 @@ export const queryFunctionTimeline = async ({
case 'Account':
if (!page.id) return Promise.reject('Timeline query account id not provided')
if (page.exclude_reblogs) {
if (page.only_media) {
return apiInstance<Mastodon.Status[]>({
method: 'get',
url: `accounts/${page.id}/statuses`,
params: {
only_media: 'true',
...params
}
})
} else if (page.exclude_reblogs) {
if (pageParam && pageParam.hasOwnProperty('max_id')) {
return apiInstance<Mastodon.Status[]>({
method: 'get',
@ -196,8 +205,8 @@ export const queryFunctionTimeline = async ({
url: `accounts/${page.id}/statuses`,
params: {
...params,
exclude_replies: page.exclude_reblogs.toString(),
only_media: page.only_media.toString()
exclude_replies: false,
only_media: false
}
})
}
@ -247,14 +256,18 @@ const useTimelineQuery = ({
options,
...queryKeyParams
}: QueryKeyTimeline[1] & {
options?: UseInfiniteQueryOptions<PagedResponse<Mastodon.Status[]>, AxiosError>
options?: Omit<
UseInfiniteQueryOptions<PagedResponse<Mastodon.Status[]>, AxiosError>,
'getPreviousPageParam' | 'getNextPageParam'
>
}) => {
const queryKey: QueryKeyTimeline = ['Timeline', { ...queryKeyParams }]
return useInfiniteQuery(queryKey, queryFunctionTimeline, {
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
...options
...options,
...infinitePageParams
})
}

View File

@ -9,6 +9,7 @@ import apiInstance from '@utils/api/instance'
import { urlMatcher } from '@utils/helpers/urlMatcher'
import { TabSharedStackParamList } from '@utils/navigation/navigators'
import { AxiosError } from 'axios'
import { infinitePageParams } from './utils'
export type QueryKeyUsers = ['Users', TabSharedStackParamList['Tab-Shared-Users']]
@ -73,13 +74,19 @@ const useUsersQuery = ({
options,
...queryKeyParams
}: QueryKeyUsers[1] & {
options?: UseInfiniteQueryOptions<
PagedResponse<Mastodon.Account[]> & { warnIncomplete: boolean; remoteData: boolean },
AxiosError
options?: Omit<
UseInfiniteQueryOptions<
PagedResponse<Mastodon.Account[]> & { warnIncomplete: boolean; remoteData: boolean },
AxiosError
>,
'getPreviousPageParam' | 'getNextPageParam'
>
}) => {
const queryKey: QueryKeyUsers = ['Users', { ...queryKeyParams }]
return useInfiniteQuery(queryKey, queryFunction, options)
return useInfiniteQuery(queryKey, queryFunction, {
...options,
...infinitePageParams
})
}
export { useUsersQuery }

View File

@ -2,10 +2,8 @@ import { InfiniteData } from '@tanstack/react-query'
import { PagedResponse } from '@utils/api/helpers'
export const infinitePageParams = {
getPreviousPageParam: (firstPage: PagedResponse<any>) =>
firstPage.links?.prev && { min_id: firstPage.links.next },
getNextPageParam: (lastPage: PagedResponse<any>) =>
lastPage.links?.next && { max_id: lastPage.links.next }
getPreviousPageParam: (firstPage: PagedResponse<any>) => firstPage.links?.prev,
getNextPageParam: (lastPage: PagedResponse<any>) => lastPage.links?.next
}
export const flattenPages = <T>(data: InfiniteData<PagedResponse<T[]>> | undefined): T[] | [] =>