Refactor lists to use new queries (#1875)
* Refactor lists queries to react-query * Delete old lists-list model * Implement list, list-members, and list-memberships react-queries * Update CreateOrEditList modal * First pass at my-follows and actor-autocomplete queries * Update ListAddUserModal to use new queries, change to ListAddRemoveUsersModal * Update UserAddRemoveLists modal * Remove old TODO * Fix indent, autocomplete query * Add a todo --------- Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
parent
05b728fffc
commit
d9e0a927c1
25 changed files with 1303 additions and 1545 deletions
|
@ -7,7 +7,7 @@ import {RichText as RichTextCom} from '../util/text/RichText'
|
|||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {s} from 'lib/styles'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useStores} from 'state/index'
|
||||
import {useSession} from '#/state/session'
|
||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||
import {sanitizeHandle} from 'lib/strings/handles'
|
||||
import {makeProfileLink} from 'lib/routes/links'
|
||||
|
@ -28,7 +28,7 @@ export const ListCard = ({
|
|||
style?: StyleProp<ViewStyle>
|
||||
}) => {
|
||||
const pal = usePalette('default')
|
||||
const store = useStores()
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
const rkey = React.useMemo(() => {
|
||||
try {
|
||||
|
@ -80,7 +80,7 @@ export const ListCard = ({
|
|||
{list.purpose === 'app.bsky.graph.defs#modlist' &&
|
||||
'Moderation list '}
|
||||
by{' '}
|
||||
{list.creator.did === store.me.did
|
||||
{list.creator.did === currentAccount?.did
|
||||
? 'you'
|
||||
: sanitizeHandle(list.creator.handle, '@')}
|
||||
</Text>
|
||||
|
|
|
@ -9,27 +9,28 @@ import {
|
|||
} from 'react-native'
|
||||
import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
|
||||
import {FlatList} from '../util/Views'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {ProfileCardFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
||||
import {ProfileCard} from '../profile/ProfileCard'
|
||||
import {Button} from '../util/forms/Button'
|
||||
import {ListModel} from 'state/models/content/list'
|
||||
import {useAnalytics} from 'lib/analytics/analytics'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {useListMembersQuery} from '#/state/queries/list-members'
|
||||
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
|
||||
import {logger} from '#/logger'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
|
||||
import {useSession} from '#/state/session'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
|
||||
const LOADING_ITEM = {_reactKey: '__loading__'}
|
||||
const EMPTY_ITEM = {_reactKey: '__empty__'}
|
||||
const ERROR_ITEM = {_reactKey: '__error__'}
|
||||
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
|
||||
|
||||
export const ListItems = observer(function ListItemsImpl({
|
||||
export function ListMembers({
|
||||
list,
|
||||
style,
|
||||
scrollElRef,
|
||||
|
@ -42,7 +43,7 @@ export const ListItems = observer(function ListItemsImpl({
|
|||
headerOffset = 0,
|
||||
desktopFixedHeightOffset,
|
||||
}: {
|
||||
list: ListModel
|
||||
list: string
|
||||
style?: StyleProp<ViewStyle>
|
||||
scrollElRef?: MutableRefObject<FlatList<any> | null>
|
||||
onScroll: OnScrollHandler
|
||||
|
@ -59,33 +60,43 @@ export const ListItems = observer(function ListItemsImpl({
|
|||
const [isRefreshing, setIsRefreshing] = React.useState(false)
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const {openModal} = useModalControls()
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
const data = React.useMemo(() => {
|
||||
const {
|
||||
data,
|
||||
isFetching,
|
||||
isFetched,
|
||||
isError,
|
||||
error,
|
||||
refetch,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
} = useListMembersQuery(list)
|
||||
const isEmpty = !isFetching && !data?.pages[0].items.length
|
||||
const isOwner =
|
||||
currentAccount && data?.pages[0].list.creator.did === currentAccount.did
|
||||
|
||||
const items = React.useMemo(() => {
|
||||
let items: any[] = []
|
||||
if (list.hasLoaded) {
|
||||
if (list.hasError) {
|
||||
if (isFetched) {
|
||||
if (isEmpty && isError) {
|
||||
items = items.concat([ERROR_ITEM])
|
||||
}
|
||||
if (list.isEmpty) {
|
||||
if (isEmpty) {
|
||||
items = items.concat([EMPTY_ITEM])
|
||||
} else {
|
||||
items = items.concat(list.items)
|
||||
} else if (data) {
|
||||
for (const page of data.pages) {
|
||||
items = items.concat(page.items)
|
||||
}
|
||||
}
|
||||
if (list.loadMoreError) {
|
||||
if (!isEmpty && isError) {
|
||||
items = items.concat([LOAD_MORE_ERROR_ITEM])
|
||||
}
|
||||
} else if (list.isLoading) {
|
||||
} else if (isFetching) {
|
||||
items = items.concat([LOADING_ITEM])
|
||||
}
|
||||
return items
|
||||
}, [
|
||||
list.hasError,
|
||||
list.hasLoaded,
|
||||
list.isLoading,
|
||||
list.isEmpty,
|
||||
list.items,
|
||||
list.loadMoreError,
|
||||
])
|
||||
}, [isFetched, isEmpty, isError, data, isFetching])
|
||||
|
||||
// events
|
||||
// =
|
||||
|
@ -94,25 +105,26 @@ export const ListItems = observer(function ListItemsImpl({
|
|||
track('Lists:onRefresh')
|
||||
setIsRefreshing(true)
|
||||
try {
|
||||
await list.refresh()
|
||||
await refetch()
|
||||
} catch (err) {
|
||||
logger.error('Failed to refresh lists', {error: err})
|
||||
}
|
||||
setIsRefreshing(false)
|
||||
}, [list, track, setIsRefreshing])
|
||||
}, [refetch, track, setIsRefreshing])
|
||||
|
||||
const onEndReached = React.useCallback(async () => {
|
||||
if (isFetching || !hasNextPage || isError) return
|
||||
track('Lists:onEndReached')
|
||||
try {
|
||||
await list.loadMore()
|
||||
await fetchNextPage()
|
||||
} catch (err) {
|
||||
logger.error('Failed to load more lists', {error: err})
|
||||
}
|
||||
}, [list, track])
|
||||
}, [isFetching, hasNextPage, isError, fetchNextPage, track])
|
||||
|
||||
const onPressRetryLoadMore = React.useCallback(() => {
|
||||
list.retryLoadMore()
|
||||
}, [list])
|
||||
fetchNextPage()
|
||||
}, [fetchNextPage])
|
||||
|
||||
const onPressEditMembership = React.useCallback(
|
||||
(profile: AppBskyActorDefs.ProfileViewBasic) => {
|
||||
|
@ -120,19 +132,9 @@ export const ListItems = observer(function ListItemsImpl({
|
|||
name: 'user-add-remove-lists',
|
||||
subject: profile.did,
|
||||
displayName: profile.displayName || profile.handle,
|
||||
onAdd(listUri: string) {
|
||||
if (listUri === list.uri) {
|
||||
list.cacheAddMember(profile)
|
||||
}
|
||||
},
|
||||
onRemove(listUri: string) {
|
||||
if (listUri === list.uri) {
|
||||
list.cacheRemoveMember(profile)
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
[openModal, list],
|
||||
[openModal],
|
||||
)
|
||||
|
||||
// rendering
|
||||
|
@ -140,7 +142,7 @@ export const ListItems = observer(function ListItemsImpl({
|
|||
|
||||
const renderMemberButton = React.useCallback(
|
||||
(profile: AppBskyActorDefs.ProfileViewBasic) => {
|
||||
if (!list.isOwner) {
|
||||
if (!isOwner) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
|
@ -152,7 +154,7 @@ export const ListItems = observer(function ListItemsImpl({
|
|||
/>
|
||||
)
|
||||
},
|
||||
[list, onPressEditMembership],
|
||||
[isOwner, onPressEditMembership],
|
||||
)
|
||||
|
||||
const renderItem = React.useCallback(
|
||||
|
@ -162,7 +164,7 @@ export const ListItems = observer(function ListItemsImpl({
|
|||
} else if (item === ERROR_ITEM) {
|
||||
return (
|
||||
<ErrorMessage
|
||||
message={list.error}
|
||||
message={cleanError(error)}
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
/>
|
||||
)
|
||||
|
@ -190,7 +192,7 @@ export const ListItems = observer(function ListItemsImpl({
|
|||
[
|
||||
renderMemberButton,
|
||||
renderEmptyState,
|
||||
list.error,
|
||||
error,
|
||||
onPressTryAgain,
|
||||
onPressRetryLoadMore,
|
||||
isMobile,
|
||||
|
@ -200,10 +202,10 @@ export const ListItems = observer(function ListItemsImpl({
|
|||
const Footer = React.useCallback(
|
||||
() => (
|
||||
<View style={{paddingTop: 20, paddingBottom: 200}}>
|
||||
{list.isLoading && <ActivityIndicator />}
|
||||
{isFetching && <ActivityIndicator />}
|
||||
</View>
|
||||
),
|
||||
[list.isLoading],
|
||||
[isFetching],
|
||||
)
|
||||
|
||||
const scrollHandler = useAnimatedScrollHandler(onScroll)
|
||||
|
@ -212,8 +214,8 @@ export const ListItems = observer(function ListItemsImpl({
|
|||
<FlatList
|
||||
testID={testID ? `${testID}-flatlist` : undefined}
|
||||
ref={scrollElRef}
|
||||
data={data}
|
||||
keyExtractor={(item: any) => item._reactKey}
|
||||
data={items}
|
||||
keyExtractor={(item: any) => item.uri || item._reactKey}
|
||||
renderItem={renderItem}
|
||||
ListHeaderComponent={renderHeader}
|
||||
ListFooterComponent={Footer}
|
||||
|
@ -241,4 +243,4 @@ export const ListItems = observer(function ListItemsImpl({
|
|||
/>
|
||||
</View>
|
||||
)
|
||||
})
|
||||
}
|
|
@ -8,68 +8,59 @@ import {
|
|||
View,
|
||||
ViewStyle,
|
||||
} from 'react-native'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {AppBskyGraphDefs as GraphDefs} from '@atproto/api'
|
||||
import {ListCard} from './ListCard'
|
||||
import {MyListsFilter, useMyListsQuery} from '#/state/queries/my-lists'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {ListsListModel} from 'state/models/lists/lists-list'
|
||||
import {useAnalytics} from 'lib/analytics/analytics'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {FlatList} from '../util/Views'
|
||||
import {s} from 'lib/styles'
|
||||
import {logger} from '#/logger'
|
||||
import {Trans} from '@lingui/macro'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
|
||||
const LOADING = {_reactKey: '__loading__'}
|
||||
const EMPTY = {_reactKey: '__empty__'}
|
||||
const ERROR_ITEM = {_reactKey: '__error__'}
|
||||
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
|
||||
|
||||
export const ListsList = observer(function ListsListImpl({
|
||||
listsList,
|
||||
export function ListsList({
|
||||
filter,
|
||||
inline,
|
||||
style,
|
||||
onPressTryAgain,
|
||||
renderItem,
|
||||
testID,
|
||||
}: {
|
||||
listsList: ListsListModel
|
||||
filter: MyListsFilter
|
||||
inline?: boolean
|
||||
style?: StyleProp<ViewStyle>
|
||||
onPressTryAgain?: () => void
|
||||
renderItem?: (list: GraphDefs.ListView, index: number) => JSX.Element
|
||||
testID?: string
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const {track} = useAnalytics()
|
||||
const [isRefreshing, setIsRefreshing] = React.useState(false)
|
||||
const {data, isFetching, isFetched, isError, error, refetch} =
|
||||
useMyListsQuery(filter)
|
||||
const isEmpty = !isFetching && !data?.length
|
||||
|
||||
const data = React.useMemo(() => {
|
||||
const items = React.useMemo(() => {
|
||||
let items: any[] = []
|
||||
if (listsList.hasError) {
|
||||
if (isError && isEmpty) {
|
||||
items = items.concat([ERROR_ITEM])
|
||||
}
|
||||
if (!listsList.hasLoaded && listsList.isLoading) {
|
||||
if (!isFetched && isFetching) {
|
||||
items = items.concat([LOADING])
|
||||
} else if (listsList.isEmpty) {
|
||||
} else if (isEmpty) {
|
||||
items = items.concat([EMPTY])
|
||||
} else {
|
||||
items = items.concat(listsList.lists)
|
||||
}
|
||||
if (listsList.loadMoreError) {
|
||||
items = items.concat([LOAD_MORE_ERROR_ITEM])
|
||||
items = items.concat(data)
|
||||
}
|
||||
return items
|
||||
}, [
|
||||
listsList.hasError,
|
||||
listsList.hasLoaded,
|
||||
listsList.isLoading,
|
||||
listsList.lists,
|
||||
listsList.isEmpty,
|
||||
listsList.loadMoreError,
|
||||
])
|
||||
}, [isError, isEmpty, isFetched, isFetching, data])
|
||||
|
||||
// events
|
||||
// =
|
||||
|
@ -78,25 +69,12 @@ export const ListsList = observer(function ListsListImpl({
|
|||
track('Lists:onRefresh')
|
||||
setIsRefreshing(true)
|
||||
try {
|
||||
await listsList.refresh()
|
||||
await refetch()
|
||||
} catch (err) {
|
||||
logger.error('Failed to refresh lists', {error: err})
|
||||
}
|
||||
setIsRefreshing(false)
|
||||
}, [listsList, track, setIsRefreshing])
|
||||
|
||||
const onEndReached = React.useCallback(async () => {
|
||||
track('Lists:onEndReached')
|
||||
try {
|
||||
await listsList.loadMore()
|
||||
} catch (err) {
|
||||
logger.error('Failed to load more lists', {error: err})
|
||||
}
|
||||
}, [listsList, track])
|
||||
|
||||
const onPressRetryLoadMore = React.useCallback(() => {
|
||||
listsList.retryLoadMore()
|
||||
}, [listsList])
|
||||
}, [refetch, track, setIsRefreshing])
|
||||
|
||||
// rendering
|
||||
// =
|
||||
|
@ -116,15 +94,15 @@ export const ListsList = observer(function ListsListImpl({
|
|||
} else if (item === ERROR_ITEM) {
|
||||
return (
|
||||
<ErrorMessage
|
||||
message={listsList.error}
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
message={cleanError(error)}
|
||||
onPressTryAgain={onRefresh}
|
||||
/>
|
||||
)
|
||||
} else if (item === LOAD_MORE_ERROR_ITEM) {
|
||||
return (
|
||||
<LoadMoreRetryBtn
|
||||
label="There was an issue fetching your lists. Tap here to try again."
|
||||
onPress={onPressRetryLoadMore}
|
||||
onPress={onRefresh}
|
||||
/>
|
||||
)
|
||||
} else if (item === LOADING) {
|
||||
|
@ -144,16 +122,16 @@ export const ListsList = observer(function ListsListImpl({
|
|||
/>
|
||||
)
|
||||
},
|
||||
[listsList, onPressTryAgain, onPressRetryLoadMore, renderItem, pal],
|
||||
[error, onRefresh, renderItem, pal],
|
||||
)
|
||||
|
||||
const FlatListCom = inline ? RNFlatList : FlatList
|
||||
return (
|
||||
<View testID={testID} style={style}>
|
||||
{data.length > 0 && (
|
||||
{items.length > 0 && (
|
||||
<FlatListCom
|
||||
testID={testID ? `${testID}-flatlist` : undefined}
|
||||
data={data}
|
||||
data={items}
|
||||
keyExtractor={(item: any) => item._reactKey}
|
||||
renderItem={renderItemInner}
|
||||
refreshControl={
|
||||
|
@ -165,8 +143,6 @@ export const ListsList = observer(function ListsListImpl({
|
|||
/>
|
||||
}
|
||||
contentContainerStyle={[s.contentContainer]}
|
||||
onEndReached={onEndReached}
|
||||
onEndReachedThreshold={0.6}
|
||||
removeClippedSubviews={true}
|
||||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
|
@ -174,7 +150,7 @@ export const ListsList = observer(function ListsListImpl({
|
|||
)}
|
||||
</View>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
item: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue