PostThread perf: reduce time wasted on rendering skeletons (#10092)

This commit is contained in:
Samuel Newman
2026-03-25 12:44:36 -07:00
committed by GitHub
parent cc5580848c
commit c06312f09a
2 changed files with 45 additions and 27 deletions
@@ -1,4 +1,4 @@
import {memo, useCallback, useMemo} from 'react'
import {memo, useMemo} from 'react'
import {Text as RNText, View} from 'react-native'
import {
AppBskyFeedDefs,
@@ -9,6 +9,7 @@ import {
} from '@atproto/api'
import {Plural, Trans, useLingui} from '@lingui/react/macro'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
import {makeProfileLink} from '#/lib/routes/links'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
@@ -245,7 +246,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
}
}, [postSource])
const onPressReply = useCallback(() => {
const onPressReply = useNonReactiveCallback(() => {
openComposer({
replyTo: {
uri: post.uri,
@@ -268,15 +269,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
reqId: postSource.post.reqId,
})
}
}, [
openComposer,
post,
record,
onPostSuccess,
moderation,
postSource,
feedFeedback,
])
})
const onOpenAuthor = () => {
ax.metric('post:clickthroughAuthor', {
+41 -16
View File
@@ -1,9 +1,17 @@
import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {
startTransition,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import {useWindowDimensions, View} from 'react-native'
import Animated, {useAnimatedStyle} from 'react-native-reanimated'
import {Trans} from '@lingui/react/macro'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking'
import {useFeedFeedback} from '#/state/feed-feedback'
@@ -98,7 +106,7 @@ export function PostThread({uri}: {uri: string}) {
const trackThreadItemView = usePostViewTracking('PostThreadItem')
const {openComposer} = useOpenComposer()
const optimisticOnPostReply = useCallback(
const optimisticOnPostReply = useNonReactiveCallback(
(payload: OnPostSuccessData) => {
if (payload) {
const {replyToUri, posts} = payload
@@ -107,9 +115,8 @@ export function PostThread({uri}: {uri: string}) {
}
}
},
[thread],
)
const onReplyToAnchor = useCallback(() => {
const onReplyToAnchor = useNonReactiveCallback(() => {
if (anchor?.type !== 'threadPost') {
return
}
@@ -136,13 +143,7 @@ export function PostThread({uri}: {uri: string}) {
reqId: anchorPostSource.post.reqId,
})
}
}, [
anchor,
openComposer,
optimisticOnPostReply,
anchorPostSource,
feedFeedback,
])
})
const isRoot = !!anchor && anchor.value.post.record.reply === undefined
const canReply = !anchor?.value.post?.viewer?.replyDisabled
@@ -389,12 +390,34 @@ export function PostThread({uri}: {uri: string}) {
return results
}, [thread, deferParents, maxParentCount, maxChildrenCount])
/**
* Defer rendering reply skeletons so that the anchor post (from cache)
* can paint without being blocked by skeleton layout work. On mount,
* skeletons are filtered out. After the first render, they're added
* back via a low-priority transition.
*/
const [showReplySkeletons, setShowReplySkeletons] = useState(false)
useEffect(() => {
if (thread.state.isPlaceholderData && !showReplySkeletons) {
startTransition(() => {
setShowReplySkeletons(true)
})
}
}, [thread.state.isPlaceholderData, showReplySkeletons])
const deferredSlices = useMemo(() => {
if (showReplySkeletons) return slices
return slices.filter(
item => !(item.type === 'skeleton' && item.item === 'reply'),
)
}, [slices, showReplySkeletons])
const isTombstoneView = useMemo(() => {
if (slices.length > 1) return false
return slices.every(
if (deferredSlices.length > 1) return false
return deferredSlices.every(
s => s.type === 'threadPostBlocked' || s.type === 'threadPostNotFound',
)
}, [slices])
}, [deferredSlices])
const renderItem = useCallback(
({item, index}: {item: ThreadItem; index: number}) => {
@@ -547,7 +570,7 @@ export function PostThread({uri}: {uri: string}) {
) : (
<List
ref={listRef}
data={slices}
data={deferredSlices}
renderItem={renderItem}
keyExtractor={keyExtractor}
onContentSizeChange={platform({
@@ -594,8 +617,10 @@ export function PostThread({uri}: {uri: string}) {
initialNumToRender={initialNumToRender}
/**
* Default: 21
*
* Smaller for placeholder data so we don't waste time rendering skeletons
*/
windowSize={7}
windowSize={thread.state.isPlaceholderData ? 1 : 7}
/**
* Default: 10
*/