Shadow refactoring and improvements (#1959)

* Make shadow a type-only concept

* Prevent unnecessary init state recalc

* Use derived state instead of effects

* Batch emitter updates

* Use object first seen time instead of dataUpdatedAt

* Stop threading dataUpdatedAt through

* Use same value consistently
zio/stable
dan 2023-11-21 22:42:30 +00:00 committed by GitHub
parent f18b9b32b0
commit 4c4ba553bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 115 additions and 203 deletions

View File

@ -0,0 +1 @@
export {unstable_batchedUpdates as batchedUpdates} from 'react-native'

View File

@ -0,0 +1,2 @@
// @ts-ignore
export {unstable_batchedUpdates as batchedUpdates} from 'react-dom'

View File

@ -1,7 +1,8 @@
import {useEffect, useState, useMemo, useCallback, useRef} from 'react' import {useEffect, useState, useMemo, useCallback} from 'react'
import EventEmitter from 'eventemitter3' import EventEmitter from 'eventemitter3'
import {AppBskyFeedDefs} from '@atproto/api' import {AppBskyFeedDefs} from '@atproto/api'
import {Shadow} from './types' import {batchedUpdates} from '#/lib/batchedUpdates'
import {Shadow, castAsShadow} from './types'
export type {Shadow} from './types' export type {Shadow} from './types'
const emitter = new EventEmitter() const emitter = new EventEmitter()
@ -21,15 +22,36 @@ interface CacheEntry {
value: PostShadow value: PostShadow
} }
const firstSeenMap = new WeakMap<AppBskyFeedDefs.PostView, number>()
function getFirstSeenTS(post: AppBskyFeedDefs.PostView): number {
let timeStamp = firstSeenMap.get(post)
if (timeStamp !== undefined) {
return timeStamp
}
timeStamp = Date.now()
firstSeenMap.set(post, timeStamp)
return timeStamp
}
export function usePostShadow( export function usePostShadow(
post: AppBskyFeedDefs.PostView, post: AppBskyFeedDefs.PostView,
ifAfterTS: number,
): Shadow<AppBskyFeedDefs.PostView> | typeof POST_TOMBSTONE { ): Shadow<AppBskyFeedDefs.PostView> | typeof POST_TOMBSTONE {
const [state, setState] = useState<CacheEntry>({ const postSeenTS = getFirstSeenTS(post)
ts: Date.now(), const [state, setState] = useState<CacheEntry>(() => ({
ts: postSeenTS,
value: fromPost(post), value: fromPost(post),
}) }))
const firstRun = useRef(true)
const [prevPost, setPrevPost] = useState(post)
if (post !== prevPost) {
// if we got a new prop, assume it's fresher
// than whatever shadow state we accumulated
setPrevPost(post)
setState({
ts: postSeenTS,
value: fromPost(post),
})
}
const onUpdate = useCallback( const onUpdate = useCallback(
(value: Partial<PostShadow>) => { (value: Partial<PostShadow>) => {
@ -46,30 +68,17 @@ export function usePostShadow(
} }
}, [post.uri, onUpdate]) }, [post.uri, onUpdate])
// react to post updates
useEffect(() => {
// dont fire on first run to avoid needless re-renders
if (!firstRun.current) {
setState({ts: Date.now(), value: fromPost(post)})
}
firstRun.current = false
}, [post])
return useMemo(() => { return useMemo(() => {
return state.ts > ifAfterTS return state.ts > postSeenTS
? mergeShadow(post, state.value) ? mergeShadow(post, state.value)
: {...post, isShadowed: true} : castAsShadow(post)
}, [post, state, ifAfterTS]) }, [post, state, postSeenTS])
} }
export function updatePostShadow(uri: string, value: Partial<PostShadow>) { export function updatePostShadow(uri: string, value: Partial<PostShadow>) {
emitter.emit(uri, value) batchedUpdates(() => {
} emitter.emit(uri, value)
})
export function isPostShadowed(
v: AppBskyFeedDefs.PostView | Shadow<AppBskyFeedDefs.PostView>,
): v is Shadow<AppBskyFeedDefs.PostView> {
return 'isShadowed' in v && !!v.isShadowed
} }
function fromPost(post: AppBskyFeedDefs.PostView): PostShadow { function fromPost(post: AppBskyFeedDefs.PostView): PostShadow {
@ -89,7 +98,7 @@ function mergeShadow(
if (shadow.isDeleted) { if (shadow.isDeleted) {
return POST_TOMBSTONE return POST_TOMBSTONE
} }
return { return castAsShadow({
...post, ...post,
likeCount: shadow.likeCount, likeCount: shadow.likeCount,
repostCount: shadow.repostCount, repostCount: shadow.repostCount,
@ -98,6 +107,5 @@ function mergeShadow(
like: shadow.likeUri, like: shadow.likeUri,
repost: shadow.repostUri, repost: shadow.repostUri,
}, },
isShadowed: true, })
}
} }

View File

