about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/lib/batchedUpdates.ts1
-rw-r--r--src/lib/batchedUpdates.web.ts2
-rw-r--r--src/state/cache/post-shadow.ts66
-rw-r--r--src/state/cache/profile-shadow.ts70
-rw-r--r--src/state/cache/types.ts8
-rw-r--r--src/view/com/auth/onboarding/RecommendedFollows.tsx4
-rw-r--r--src/view/com/auth/onboarding/RecommendedFollowsItem.tsx4
-rw-r--r--src/view/com/lists/ListMembers.tsx3
-rw-r--r--src/view/com/modals/ProfilePreview.tsx13
-rw-r--r--src/view/com/notifications/Feed.tsx11
-rw-r--r--src/view/com/notifications/FeedItem.tsx3
-rw-r--r--src/view/com/post-thread/PostLikedBy.tsx18
-rw-r--r--src/view/com/post-thread/PostRepostedBy.tsx11
-rw-r--r--src/view/com/post-thread/PostThread.tsx6
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx4
-rw-r--r--src/view/com/post/Post.tsx4
-rw-r--r--src/view/com/posts/Feed.tsx3
-rw-r--r--src/view/com/posts/FeedItem.tsx4
-rw-r--r--src/view/com/posts/FeedSlice.tsx6
-rw-r--r--src/view/com/profile/ProfileCard.tsx7
-rw-r--r--src/view/com/profile/ProfileFollowers.tsx9
-rw-r--r--src/view/com/profile/ProfileFollows.tsx9
-rw-r--r--src/view/com/profile/ProfileHeaderSuggestedFollows.tsx12
-rw-r--r--src/view/screens/ModerationBlockedAccounts.tsx2
-rw-r--r--src/view/screens/ModerationMutedAccounts.tsx2
-rw-r--r--src/view/screens/Profile.tsx6
-rw-r--r--src/view/screens/Search/Search.tsx30
27 files changed, 115 insertions, 203 deletions
diff --git a/src/lib/batchedUpdates.ts b/src/lib/batchedUpdates.ts
new file mode 100644
index 000000000..2530d6ca9
--- /dev/null
+++ b/src/lib/batchedUpdates.ts
@@ -0,0 +1 @@
+export {unstable_batchedUpdates as batchedUpdates} from 'react-native'
diff --git a/src/lib/batchedUpdates.web.ts b/src/lib/batchedUpdates.web.ts
new file mode 100644
index 000000000..03147ed67
--- /dev/null
+++ b/src/lib/batchedUpdates.web.ts
@@ -0,0 +1,2 @@
+// @ts-ignore
+export {unstable_batchedUpdates as batchedUpdates} from 'react-dom'
diff --git a/src/state/cache/post-shadow.ts b/src/state/cache/post-shadow.ts
index d20f6ebaa..b21bb7129 100644
--- a/src/state/cache/post-shadow.ts
+++ b/src/state/cache/post-shadow.ts
@@ -1,7 +1,8 @@
-import {useEffect, useState, useMemo, useCallback, useRef} from 'react'
+import {useEffect, useState, useMemo, useCallback} from 'react'
 import EventEmitter from 'eventemitter3'
 import {AppBskyFeedDefs} from '@atproto/api'
-import {Shadow} from './types'
+import {batchedUpdates} from '#/lib/batchedUpdates'
+import {Shadow, castAsShadow} from './types'
 export type {Shadow} from './types'
 
 const emitter = new EventEmitter()
