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:
parent
44f8900902
commit
7db8b26dd9
@ -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()
|
||||
|
@ -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 => {
|
||||
|
@ -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 })
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -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: () => {
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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[] | [] =>
|
||||
|
Loading…
x
Reference in New Issue
Block a user