@ -1,7 +1,8 @@
import {useEffect, useState, useMemo, useCallback, useRef} from 'react' import {useEffect, useState, useMemo, useCallback} from 'react'
import EventEmitter from 'eventemitter3' import EventEmitter from 'eventemitter3'
import {AppBskyActorDefs} from '@atproto/api' import {AppBskyActorDefs} from '@atproto/api'
import {Shadow} from './types' import {batchedUpdates} from '#/lib/batchedUpdates'
import {Shadow, castAsShadow} from './types'
export type {Shadow} from './types' export type {Shadow} from './types'
const emitter = new EventEmitter() const emitter = new EventEmitter()
@ -22,15 +23,34 @@ type ProfileView =
| AppBskyActorDefs.ProfileViewBasic | AppBskyActorDefs.ProfileViewBasic
| AppBskyActorDefs.ProfileViewDetailed | AppBskyActorDefs.ProfileViewDetailed
export function useProfileShadow( const firstSeenMap = new WeakMap<ProfileView, number>()
profile: ProfileView, function getFirstSeenTS(profile: ProfileView): number {
ifAfterTS: number, let timeStamp = firstSeenMap.get(profile)
): Shadow<ProfileView> { if (timeStamp !== undefined) {
const [state, setState] = useState<CacheEntry>({ return timeStamp
ts: Date.now(), }
timeStamp = Date.now()
firstSeenMap.set(profile, timeStamp)
return timeStamp
}
export function useProfileShadow(profile: ProfileView): Shadow<ProfileView> {
const profileSeenTS = getFirstSeenTS(profile)
const [state, setState] = useState<CacheEntry>(() => ({
ts: profileSeenTS,
value: fromProfile(profile), value: fromProfile(profile),
}) }))
const firstRun = useRef(true)
const [prevProfile, setPrevProfile] = useState(profile)
if (profile !== prevProfile) {
// if we got a new prop, assume it's fresher
// than whatever shadow state we accumulated
setPrevProfile(profile)
setState({
ts: profileSeenTS,
value: fromProfile(profile),
})
}
const onUpdate = useCallback( const onUpdate = useCallback(
(value: Partial<ProfileShadow>) => { (value: Partial<ProfileShadow>) => {
@ -47,33 +67,20 @@ export function useProfileShadow(
} }
}, [profile.did, onUpdate]) }, [profile.did, onUpdate])
// react to profile updates
useEffect(() => {
// dont fire on first run to avoid needless re-renders
if (!firstRun.current) {
setState({ts: Date.now(), value: fromProfile(profile)})
}
firstRun.current = false
}, [profile])
return useMemo(() => { return useMemo(() => {
return state.ts > ifAfterTS return state.ts > profileSeenTS
? mergeShadow(profile, state.value) ? mergeShadow(profile, state.value)
: {...profile, isShadowed: true} : castAsShadow(profile)
}, [profile, state, ifAfterTS]) }, [profile, state, profileSeenTS])
} }
export function updateProfileShadow( export function updateProfileShadow(
uri: string, uri: string,
value: Partial<ProfileShadow>, value: Partial<ProfileShadow>,
) { ) {
emitter.emit(uri, value) batchedUpdates(() => {
} emitter.emit(uri, value)
})
export function isProfileShadowed<T extends ProfileView>(
v: T | Shadow<T>,
): v is Shadow<T> {
return 'isShadowed' in v && !!v.isShadowed
} }
function fromProfile(profile: ProfileView): ProfileShadow { function fromProfile(profile: ProfileView): ProfileShadow {
@ -88,7 +95,7 @@ function mergeShadow(
profile: ProfileView, profile: ProfileView,
shadow: ProfileShadow, shadow: ProfileShadow,
): Shadow<ProfileView> { ): Shadow<ProfileView> {
return { return castAsShadow({
...profile, ...profile,
viewer: { viewer: {
...(profile.viewer || {}), ...(profile.viewer || {}),
@ -96,6 +103,5 @@ function mergeShadow(
muted: shadow.muted, muted: shadow.muted,
blocking: shadow.blockingUri, blocking: shadow.blockingUri,
}, },
isShadowed: true, })
}
} }

View File

@ -1 +1,7 @@
export type Shadow<T> = T & {isShadowed: true} // This isn't a real property, but it prevents T being compatible with Shadow<T>.
declare const shadowTag: unique symbol
export type Shadow<T> = T & {[shadowTag]: true}
export function castAsShadow<T>(value: T): Shadow<T> {
return value as any as Shadow<T>
}

View File

@ -24,7 +24,7 @@ export function RecommendedFollows({next}: Props) {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
const {isTabletOrMobile} = useWebMediaQueries() const {isTabletOrMobile} = useWebMediaQueries()
const {data: suggestedFollows, dataUpdatedAt} = useSuggestedFollowsQuery() const {data: suggestedFollows} = useSuggestedFollowsQuery()
const getSuggestedFollowsByActor = useGetSuggestedFollowersByActor() const getSuggestedFollowsByActor = useGetSuggestedFollowersByActor()
const [additionalSuggestions, setAdditionalSuggestions] = React.useState<{ const [additionalSuggestions, setAdditionalSuggestions] = React.useState<{
[did: string]: AppBskyActorDefs.ProfileView[] [did: string]: AppBskyActorDefs.ProfileView[]
@ -162,7 +162,6 @@ export function RecommendedFollows({next}: Props) {
renderItem={({item}) => ( renderItem={({item}) => (
<RecommendedFollowsItem <RecommendedFollowsItem
profile={item} profile={item}
dataUpdatedAt={dataUpdatedAt}
onFollowStateChange={onFollowStateChange} onFollowStateChange={onFollowStateChange}
moderation={moderateProfile(item, moderationOpts)} moderation={moderateProfile(item, moderationOpts)}
/> />
@ -197,7 +196,6 @@ export function RecommendedFollows({next}: Props) {
renderItem={({item}) => ( renderItem={({item}) => (
<RecommendedFollowsItem <RecommendedFollowsItem
profile={item} profile={item}
dataUpdatedAt={dataUpdatedAt}
onFollowStateChange={onFollowStateChange} onFollowStateChange={onFollowStateChange}
moderation={moderateProfile(item, moderationOpts)} moderation={moderateProfile(item, moderationOpts)}
/> />

View File

@ -18,7 +18,6 @@ import {logger} from '#/logger'
type Props = { type Props = {
profile: AppBskyActorDefs.ProfileViewBasic profile: AppBskyActorDefs.ProfileViewBasic
dataUpdatedAt: number
moderation: ProfileModeration moderation: ProfileModeration
onFollowStateChange: (props: { onFollowStateChange: (props: {
did: string did: string
@ -28,13 +27,12 @@ type Props = {
export function RecommendedFollowsItem({ export function RecommendedFollowsItem({
profile, profile,
dataUpdatedAt,
moderation, moderation,
onFollowStateChange, onFollowStateChange,
}: React.PropsWithChildren<Props>) { }: React.PropsWithChildren<Props>) {
const pal = usePalette('default') const pal = usePalette('default')
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const shadowedProfile = useProfileShadow(profile, dataUpdatedAt) const shadowedProfile = useProfileShadow(profile)
return ( return (
<Animated.View <Animated.View

View File

@ -64,7 +64,6 @@ export function ListMembers({
const { const {
data, data,
dataUpdatedAt,
isFetching, isFetching,
isFetched, isFetched,
isError, isError,
@ -185,7 +184,6 @@ export function ListMembers({
(item as AppBskyGraphDefs.ListItemView).subject.handle (item as AppBskyGraphDefs.ListItemView).subject.handle
}`} }`}
profile={(item as AppBskyGraphDefs.ListItemView).subject} profile={(item as AppBskyGraphDefs.ListItemView).subject}
dataUpdatedAt={dataUpdatedAt}
renderButton={renderMemberButton} renderButton={renderMemberButton}
style={{paddingHorizontal: isMobile ? 8 : 14, paddingVertical: 4}} style={{paddingHorizontal: isMobile ? 8 : 14, paddingVertical: 4}}
/> />
@ -198,7 +196,6 @@ export function ListMembers({
onPressTryAgain, onPressTryAgain,
onPressRetryLoadMore, onPressRetryLoadMore,
isMobile, isMobile,
dataUpdatedAt,
], ],
) )

View File

@ -22,7 +22,6 @@ export function Component({did}: {did: string}) {
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const { const {
data: profile, data: profile,
dataUpdatedAt,
error: profileError, error: profileError,
refetch: refetchProfile, refetch: refetchProfile,
isFetching: isFetchingProfile, isFetching: isFetchingProfile,
@ -51,13 +50,7 @@ export function Component({did}: {did: string}) {
) )
} }
if (profile && moderationOpts) { if (profile && moderationOpts) {
return ( return <ComponentLoaded profile={profile} moderationOpts={moderationOpts} />
<ComponentLoaded
profile={profile}
dataUpdatedAt={dataUpdatedAt}
moderationOpts={moderationOpts}
/>
)
} }
// should never happen // should never happen
return ( return (
@ -71,15 +64,13 @@ export function Component({did}: {did: string}) {
function ComponentLoaded({ function ComponentLoaded({
profile: profileUnshadowed, profile: profileUnshadowed,
dataUpdatedAt,
moderationOpts, moderationOpts,
}: { }: {
profile: AppBskyActorDefs.ProfileViewDetailed profile: AppBskyActorDefs.ProfileViewDetailed
dataUpdatedAt: number
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const profile = useProfileShadow(profileUnshadowed, dataUpdatedAt) const profile = useProfileShadow(profileUnshadowed)
const {screen} = useAnalytics() const {screen} = useAnalytics()
const moderation = React.useMemo( const moderation = React.useMemo(
() => moderateProfile(profile, moderationOpts), () => moderateProfile(profile, moderationOpts),

View File

@ -38,7 +38,6 @@ export function Feed({
const {markAllRead} = useUnreadNotificationsApi() const {markAllRead} = useUnreadNotificationsApi()
const { const {
data, data,
dataUpdatedAt,
isLoading, isLoading,
isFetching, isFetching,
isFetched, isFetched,
@ -132,15 +131,9 @@ export function Feed({
} else if (item === LOADING_ITEM) { } else if (item === LOADING_ITEM) {
return <NotificationFeedLoadingPlaceholder /> return <NotificationFeedLoadingPlaceholder />
} }
return ( return <FeedItem item={item} moderationOpts={moderationOpts!} />
<FeedItem
item={item}
dataUpdatedAt={dataUpdatedAt}
moderationOpts={moderationOpts!}
/>
)
}, },
[onPressRetryLoadMore, dataUpdatedAt, moderationOpts], [onPressRetryLoadMore, moderationOpts],
) )
const showHeaderSpinner = !isPTRing && isFetching && !isLoading const showHeaderSpinner = !isPTRing && isFetching && !isLoading

View File

@ -58,11 +58,9 @@ interface Author {
let FeedItem = ({ let FeedItem = ({
item, item,
dataUpdatedAt,
moderationOpts, moderationOpts,
}: { }: {
item: FeedNotification item: FeedNotification
dataUpdatedAt: number
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
}): React.ReactNode => { }): React.ReactNode => {
const pal = usePalette('default') const pal = usePalette('default')
@ -135,7 +133,6 @@ let FeedItem = ({
accessible={false}> accessible={false}>
<Post <Post
post={item.subject} post={item.subject}
dataUpdatedAt={dataUpdatedAt}
style={ style={
item.notification.isRead item.notification.isRead
? undefined ? undefined

View File

@ -20,7 +20,6 @@ export function PostLikedBy({uri}: {uri: string}) {
} = useResolveUriQuery(uri) } = useResolveUriQuery(uri)
const { const {
data, data,
dataUpdatedAt,
isFetching, isFetching,
isFetched, isFetched,
isFetchingNextPage, isFetchingNextPage,
@ -55,18 +54,11 @@ export function PostLikedBy({uri}: {uri: string}) {
} }
}, [isFetching, hasNextPage, isError, fetchNextPage]) }, [isFetching, hasNextPage, isError, fetchNextPage])
const renderItem = useCallback( const renderItem = useCallback(({item}: {item: GetLikes.Like}) => {
({item}: {item: GetLikes.Like}) => { return (
return ( <ProfileCardWithFollowBtn key={item.actor.did} profile={item.actor} />
<ProfileCardWithFollowBtn )
key={item.actor.did} }, [])
profile={item.actor}
dataUpdatedAt={dataUpdatedAt}
/>
)
},
[dataUpdatedAt],
)
if (isFetchingResolvedUri || !isFetched) { if (isFetchingResolvedUri || !isFetched) {
return ( return (

View File

@ -20,7 +20,6 @@ export function PostRepostedBy({uri}: {uri: string}) {
} = useResolveUriQuery(uri) } = useResolveUriQuery(uri)
const { const {
data, data,
dataUpdatedAt,
isFetching, isFetching,
isFetched, isFetched,
isFetchingNextPage, isFetchingNextPage,
@ -57,15 +56,9 @@ export function PostRepostedBy({uri}: {uri: string}) {
const renderItem = useCallback( const renderItem = useCallback(
({item}: {item: ActorDefs.ProfileViewBasic}) => { ({item}: {item: ActorDefs.ProfileViewBasic}) => {
return ( return <ProfileCardWithFollowBtn key={item.did} profile={item} />
<ProfileCardWithFollowBtn
key={item.did}
profile={item}
dataUpdatedAt={dataUpdatedAt}
/>
)
}, },
[dataUpdatedAt], [],
) )
if (isFetchingResolvedUri || !isFetched) { if (isFetchingResolvedUri || !isFetched) {

View File

@ -73,7 +73,6 @@ export function PostThread({
refetch, refetch,
isRefetching, isRefetching,
data: thread, data: thread,
dataUpdatedAt,
} = usePostThreadQuery(uri) } = usePostThreadQuery(uri)
const {data: preferences} = usePreferencesQuery() const {data: preferences} = usePreferencesQuery()
const rootPost = thread?.type === 'post' ? thread.post : undefined const rootPost = thread?.type === 'post' ? thread.post : undefined
@ -111,7 +110,6 @@ export function PostThread({
<PostThreadLoaded <PostThreadLoaded
thread={thread} thread={thread}
isRefetching={isRefetching} isRefetching={isRefetching}
dataUpdatedAt={dataUpdatedAt}
threadViewPrefs={preferences.threadViewPrefs} threadViewPrefs={preferences.threadViewPrefs}
onRefresh={refetch} onRefresh={refetch}
onPressReply={onPressReply} onPressReply={onPressReply}
@ -122,14 +120,12 @@ export function PostThread({
function PostThreadLoaded({ function PostThreadLoaded({
thread, thread,
isRefetching, isRefetching,
dataUpdatedAt,
threadViewPrefs, threadViewPrefs,
onRefresh, onRefresh,
onPressReply, onPressReply,
}: { }: {
thread: ThreadNode thread: ThreadNode
isRefetching: boolean isRefetching: boolean
dataUpdatedAt: number
threadViewPrefs: UsePreferencesQueryResponse['threadViewPrefs'] threadViewPrefs: UsePreferencesQueryResponse['threadViewPrefs']
onRefresh: () => void onRefresh: () => void
onPressReply: () => void onPressReply: () => void
@ -295,7 +291,6 @@ function PostThreadLoaded({
<PostThreadItem <PostThreadItem
post={item.post} post={item.post}
record={item.record} record={item.record}
dataUpdatedAt={dataUpdatedAt}
treeView={threadViewPrefs.lab_treeViewEnabled || false} treeView={threadViewPrefs.lab_treeViewEnabled || false}
depth={item.ctx.depth} depth={item.ctx.depth}
isHighlightedPost={item.ctx.isHighlightedPost} isHighlightedPost={item.ctx.isHighlightedPost}
@ -322,7 +317,6 @@ function PostThreadLoaded({
posts, posts,
onRefresh, onRefresh,
threadViewPrefs.lab_treeViewEnabled, threadViewPrefs.lab_treeViewEnabled,
dataUpdatedAt,
_, _,
], ],
) )

View File

@ -45,7 +45,6 @@ import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
export function PostThreadItem({ export function PostThreadItem({
post, post,
record, record,
dataUpdatedAt,
treeView, treeView,
depth, depth,
isHighlightedPost, isHighlightedPost,
@ -57,7 +56,6 @@ export function PostThreadItem({
}: { }: {
post: AppBskyFeedDefs.PostView post: AppBskyFeedDefs.PostView
record: AppBskyFeedPost.Record record: AppBskyFeedPost.Record
dataUpdatedAt: number
treeView: boolean treeView: boolean
depth: number depth: number
isHighlightedPost?: boolean isHighlightedPost?: boolean
@ -68,7 +66,7 @@ export function PostThreadItem({
onPostReply: () => void onPostReply: () => void
}) { }) {
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const postShadowed = usePostShadow(post, dataUpdatedAt) const postShadowed = usePostShadow(post)
const richText = useMemo( const richText = useMemo(
() => () =>
new RichTextAPI({ new RichTextAPI({

View File

@ -30,12 +30,10 @@ import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
export function Post({ export function Post({
post, post,
dataUpdatedAt,
showReplyLine, showReplyLine,
style, style,
}: { }: {
post: AppBskyFeedDefs.PostView post: AppBskyFeedDefs.PostView
dataUpdatedAt: number
showReplyLine?: boolean showReplyLine?: boolean
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {
@ -48,7 +46,7 @@ export function Post({
: undefined, : undefined,
[post], [post],
) )
const postShadowed = usePostShadow(post, dataUpdatedAt) const postShadowed = usePostShadow(post)
const richText = useMemo( const richText = useMemo(
() => () =>
record record

View File

@ -76,7 +76,6 @@ let Feed = ({
const opts = React.useMemo(() => ({enabled}), [enabled]) const opts = React.useMemo(() => ({enabled}), [enabled])
const { const {
data, data,
dataUpdatedAt,
isFetching, isFetching,
isFetched, isFetched,
isError, isError,
@ -200,7 +199,6 @@ let Feed = ({
return ( return (
<FeedSlice <FeedSlice
slice={item} slice={item}
dataUpdatedAt={dataUpdatedAt}
// we check for this before creating the feedItems array // we check for this before creating the feedItems array
moderationOpts={moderationOpts!} moderationOpts={moderationOpts!}
/> />
@ -208,7 +206,6 @@ let Feed = ({
}, },
[ [
feed, feed,
dataUpdatedAt,
error, error,
onPressTryAgain, onPressTryAgain,
onPressRetryLoadMore, onPressRetryLoadMore,

View File

@ -40,7 +40,6 @@ export function FeedItem({
record, record,
reason, reason,
moderation, moderation,
dataUpdatedAt,
isThreadChild, isThreadChild,
isThreadLastChild, isThreadLastChild,
isThreadParent, isThreadParent,
@ -49,12 +48,11 @@ export function FeedItem({
record: AppBskyFeedPost.Record record: AppBskyFeedPost.Record
reason: AppBskyFeedDefs.ReasonRepost | ReasonFeedSource | undefined reason: AppBskyFeedDefs.ReasonRepost | ReasonFeedSource | undefined
moderation: PostModeration moderation: PostModeration
dataUpdatedAt: number
isThreadChild?: boolean isThreadChild?: boolean
isThreadLastChild?: boolean isThreadLastChild?: boolean
isThreadParent?: boolean isThreadParent?: boolean
}) { }) {
const postShadowed = usePostShadow(post, dataUpdatedAt) const postShadowed = usePostShadow(post)
const richText = useMemo( const richText = useMemo(
() => () =>
new RichTextAPI({ new RichTextAPI({

View File

@ -11,12 +11,10 @@ import {makeProfileLink} from 'lib/routes/links'
let FeedSlice = ({ let FeedSlice = ({
slice, slice,
dataUpdatedAt,
ignoreFilterFor, ignoreFilterFor,
moderationOpts, moderationOpts,
}: { }: {
slice: FeedPostSlice slice: FeedPostSlice
dataUpdatedAt: number
ignoreFilterFor?: string ignoreFilterFor?: string
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
}): React.ReactNode => { }): React.ReactNode => {
@ -44,7 +42,6 @@ let FeedSlice = ({
record={slice.items[0].record} record={slice.items[0].record}
reason={slice.items[0].reason} reason={slice.items[0].reason}
moderation={moderations[0]} moderation={moderations[0]}
dataUpdatedAt={dataUpdatedAt}
isThreadParent={isThreadParentAt(slice.items, 0)} isThreadParent={isThreadParentAt(slice.items, 0)}
isThreadChild={isThreadChildAt(slice.items, 0)} isThreadChild={isThreadChildAt(slice.items, 0)}
/> />
@ -54,7 +51,6 @@ let FeedSlice = ({
record={slice.items[1].record} record={slice.items[1].record}
reason={slice.items[1].reason} reason={slice.items[1].reason}
moderation={moderations[1]} moderation={moderations[1]}
dataUpdatedAt={dataUpdatedAt}
isThreadParent={isThreadParentAt(slice.items, 1)} isThreadParent={isThreadParentAt(slice.items, 1)}
isThreadChild={isThreadChildAt(slice.items, 1)} isThreadChild={isThreadChildAt(slice.items, 1)}
/> />
@ -65,7 +61,6 @@ let FeedSlice = ({
record={slice.items[last].record} record={slice.items[last].record}
reason={slice.items[last].reason} reason={slice.items[last].reason}
moderation={moderations[last]} moderation={moderations[last]}
dataUpdatedAt={dataUpdatedAt}
isThreadParent={isThreadParentAt(slice.items, last)} isThreadParent={isThreadParentAt(slice.items, last)}
isThreadChild={isThreadChildAt(slice.items, last)} isThreadChild={isThreadChildAt(slice.items, last)}
isThreadLastChild isThreadLastChild
@ -83,7 +78,6 @@ let FeedSlice = ({
record={slice.items[i].record} record={slice.items[i].record}
reason={slice.items[i].reason} reason={slice.items[i].reason}
moderation={moderations[i]} moderation={moderations[i]}
dataUpdatedAt={dataUpdatedAt}
isThreadParent={isThreadParentAt(slice.items, i)} isThreadParent={isThreadParentAt(slice.items, i)}
isThreadChild={isThreadChildAt(slice.items, i)} isThreadChild={isThreadChildAt(slice.items, i)}
isThreadLastChild={ isThreadLastChild={

View File

@ -27,7 +27,6 @@ import {useSession} from '#/state/session'
export function ProfileCard({ export function ProfileCard({
testID, testID,
profile: profileUnshadowed, profile: profileUnshadowed,
dataUpdatedAt,
noBg, noBg,
noBorder, noBorder,
followers, followers,
@ -36,7 +35,6 @@ export function ProfileCard({
}: { }: {
testID?: string testID?: string
profile: AppBskyActorDefs.ProfileViewBasic profile: AppBskyActorDefs.ProfileViewBasic
dataUpdatedAt: number
noBg?: boolean noBg?: boolean
noBorder?: boolean noBorder?: boolean
followers?: AppBskyActorDefs.ProfileView[] | undefined followers?: AppBskyActorDefs.ProfileView[] | undefined
@ -46,7 +44,7 @@ export function ProfileCard({
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const profile = useProfileShadow(profileUnshadowed, dataUpdatedAt) const profile = useProfileShadow(profileUnshadowed)
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
if (!moderationOpts) { if (!moderationOpts) {
return null return null
@ -202,13 +200,11 @@ export function ProfileCardWithFollowBtn({
noBg, noBg,
noBorder, noBorder,
followers, followers,
dataUpdatedAt,
}: { }: {
profile: AppBskyActorDefs.ProfileViewBasic profile: AppBskyActorDefs.ProfileViewBasic
noBg?: boolean noBg?: boolean
noBorder?: boolean noBorder?: boolean
followers?: AppBskyActorDefs.ProfileView[] | undefined followers?: AppBskyActorDefs.ProfileView[] | undefined
dataUpdatedAt: number
}) { }) {
const {currentAccount} = useSession() const {currentAccount} = useSession()
const isMe = profile.did === currentAccount?.did const isMe = profile.did === currentAccount?.did
@ -224,7 +220,6 @@ export function ProfileCardWithFollowBtn({
? undefined ? undefined
: profileShadow => <FollowButton profile={profileShadow} /> : profileShadow => <FollowButton profile={profileShadow} />
} }
dataUpdatedAt={dataUpdatedAt}
/> />
) )
} }

View File

@ -20,7 +20,6 @@ export function ProfileFollowers({name}: {name: string}) {
} = useResolveDidQuery(name) } = useResolveDidQuery(name)
const { const {
data, data,
dataUpdatedAt,
isFetching, isFetching,
isFetched, isFetched,
isFetchingNextPage, isFetchingNextPage,
@ -58,13 +57,9 @@ export function ProfileFollowers({name}: {name: string}) {
const renderItem = React.useCallback( const renderItem = React.useCallback(
({item}: {item: ActorDefs.ProfileViewBasic}) => ( ({item}: {item: ActorDefs.ProfileViewBasic}) => (
<ProfileCardWithFollowBtn <ProfileCardWithFollowBtn key={item.did} profile={item} />
key={item.did}
profile={item}
dataUpdatedAt={dataUpdatedAt}
/>
), ),
[dataUpdatedAt], [],
) )
if (isFetchingDid || !isFetched) { if (isFetchingDid || !isFetched) {

View File

@ -20,7 +20,6 @@ export function ProfileFollows({name}: {name: string}) {
} = useResolveDidQuery(name) } = useResolveDidQuery(name)
const { const {
data, data,
dataUpdatedAt,
isFetching, isFetching,
isFetched, isFetched,
isFetchingNextPage, isFetchingNextPage,
@ -58,13 +57,9 @@ export function ProfileFollows({name}: {name: string}) {
const renderItem = React.useCallback( const renderItem = React.useCallback(
({item}: {item: ActorDefs.ProfileViewBasic}) => ( ({item}: {item: ActorDefs.ProfileViewBasic}) => (
<ProfileCardWithFollowBtn <ProfileCardWithFollowBtn key={item.did} profile={item} />
key={item.did}
profile={item}
dataUpdatedAt={dataUpdatedAt}
/>
), ),
[dataUpdatedAt], [],
) )
if (isFetchingDid || !isFetched) { if (isFetchingDid || !isFetched) {

View File

@ -65,7 +65,7 @@ export function ProfileHeaderSuggestedFollows({
} }
}, [active, animatedHeight, track]) }, [active, animatedHeight, track])
const {isLoading, data, dataUpdatedAt} = useSuggestedFollowsByActorQuery({ const {isLoading, data} = useSuggestedFollowsByActorQuery({
did: actorDid, did: actorDid,
}) })
@ -127,11 +127,7 @@ export function ProfileHeaderSuggestedFollows({
</> </>
) : data ? ( ) : data ? (
data.suggestions.map(profile => ( data.suggestions.map(profile => (
<SuggestedFollow <SuggestedFollow key={profile.did} profile={profile} />
key={profile.did}
profile={profile}
dataUpdatedAt={dataUpdatedAt}
/>
)) ))
) : ( ) : (
<View /> <View />
@ -196,15 +192,13 @@ function SuggestedFollowSkeleton() {
function SuggestedFollow({ function SuggestedFollow({
profile: profileUnshadowed, profile: profileUnshadowed,
dataUpdatedAt,
}: { }: {
profile: AppBskyActorDefs.ProfileView profile: AppBskyActorDefs.ProfileView
dataUpdatedAt: number
}) { }) {
const {track} = useAnalytics() const {track} = useAnalytics()
const pal = usePalette('default') const pal = usePalette('default')
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const profile = useProfileShadow(profileUnshadowed, dataUpdatedAt) const profile = useProfileShadow(profileUnshadowed)
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile) const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
const onPressFollow = React.useCallback(async () => { const onPressFollow = React.useCallback(async () => {

View File

@ -40,7 +40,6 @@ export const ModerationBlockedAccounts = withAuthRequired(
const [isPTRing, setIsPTRing] = React.useState(false) const [isPTRing, setIsPTRing] = React.useState(false)
const { const {
data, data,
dataUpdatedAt,
isFetching, isFetching,
isError, isError,
error, error,
@ -95,7 +94,6 @@ export const ModerationBlockedAccounts = withAuthRequired(
testID={`blockedAccount-${index}`} testID={`blockedAccount-${index}`}
key={item.did} key={item.did}
profile={item} profile={item}
dataUpdatedAt={dataUpdatedAt}
/> />
) )
return ( return (

View File

@ -40,7 +40,6 @@ export const ModerationMutedAccounts = withAuthRequired(
const [isPTRing, setIsPTRing] = React.useState(false) const [isPTRing, setIsPTRing] = React.useState(false)
const { const {
data, data,
dataUpdatedAt,
isFetching, isFetching,
isError, isError,
error, error,
@ -95,7 +94,6 @@ export const ModerationMutedAccounts = withAuthRequired(
testID={`mutedAccount-${index}`} testID={`mutedAccount-${index}`}
key={item.did} key={item.did}
profile={item} profile={item}
dataUpdatedAt={dataUpdatedAt}
/> />
) )
return ( return (

View File

@ -57,7 +57,6 @@ export const ProfileScreen = withAuthRequired(
} = useResolveDidQuery(name) } = useResolveDidQuery(name)
const { const {
data: profile, data: profile,
dataUpdatedAt,
error: profileError, error: profileError,
refetch: refetchProfile, refetch: refetchProfile,
isFetching: isFetchingProfile, isFetching: isFetchingProfile,
@ -100,7 +99,6 @@ export const ProfileScreen = withAuthRequired(
return ( return (
<ProfileScreenLoaded <ProfileScreenLoaded
profile={profile} profile={profile}
dataUpdatedAt={dataUpdatedAt}
moderationOpts={moderationOpts} moderationOpts={moderationOpts}
hideBackButton={!!route.params.hideBackButton} hideBackButton={!!route.params.hideBackButton}
/> />
@ -125,16 +123,14 @@ export const ProfileScreen = withAuthRequired(
function ProfileScreenLoaded({ function ProfileScreenLoaded({
profile: profileUnshadowed, profile: profileUnshadowed,
dataUpdatedAt,
moderationOpts, moderationOpts,
hideBackButton, hideBackButton,
}: { }: {
profile: AppBskyActorDefs.ProfileViewDetailed profile: AppBskyActorDefs.ProfileViewDetailed
dataUpdatedAt: number
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
hideBackButton: boolean hideBackButton: boolean
}) { }) {
const profile = useProfileShadow(profileUnshadowed, dataUpdatedAt) const profile = useProfileShadow(profileUnshadowed)
const {currentAccount} = useSession() const {currentAccount} = useSession()
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const {openComposer} = useComposerControls() const {openComposer} = useComposerControls()

View File

@ -111,7 +111,6 @@ function EmptyState({message, error}: {message: string; error?: string}) {
function SearchScreenSuggestedFollows() { function SearchScreenSuggestedFollows() {
const pal = usePalette('default') const pal = usePalette('default')
const {currentAccount} = useSession() const {currentAccount} = useSession()
const [dataUpdatedAt, setDataUpdatedAt] = React.useState(0)
const [suggestions, setSuggestions] = React.useState< const [suggestions, setSuggestions] = React.useState<
AppBskyActorDefs.ProfileViewBasic[] AppBskyActorDefs.ProfileViewBasic[]
>([]) >([])
@ -141,7 +140,6 @@ function SearchScreenSuggestedFollows() {
) )
setSuggestions(Array.from(friendsOfFriends.values())) setSuggestions(Array.from(friendsOfFriends.values()))
setDataUpdatedAt(Date.now())
} }
try { try {
@ -151,23 +149,12 @@ function SearchScreenSuggestedFollows() {
error: e, error: e,
}) })
} }
}, [ }, [currentAccount, setSuggestions, getSuggestedFollowsByActor])
currentAccount,
setSuggestions,
setDataUpdatedAt,
getSuggestedFollowsByActor,
])
return suggestions.length ? ( return suggestions.length ? (
<FlatList <FlatList
data={suggestions} data={suggestions}
renderItem={({item}) => ( renderItem={({item}) => <ProfileCardWithFollowBtn profile={item} noBg />}
<ProfileCardWithFollowBtn
profile={item}
noBg
dataUpdatedAt={dataUpdatedAt}
/>
)}
keyExtractor={item => item.did} keyExtractor={item => item.did}
// @ts-ignore web only -prf // @ts-ignore web only -prf
desktopFixedHeight desktopFixedHeight
@ -205,7 +192,6 @@ function SearchScreenPostResults({query}: {query: string}) {
fetchNextPage, fetchNextPage,
isFetchingNextPage, isFetchingNextPage,
hasNextPage, hasNextPage,
dataUpdatedAt,
} = useSearchPostsQuery({query}) } = useSearchPostsQuery({query})
const onPullToRefresh = React.useCallback(async () => { const onPullToRefresh = React.useCallback(async () => {
@ -258,7 +244,7 @@ function SearchScreenPostResults({query}: {query: string}) {
data={items} data={items}
renderItem={({item}) => { renderItem={({item}) => {
if (item.type === 'post') { if (item.type === 'post') {
return <Post post={item.post} dataUpdatedAt={dataUpdatedAt} /> return <Post post={item.post} />
} else { } else {
return <Loader /> return <Loader />
} }
@ -291,7 +277,6 @@ function SearchScreenPostResults({query}: {query: string}) {
function SearchScreenUserResults({query}: {query: string}) { function SearchScreenUserResults({query}: {query: string}) {
const {_} = useLingui() const {_} = useLingui()
const [isFetched, setIsFetched] = React.useState(false) const [isFetched, setIsFetched] = React.useState(false)
const [dataUpdatedAt, setDataUpdatedAt] = React.useState(0)
const [results, setResults] = React.useState< const [results, setResults] = React.useState<
AppBskyActorDefs.ProfileViewBasic[] AppBskyActorDefs.ProfileViewBasic[]
>([]) >([])
@ -302,7 +287,6 @@ function SearchScreenUserResults({query}: {query: string}) {
const searchResults = await search({query, limit: 30}) const searchResults = await search({query, limit: 30})
if (searchResults) { if (searchResults) {
setDataUpdatedAt(Date.now())
setResults(results) setResults(results)
setIsFetched(true) setIsFetched(true)
} }
@ -314,7 +298,7 @@ function SearchScreenUserResults({query}: {query: string}) {
setResults([]) setResults([])
setIsFetched(false) setIsFetched(false)
} }
}, [query, setDataUpdatedAt, search, results]) }, [query, search, results])
return isFetched ? ( return isFetched ? (
<> <>
@ -322,11 +306,7 @@ function SearchScreenUserResults({query}: {query: string}) {
<FlatList <FlatList
data={results} data={results}
renderItem={({item}) => ( renderItem={({item}) => (
<ProfileCardWithFollowBtn <ProfileCardWithFollowBtn profile={item} noBg />
profile={item}
noBg
dataUpdatedAt={dataUpdatedAt}
/>
)} )}
keyExtractor={item => item.did} keyExtractor={item => item.did}
// @ts-ignore web only -prf // @ts-ignore web only -prf