@@ -21,15 +22,36 @@ interface CacheEntry {
   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(
   post: AppBskyFeedDefs.PostView,
-  ifAfterTS: number,
 ): Shadow<AppBskyFeedDefs.PostView> | typeof POST_TOMBSTONE {
-  const [state, setState] = useState<CacheEntry>({
-    ts: Date.now(),
+  const postSeenTS = getFirstSeenTS(post)
+  const [state, setState] = useState<CacheEntry>(() => ({
+    ts: postSeenTS,
     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(
     (value: Partial<PostShadow>) => {
@@ -46,30 +68,17 @@ export function usePostShadow(
     }
   }, [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 state.ts > ifAfterTS
+    return state.ts > postSeenTS
       ? mergeShadow(post, state.value)
-      : {...post, isShadowed: true}
-  }, [post, state, ifAfterTS])
+      : castAsShadow(post)
+  }, [post, state, postSeenTS])
 }
 
 export function updatePostShadow(uri: string, value: Partial<PostShadow>) {
-  emitter.emit(uri, value)
-}
-
-export function isPostShadowed(
-  v: AppBskyFeedDefs.PostView | Shadow<AppBskyFeedDefs.PostView>,
-): v is Shadow<AppBskyFeedDefs.PostView> {
-  return 'isShadowed' in v && !!v.isShadowed
+  batchedUpdates(() => {
+    emitter.emit(uri, value)
+  })
 }
 
 function fromPost(post: AppBskyFeedDefs.PostView): PostShadow {
@@ -89,7 +98,7 @@ function mergeShadow(
   if (shadow.isDeleted) {
     return POST_TOMBSTONE
   }
-  return {
+  return castAsShadow({
     ...post,
     likeCount: shadow.likeCount,
     repostCount: shadow.repostCount,
@@ -98,6 +107,5 @@ function mergeShadow(
       like: shadow.likeUri,
       repost: shadow.repostUri,
     },
-    isShadowed: true,
-  }
+  })
 }
diff --git a/src/state/cache/profile-shadow.ts b/src/state/cache/profile-shadow.ts
index 5323effaf..6ebd39132 100644
--- a/src/state/cache/profile-shadow.ts
+++ b/src/state/cache/profile-shadow.ts
@@ -1,7 +1,8 @@
-import {useEffect, useState, useMemo, useCallback, useRef} from 'react'
+import {useEffect, useState, useMemo, useCallback} from 'react'
 import EventEmitter from 'eventemitter3'
 import {AppBskyActorDefs} from '@atproto/api'
-import {Shadow} from './types'
+import {batchedUpdates} from '#/lib/batchedUpdates'
+import {Shadow, castAsShadow} from './types'
 export type {Shadow} from './types'
 
 const emitter = new EventEmitter()
@@ -22,15 +23,34 @@ type ProfileView =
   | AppBskyActorDefs.ProfileViewBasic
   | AppBskyActorDefs.ProfileViewDetailed
 
-export function useProfileShadow(
-  profile: ProfileView,
-  ifAfterTS: number,
-): Shadow<ProfileView> {
-  const [state, setState] = useState<CacheEntry>({
-    ts: Date.now(),
+const firstSeenMap = new WeakMap<ProfileView, number>()
+function getFirstSeenTS(profile: ProfileView): number {
+  let timeStamp = firstSeenMap.get(profile)
+  if (timeStamp !== undefined) {
+    return timeStamp
+  }
+  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),
-  })
-  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(
     (value: Partial<ProfileShadow>) => {
@@ -47,33 +67,20 @@ export function useProfileShadow(
     }
   }, [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 state.ts > ifAfterTS
+    return state.ts > profileSeenTS
       ? mergeShadow(profile, state.value)
-      : {...profile, isShadowed: true}
-  }, [profile, state, ifAfterTS])
+      : castAsShadow(profile)
+  }, [profile, state, profileSeenTS])
 }
 
 export function updateProfileShadow(
   uri: string,
   value: Partial<ProfileShadow>,
 ) {
-  emitter.emit(uri, value)
-}
-
-export function isProfileShadowed<T extends ProfileView>(
-  v: T | Shadow<T>,
-): v is Shadow<T> {
-  return 'isShadowed' in v && !!v.isShadowed
+  batchedUpdates(() => {
+    emitter.emit(uri, value)
+  })
 }
 
 function fromProfile(profile: ProfileView): ProfileShadow {
@@ -88,7 +95,7 @@ function mergeShadow(
   profile: ProfileView,
   shadow: ProfileShadow,
 ): Shadow<ProfileView> {
-  return {
+  return castAsShadow({
     ...profile,
     viewer: {
       ...(profile.viewer || {}),
@@ -96,6 +103,5 @@ function mergeShadow(
       muted: shadow.muted,
       blocking: shadow.blockingUri,
     },
-    isShadowed: true,
-  }
+  })
 }
diff --git a/src/state/cache/types.ts b/src/state/cache/types.ts
index 8bfcc867c..055f4167e 100644
--- a/src/state/cache/types.ts
+++ b/src/state/cache/types.ts
@@ -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>
+}
diff --git a/src/view/com/auth/onboarding/RecommendedFollows.tsx b/src/view/com/auth/onboarding/RecommendedFollows.tsx
index 7bf8c97e4..372bbec6a 100644
--- a/src/view/com/auth/onboarding/RecommendedFollows.tsx
+++ b/src/view/com/auth/onboarding/RecommendedFollows.tsx
@@ -24,7 +24,7 @@ export function RecommendedFollows({next}: Props) {
   const pal = usePalette('default')
   const {_} = useLingui()
   const {isTabletOrMobile} = useWebMediaQueries()
-  const {data: suggestedFollows, dataUpdatedAt} = useSuggestedFollowsQuery()
+  const {data: suggestedFollows} = useSuggestedFollowsQuery()
   const getSuggestedFollowsByActor = useGetSuggestedFollowersByActor()
   const [additionalSuggestions, setAdditionalSuggestions] = React.useState<{
     [did: string]: AppBskyActorDefs.ProfileView[]
@@ -162,7 +162,6 @@ export function RecommendedFollows({next}: Props) {
               renderItem={({item}) => (
                 <RecommendedFollowsItem
                   profile={item}
-                  dataUpdatedAt={dataUpdatedAt}
                   onFollowStateChange={onFollowStateChange}
                   moderation={moderateProfile(item, moderationOpts)}
                 />
@@ -197,7 +196,6 @@ export function RecommendedFollows({next}: Props) {
               renderItem={({item}) => (
                 <RecommendedFollowsItem
                   profile={item}
-                  dataUpdatedAt={dataUpdatedAt}
                   onFollowStateChange={onFollowStateChange}
                   moderation={moderateProfile(item, moderationOpts)}
                 />
diff --git a/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx b/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx
index eadc3caff..93c515f38 100644
--- a/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx
+++ b/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx
@@ -18,7 +18,6 @@ import {logger} from '#/logger'
 
 type Props = {
   profile: AppBskyActorDefs.ProfileViewBasic
-  dataUpdatedAt: number
   moderation: ProfileModeration
   onFollowStateChange: (props: {
     did: string
@@ -28,13 +27,12 @@ type Props = {
 
 export function RecommendedFollowsItem({
   profile,
-  dataUpdatedAt,
   moderation,
   onFollowStateChange,
 }: React.PropsWithChildren<Props>) {
   const pal = usePalette('default')
   const {isMobile} = useWebMediaQueries()
-  const shadowedProfile = useProfileShadow(profile, dataUpdatedAt)
+  const shadowedProfile = useProfileShadow(profile)
 
   return (
     <Animated.View
diff --git a/src/view/com/lists/ListMembers.tsx b/src/view/com/lists/ListMembers.tsx
index 940761e31..4a25c53e6 100644
--- a/src/view/com/lists/ListMembers.tsx
+++ b/src/view/com/lists/ListMembers.tsx
@@ -64,7 +64,6 @@ export function ListMembers({
 
   const {
     data,
-    dataUpdatedAt,
     isFetching,
     isFetched,
     isError,
@@ -185,7 +184,6 @@ export function ListMembers({
             (item as AppBskyGraphDefs.ListItemView).subject.handle
           }`}
           profile={(item as AppBskyGraphDefs.ListItemView).subject}
-          dataUpdatedAt={dataUpdatedAt}
           renderButton={renderMemberButton}
           style={{paddingHorizontal: isMobile ? 8 : 14, paddingVertical: 4}}
         />
@@ -198,7 +196,6 @@ export function ListMembers({
       onPressTryAgain,
       onPressRetryLoadMore,
       isMobile,
-      dataUpdatedAt,
     ],
   )
 
diff --git a/src/view/com/modals/ProfilePreview.tsx b/src/view/com/modals/ProfilePreview.tsx
index 8a505397a..edfbf6a82 100644
--- a/src/view/com/modals/ProfilePreview.tsx
+++ b/src/view/com/modals/ProfilePreview.tsx
@@ -22,7 +22,6 @@ export function Component({did}: {did: string}) {
   const moderationOpts = useModerationOpts()
   const {
     data: profile,
-    dataUpdatedAt,
     error: profileError,
     refetch: refetchProfile,
     isFetching: isFetchingProfile,
@@ -51,13 +50,7 @@ export function Component({did}: {did: string}) {
     )
   }
   if (profile && moderationOpts) {
-    return (
-      <ComponentLoaded
-        profile={profile}
-        dataUpdatedAt={dataUpdatedAt}
-        moderationOpts={moderationOpts}
-      />
-    )
+    return <ComponentLoaded profile={profile} moderationOpts={moderationOpts} />
   }
   // should never happen
   return (
@@ -71,15 +64,13 @@ export function Component({did}: {did: string}) {
 
 function ComponentLoaded({
   profile: profileUnshadowed,
-  dataUpdatedAt,
   moderationOpts,
 }: {
   profile: AppBskyActorDefs.ProfileViewDetailed
-  dataUpdatedAt: number
   moderationOpts: ModerationOpts
 }) {
   const pal = usePalette('default')
-  const profile = useProfileShadow(profileUnshadowed, dataUpdatedAt)
+  const profile = useProfileShadow(profileUnshadowed)
   const {screen} = useAnalytics()
   const moderation = React.useMemo(
     () => moderateProfile(profile, moderationOpts),
diff --git a/src/view/com/notifications/Feed.tsx b/src/view/com/notifications/Feed.tsx
index 2dac75e3c..ba88f78c0 100644
--- a/src/view/com/notifications/Feed.tsx
+++ b/src/view/com/notifications/Feed.tsx
@@ -38,7 +38,6 @@ export function Feed({
   const {markAllRead} = useUnreadNotificationsApi()
   const {
     data,
-    dataUpdatedAt,
     isLoading,
     isFetching,
     isFetched,
@@ -132,15 +131,9 @@ export function Feed({
       } else if (item === LOADING_ITEM) {
         return <NotificationFeedLoadingPlaceholder />
       }
-      return (
-        <FeedItem
-          item={item}
-          dataUpdatedAt={dataUpdatedAt}
-          moderationOpts={moderationOpts!}
-        />
-      )
+      return <FeedItem item={item} moderationOpts={moderationOpts!} />
     },
-    [onPressRetryLoadMore, dataUpdatedAt, moderationOpts],
+    [onPressRetryLoadMore, moderationOpts],
   )
 
   const showHeaderSpinner = !isPTRing && isFetching && !isLoading
diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx
index 75d44a2fc..aaa2ea2c6 100644
--- a/src/view/com/notifications/FeedItem.tsx
+++ b/src/view/com/notifications/FeedItem.tsx
@@ -58,11 +58,9 @@ interface Author {
 
 let FeedItem = ({
   item,
-  dataUpdatedAt,
   moderationOpts,
 }: {
   item: FeedNotification
-  dataUpdatedAt: number
   moderationOpts: ModerationOpts
 }): React.ReactNode => {
   const pal = usePalette('default')
@@ -135,7 +133,6 @@ let FeedItem = ({
         accessible={false}>
         <Post
           post={item.subject}
-          dataUpdatedAt={dataUpdatedAt}
           style={
             item.notification.isRead
               ? undefined
diff --git a/src/view/com/post-thread/PostLikedBy.tsx b/src/view/com/post-thread/PostLikedBy.tsx
index d3b5ae47b..60afe1f9c 100644
--- a/src/view/com/post-thread/PostLikedBy.tsx
+++ b/src/view/com/post-thread/PostLikedBy.tsx
@@ -20,7 +20,6 @@ export function PostLikedBy({uri}: {uri: string}) {
   } = useResolveUriQuery(uri)
   const {
     data,
-    dataUpdatedAt,
     isFetching,
     isFetched,
     isFetchingNextPage,
@@ -55,18 +54,11 @@ export function PostLikedBy({uri}: {uri: string}) {
     }
   }, [isFetching, hasNextPage, isError, fetchNextPage])
 
-  const renderItem = useCallback(
-    ({item}: {item: GetLikes.Like}) => {
-      return (
-        <ProfileCardWithFollowBtn
-          key={item.actor.did}
-          profile={item.actor}
-          dataUpdatedAt={dataUpdatedAt}
-        />
-      )
-    },
-    [dataUpdatedAt],
-  )
+  const renderItem = useCallback(({item}: {item: GetLikes.Like}) => {
+    return (
+      <ProfileCardWithFollowBtn key={item.actor.did} profile={item.actor} />
+    )
+  }, [])
 
   if (isFetchingResolvedUri || !isFetched) {
     return (
diff --git a/src/view/com/post-thread/PostRepostedBy.tsx b/src/view/com/post-thread/PostRepostedBy.tsx
index 67c043a21..1162fec40 100644
--- a/src/view/com/post-thread/PostRepostedBy.tsx
+++ b/src/view/com/post-thread/PostRepostedBy.tsx
@@ -20,7 +20,6 @@ export function PostRepostedBy({uri}: {uri: string}) {
   } = useResolveUriQuery(uri)
   const {
     data,
-    dataUpdatedAt,
     isFetching,
     isFetched,
     isFetchingNextPage,
@@ -57,15 +56,9 @@ export function PostRepostedBy({uri}: {uri: string}) {
 
   const renderItem = useCallback(
     ({item}: {item: ActorDefs.ProfileViewBasic}) => {
-      return (
-        <ProfileCardWithFollowBtn
-          key={item.did}
-          profile={item}
-          dataUpdatedAt={dataUpdatedAt}
-        />
-      )
+      return <ProfileCardWithFollowBtn key={item.did} profile={item} />
     },
-    [dataUpdatedAt],
+    [],
   )
 
   if (isFetchingResolvedUri || !isFetched) {
diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx
index 1c9f2c16f..55448fcc9 100644
--- a/src/view/com/post-thread/PostThread.tsx
+++ b/src/view/com/post-thread/PostThread.tsx
@@ -73,7 +73,6 @@ export function PostThread({
     refetch,
     isRefetching,
     data: thread,
-    dataUpdatedAt,
   } = usePostThreadQuery(uri)
   const {data: preferences} = usePreferencesQuery()
   const rootPost = thread?.type === 'post' ? thread.post : undefined
@@ -111,7 +110,6 @@ export function PostThread({
     <PostThreadLoaded
       thread={thread}
       isRefetching={isRefetching}
-      dataUpdatedAt={dataUpdatedAt}
       threadViewPrefs={preferences.threadViewPrefs}
       onRefresh={refetch}
       onPressReply={onPressReply}
@@ -122,14 +120,12 @@ export function PostThread({
 function PostThreadLoaded({
   thread,
   isRefetching,
-  dataUpdatedAt,
   threadViewPrefs,
   onRefresh,
   onPressReply,
 }: {
   thread: ThreadNode
   isRefetching: boolean
-  dataUpdatedAt: number
   threadViewPrefs: UsePreferencesQueryResponse['threadViewPrefs']
   onRefresh: () => void
   onPressReply: () => void
@@ -295,7 +291,6 @@ function PostThreadLoaded({
           <PostThreadItem
             post={item.post}
             record={item.record}
-            dataUpdatedAt={dataUpdatedAt}
             treeView={threadViewPrefs.lab_treeViewEnabled || false}
             depth={item.ctx.depth}
             isHighlightedPost={item.ctx.isHighlightedPost}
@@ -322,7 +317,6 @@ function PostThreadLoaded({
       posts,
       onRefresh,
       threadViewPrefs.lab_treeViewEnabled,
-      dataUpdatedAt,
       _,
     ],
   )
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index a4534b887..a4b7a4a9c 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -45,7 +45,6 @@ import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
 export function PostThreadItem({
   post,
   record,
-  dataUpdatedAt,
   treeView,
   depth,
   isHighlightedPost,
@@ -57,7 +56,6 @@ export function PostThreadItem({
 }: {
   post: AppBskyFeedDefs.PostView
   record: AppBskyFeedPost.Record
-  dataUpdatedAt: number
   treeView: boolean
   depth: number
   isHighlightedPost?: boolean
@@ -68,7 +66,7 @@ export function PostThreadItem({
   onPostReply: () => void
 }) {
   const moderationOpts = useModerationOpts()
-  const postShadowed = usePostShadow(post, dataUpdatedAt)
+  const postShadowed = usePostShadow(post)
   const richText = useMemo(
     () =>
       new RichTextAPI({
diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx
index 00dd4419f..2e8019e71 100644
--- a/src/view/com/post/Post.tsx
+++ b/src/view/com/post/Post.tsx
@@ -30,12 +30,10 @@ import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
 
 export function Post({
   post,
-  dataUpdatedAt,
   showReplyLine,
   style,
 }: {
   post: AppBskyFeedDefs.PostView
-  dataUpdatedAt: number
   showReplyLine?: boolean
   style?: StyleProp<ViewStyle>
 }) {
@@ -48,7 +46,7 @@ export function Post({
         : undefined,
     [post],
   )
-  const postShadowed = usePostShadow(post, dataUpdatedAt)
+  const postShadowed = usePostShadow(post)
   const richText = useMemo(
     () =>
       record
diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx
index cd4560c13..fc6d77696 100644
--- a/src/view/com/posts/Feed.tsx
+++ b/src/view/com/posts/Feed.tsx
@@ -76,7 +76,6 @@ let Feed = ({
   const opts = React.useMemo(() => ({enabled}), [enabled])
   const {
     data,
-    dataUpdatedAt,
     isFetching,
     isFetched,
     isError,
@@ -200,7 +199,6 @@ let Feed = ({
       return (
         <FeedSlice
           slice={item}
-          dataUpdatedAt={dataUpdatedAt}
           // we check for this before creating the feedItems array
           moderationOpts={moderationOpts!}
         />
@@ -208,7 +206,6 @@ let Feed = ({
     },
     [
       feed,
-      dataUpdatedAt,
       error,
       onPressTryAgain,
       onPressRetryLoadMore,
diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx
index 19f2b0e6c..dfb0cfcf6 100644
--- a/src/view/com/posts/FeedItem.tsx
+++ b/src/view/com/posts/FeedItem.tsx
@@ -40,7 +40,6 @@ export function FeedItem({
   record,
   reason,
   moderation,
-  dataUpdatedAt,
   isThreadChild,
   isThreadLastChild,
   isThreadParent,
@@ -49,12 +48,11 @@ export function FeedItem({
   record: AppBskyFeedPost.Record
   reason: AppBskyFeedDefs.ReasonRepost | ReasonFeedSource | undefined
   moderation: PostModeration
-  dataUpdatedAt: number
   isThreadChild?: boolean
   isThreadLastChild?: boolean
   isThreadParent?: boolean
 }) {
-  const postShadowed = usePostShadow(post, dataUpdatedAt)
+  const postShadowed = usePostShadow(post)
   const richText = useMemo(
     () =>
       new RichTextAPI({
diff --git a/src/view/com/posts/FeedSlice.tsx b/src/view/com/posts/FeedSlice.tsx
index 06f11aa64..a3bacdc1e 100644
--- a/src/view/com/posts/FeedSlice.tsx
+++ b/src/view/com/posts/FeedSlice.tsx
@@ -11,12 +11,10 @@ import {makeProfileLink} from 'lib/routes/links'
 
 let FeedSlice = ({
   slice,
-  dataUpdatedAt,
   ignoreFilterFor,
   moderationOpts,
 }: {
   slice: FeedPostSlice
-  dataUpdatedAt: number
   ignoreFilterFor?: string
   moderationOpts: ModerationOpts
 }): React.ReactNode => {
@@ -44,7 +42,6 @@ let FeedSlice = ({
           record={slice.items[0].record}
           reason={slice.items[0].reason}
           moderation={moderations[0]}
-          dataUpdatedAt={dataUpdatedAt}
           isThreadParent={isThreadParentAt(slice.items, 0)}
           isThreadChild={isThreadChildAt(slice.items, 0)}
         />
@@ -54,7 +51,6 @@ let FeedSlice = ({
           record={slice.items[1].record}
           reason={slice.items[1].reason}
           moderation={moderations[1]}
-          dataUpdatedAt={dataUpdatedAt}
           isThreadParent={isThreadParentAt(slice.items, 1)}
           isThreadChild={isThreadChildAt(slice.items, 1)}
         />
@@ -65,7 +61,6 @@ let FeedSlice = ({
           record={slice.items[last].record}
           reason={slice.items[last].reason}
           moderation={moderations[last]}
-          dataUpdatedAt={dataUpdatedAt}
           isThreadParent={isThreadParentAt(slice.items, last)}
           isThreadChild={isThreadChildAt(slice.items, last)}
           isThreadLastChild
@@ -83,7 +78,6 @@ let FeedSlice = ({
           record={slice.items[i].record}
           reason={slice.items[i].reason}
           moderation={moderations[i]}
-          dataUpdatedAt={dataUpdatedAt}
           isThreadParent={isThreadParentAt(slice.items, i)}
           isThreadChild={isThreadChildAt(slice.items, i)}
           isThreadLastChild={
diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx
index cd9855456..279e00d75 100644
--- a/src/view/com/profile/ProfileCard.tsx
+++ b/src/view/com/profile/ProfileCard.tsx
@@ -27,7 +27,6 @@ import {useSession} from '#/state/session'
 export function ProfileCard({
   testID,
   profile: profileUnshadowed,
-  dataUpdatedAt,
   noBg,
   noBorder,
   followers,
@@ -36,7 +35,6 @@ export function ProfileCard({
 }: {
   testID?: string
   profile: AppBskyActorDefs.ProfileViewBasic
-  dataUpdatedAt: number
   noBg?: boolean
   noBorder?: boolean
   followers?: AppBskyActorDefs.ProfileView[] | undefined
@@ -46,7 +44,7 @@ export function ProfileCard({
   style?: StyleProp<ViewStyle>
 }) {
   const pal = usePalette('default')
-  const profile = useProfileShadow(profileUnshadowed, dataUpdatedAt)
+  const profile = useProfileShadow(profileUnshadowed)
   const moderationOpts = useModerationOpts()
   if (!moderationOpts) {
     return null
@@ -202,13 +200,11 @@ export function ProfileCardWithFollowBtn({
   noBg,
   noBorder,
   followers,
-  dataUpdatedAt,
 }: {
   profile: AppBskyActorDefs.ProfileViewBasic
   noBg?: boolean
   noBorder?: boolean
   followers?: AppBskyActorDefs.ProfileView[] | undefined
-  dataUpdatedAt: number
 }) {
   const {currentAccount} = useSession()
   const isMe = profile.did === currentAccount?.did
@@ -224,7 +220,6 @@ export function ProfileCardWithFollowBtn({
           ? undefined
           : profileShadow => <FollowButton profile={profileShadow} />
       }
-      dataUpdatedAt={dataUpdatedAt}
     />
   )
 }
diff --git a/src/view/com/profile/ProfileFollowers.tsx b/src/view/com/profile/ProfileFollowers.tsx
index b9e8c0c48..45c1b3ad6 100644
--- a/src/view/com/profile/ProfileFollowers.tsx
+++ b/src/view/com/profile/ProfileFollowers.tsx
@@ -20,7 +20,6 @@ export function ProfileFollowers({name}: {name: string}) {
   } = useResolveDidQuery(name)
   const {
     data,
-    dataUpdatedAt,
     isFetching,
     isFetched,
     isFetchingNextPage,
@@ -58,13 +57,9 @@ export function ProfileFollowers({name}: {name: string}) {
 
   const renderItem = React.useCallback(
     ({item}: {item: ActorDefs.ProfileViewBasic}) => (
-      <ProfileCardWithFollowBtn
-        key={item.did}
-        profile={item}
-        dataUpdatedAt={dataUpdatedAt}
-      />
+      <ProfileCardWithFollowBtn key={item.did} profile={item} />
     ),
-    [dataUpdatedAt],
+    [],
   )
 
   if (isFetchingDid || !isFetched) {
diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx
index 77ae72da4..e1dce78a7 100644
--- a/src/view/com/profile/ProfileFollows.tsx
+++ b/src/view/com/profile/ProfileFollows.tsx
@@ -20,7 +20,6 @@ export function ProfileFollows({name}: {name: string}) {
   } = useResolveDidQuery(name)
   const {
     data,
-    dataUpdatedAt,
     isFetching,
     isFetched,
     isFetchingNextPage,
@@ -58,13 +57,9 @@ export function ProfileFollows({name}: {name: string}) {
 
   const renderItem = React.useCallback(
     ({item}: {item: ActorDefs.ProfileViewBasic}) => (
-      <ProfileCardWithFollowBtn
-        key={item.did}
-        profile={item}
-        dataUpdatedAt={dataUpdatedAt}
-      />
+      <ProfileCardWithFollowBtn key={item.did} profile={item} />
     ),
-    [dataUpdatedAt],
+    [],
   )
 
   if (isFetchingDid || !isFetched) {
diff --git a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx
index 6e060af49..f648c9801 100644
--- a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx
+++ b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx
@@ -65,7 +65,7 @@ export function ProfileHeaderSuggestedFollows({
     }
   }, [active, animatedHeight, track])
 
-  const {isLoading, data, dataUpdatedAt} = useSuggestedFollowsByActorQuery({
+  const {isLoading, data} = useSuggestedFollowsByActorQuery({
     did: actorDid,
   })
 
@@ -127,11 +127,7 @@ export function ProfileHeaderSuggestedFollows({
               </>
             ) : data ? (
               data.suggestions.map(profile => (
-                <SuggestedFollow
-                  key={profile.did}
-                  profile={profile}
-                  dataUpdatedAt={dataUpdatedAt}
-                />
+                <SuggestedFollow key={profile.did} profile={profile} />
               ))
             ) : (
               <View />
@@ -196,15 +192,13 @@ function SuggestedFollowSkeleton() {
 
 function SuggestedFollow({
   profile: profileUnshadowed,
-  dataUpdatedAt,
 }: {
   profile: AppBskyActorDefs.ProfileView
-  dataUpdatedAt: number
 }) {
   const {track} = useAnalytics()
   const pal = usePalette('default')
   const moderationOpts = useModerationOpts()
-  const profile = useProfileShadow(profileUnshadowed, dataUpdatedAt)
+  const profile = useProfileShadow(profileUnshadowed)
   const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
 
   const onPressFollow = React.useCallback(async () => {
diff --git a/src/view/screens/ModerationBlockedAccounts.tsx b/src/view/screens/ModerationBlockedAccounts.tsx
index 1c592dde8..c03275f5d 100644
--- a/src/view/screens/ModerationBlockedAccounts.tsx
+++ b/src/view/screens/ModerationBlockedAccounts.tsx
@@ -40,7 +40,6 @@ export const ModerationBlockedAccounts = withAuthRequired(
     const [isPTRing, setIsPTRing] = React.useState(false)
     const {
       data,
-      dataUpdatedAt,
       isFetching,
       isError,
       error,
@@ -95,7 +94,6 @@ export const ModerationBlockedAccounts = withAuthRequired(
         testID={`blockedAccount-${index}`}
         key={item.did}
         profile={item}
-        dataUpdatedAt={dataUpdatedAt}
       />
     )
     return (
diff --git a/src/view/screens/ModerationMutedAccounts.tsx b/src/view/screens/ModerationMutedAccounts.tsx
index 36bcbf1fa..c0ff17eb4 100644
--- a/src/view/screens/ModerationMutedAccounts.tsx
+++ b/src/view/screens/ModerationMutedAccounts.tsx
@@ -40,7 +40,6 @@ export const ModerationMutedAccounts = withAuthRequired(
     const [isPTRing, setIsPTRing] = React.useState(false)
     const {
       data,
-      dataUpdatedAt,
       isFetching,
       isError,
       error,
@@ -95,7 +94,6 @@ export const ModerationMutedAccounts = withAuthRequired(
         testID={`mutedAccount-${index}`}
         key={item.did}
         profile={item}
-        dataUpdatedAt={dataUpdatedAt}
       />
     )
     return (
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index 5411bc044..7b25e012c 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -57,7 +57,6 @@ export const ProfileScreen = withAuthRequired(
     } = useResolveDidQuery(name)
     const {
       data: profile,
-      dataUpdatedAt,
       error: profileError,
       refetch: refetchProfile,
       isFetching: isFetchingProfile,
@@ -100,7 +99,6 @@ export const ProfileScreen = withAuthRequired(
       return (
         <ProfileScreenLoaded
           profile={profile}
-          dataUpdatedAt={dataUpdatedAt}
           moderationOpts={moderationOpts}
           hideBackButton={!!route.params.hideBackButton}
         />
@@ -125,16 +123,14 @@ export const ProfileScreen = withAuthRequired(
 
 function ProfileScreenLoaded({
   profile: profileUnshadowed,
-  dataUpdatedAt,
   moderationOpts,
   hideBackButton,
 }: {
   profile: AppBskyActorDefs.ProfileViewDetailed
-  dataUpdatedAt: number
   moderationOpts: ModerationOpts
   hideBackButton: boolean
 }) {
-  const profile = useProfileShadow(profileUnshadowed, dataUpdatedAt)
+  const profile = useProfileShadow(profileUnshadowed)
   const {currentAccount} = useSession()
   const setMinimalShellMode = useSetMinimalShellMode()
   const {openComposer} = useComposerControls()
diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx
index 0788dd79d..9cd7ee370 100644
--- a/src/view/screens/Search/Search.tsx
+++ b/src/view/screens/Search/Search.tsx
@@ -111,7 +111,6 @@ function EmptyState({message, error}: {message: string; error?: string}) {
 function SearchScreenSuggestedFollows() {
   const pal = usePalette('default')
   const {currentAccount} = useSession()
-  const [dataUpdatedAt, setDataUpdatedAt] = React.useState(0)
   const [suggestions, setSuggestions] = React.useState<
     AppBskyActorDefs.ProfileViewBasic[]
   >([])
@@ -141,7 +140,6 @@ function SearchScreenSuggestedFollows() {
       )
 
       setSuggestions(Array.from(friendsOfFriends.values()))
-      setDataUpdatedAt(Date.now())
     }
 
     try {
@@ -151,23 +149,12 @@ function SearchScreenSuggestedFollows() {
         error: e,
       })
     }
-  }, [
-    currentAccount,
-    setSuggestions,
-    setDataUpdatedAt,
-    getSuggestedFollowsByActor,
-  ])
+  }, [currentAccount, setSuggestions, getSuggestedFollowsByActor])
 
   return suggestions.length ? (
     <FlatList
       data={suggestions}
-      renderItem={({item}) => (
-        <ProfileCardWithFollowBtn
-          profile={item}
-          noBg
-          dataUpdatedAt={dataUpdatedAt}
-        />
-      )}
+      renderItem={({item}) => <ProfileCardWithFollowBtn profile={item} noBg />}
       keyExtractor={item => item.did}
       // @ts-ignore web only -prf
       desktopFixedHeight
@@ -205,7 +192,6 @@ function SearchScreenPostResults({query}: {query: string}) {
     fetchNextPage,
     isFetchingNextPage,
     hasNextPage,
-    dataUpdatedAt,
   } = useSearchPostsQuery({query})
 
   const onPullToRefresh = React.useCallback(async () => {
@@ -258,7 +244,7 @@ function SearchScreenPostResults({query}: {query: string}) {
               data={items}
               renderItem={({item}) => {
                 if (item.type === 'post') {
-                  return <Post post={item.post} dataUpdatedAt={dataUpdatedAt} />
+                  return <Post post={item.post} />
                 } else {
                   return <Loader />
                 }
@@ -291,7 +277,6 @@ function SearchScreenPostResults({query}: {query: string}) {
 function SearchScreenUserResults({query}: {query: string}) {
   const {_} = useLingui()
   const [isFetched, setIsFetched] = React.useState(false)
-  const [dataUpdatedAt, setDataUpdatedAt] = React.useState(0)
   const [results, setResults] = React.useState<
     AppBskyActorDefs.ProfileViewBasic[]
   >([])
@@ -302,7 +287,6 @@ function SearchScreenUserResults({query}: {query: string}) {
       const searchResults = await search({query, limit: 30})
 
       if (searchResults) {
-        setDataUpdatedAt(Date.now())
         setResults(results)
         setIsFetched(true)
       }
@@ -314,7 +298,7 @@ function SearchScreenUserResults({query}: {query: string}) {
       setResults([])
       setIsFetched(false)
     }
-  }, [query, setDataUpdatedAt, search, results])
+  }, [query, search, results])
 
   return isFetched ? (
     <>
@@ -322,11 +306,7 @@ function SearchScreenUserResults({query}: {query: string}) {
         <FlatList
           data={results}
           renderItem={({item}) => (
-            <ProfileCardWithFollowBtn
-              profile={item}
-              noBg
-              dataUpdatedAt={dataUpdatedAt}
-            />
+            <ProfileCardWithFollowBtn profile={item} noBg />
           )}
           keyExtractor={item => item.did}
           // @ts-ignore web only -prf