PostThread perf: reduce time wasted on rendering skeletons (#10092)
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import {memo, useCallback, useMemo} from 'react'
|
import {memo, useMemo} from 'react'
|
||||||
import {Text as RNText, View} from 'react-native'
|
import {Text as RNText, View} from 'react-native'
|
||||||
import {
|
import {
|
||||||
AppBskyFeedDefs,
|
AppBskyFeedDefs,
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||||
import {makeProfileLink} from '#/lib/routes/links'
|
import {makeProfileLink} from '#/lib/routes/links'
|
||||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||||
@@ -245,7 +246,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
|||||||
}
|
}
|
||||||
}, [postSource])
|
}, [postSource])
|
||||||
|
|
||||||
const onPressReply = useCallback(() => {
|
const onPressReply = useNonReactiveCallback(() => {
|
||||||
openComposer({
|
openComposer({
|
||||||
replyTo: {
|
replyTo: {
|
||||||
uri: post.uri,
|
uri: post.uri,
|
||||||
@@ -268,15 +269,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
|||||||
reqId: postSource.post.reqId,
|
reqId: postSource.post.reqId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [
|
})
|
||||||
openComposer,
|
|
||||||
post,
|
|
||||||
record,
|
|
||||||
onPostSuccess,
|
|
||||||
moderation,
|
|
||||||
postSource,
|
|
||||||
feedFeedback,
|
|
||||||
])
|
|
||||||
|
|
||||||
const onOpenAuthor = () => {
|
const onOpenAuthor = () => {
|
||||||
ax.metric('post:clickthroughAuthor', {
|
ax.metric('post:clickthroughAuthor', {
|
||||||
|
|||||||
@@ -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 {useWindowDimensions, View} from 'react-native'
|
||||||
import Animated, {useAnimatedStyle} from 'react-native-reanimated'
|
import Animated, {useAnimatedStyle} from 'react-native-reanimated'
|
||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||||
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||||
import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking'
|
import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking'
|
||||||
import {useFeedFeedback} from '#/state/feed-feedback'
|
import {useFeedFeedback} from '#/state/feed-feedback'
|
||||||
@@ -98,7 +106,7 @@ export function PostThread({uri}: {uri: string}) {
|
|||||||
const trackThreadItemView = usePostViewTracking('PostThreadItem')
|
const trackThreadItemView = usePostViewTracking('PostThreadItem')
|
||||||
|
|
||||||
const {openComposer} = useOpenComposer()
|
const {openComposer} = useOpenComposer()
|
||||||
const optimisticOnPostReply = useCallback(
|
const optimisticOnPostReply = useNonReactiveCallback(
|
||||||
(payload: OnPostSuccessData) => {
|
(payload: OnPostSuccessData) => {
|
||||||
if (payload) {
|
if (payload) {
|
||||||
const {replyToUri, posts} = 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') {
|
if (anchor?.type !== 'threadPost') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -136,13 +143,7 @@ export function PostThread({uri}: {uri: string}) {
|
|||||||
reqId: anchorPostSource.post.reqId,
|
reqId: anchorPostSource.post.reqId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [
|
})
|
||||||
anchor,
|
|
||||||
openComposer,
|
|
||||||
optimisticOnPostReply,
|
|
||||||
anchorPostSource,
|
|
||||||
feedFeedback,
|
|
||||||
])
|
|
||||||
|
|
||||||
const isRoot = !!anchor && anchor.value.post.record.reply === undefined
|
const isRoot = !!anchor && anchor.value.post.record.reply === undefined
|
||||||
const canReply = !anchor?.value.post?.viewer?.replyDisabled
|
const canReply = !anchor?.value.post?.viewer?.replyDisabled
|
||||||
@@ -389,12 +390,34 @@ export function PostThread({uri}: {uri: string}) {
|
|||||||
return results
|
return results
|
||||||
}, [thread, deferParents, maxParentCount, maxChildrenCount])
|
}, [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(() => {
|
const isTombstoneView = useMemo(() => {
|
||||||
if (slices.length > 1) return false
|
if (deferredSlices.length > 1) return false
|
||||||
return slices.every(
|
return deferredSlices.every(
|
||||||
s => s.type === 'threadPostBlocked' || s.type === 'threadPostNotFound',
|
s => s.type === 'threadPostBlocked' || s.type === 'threadPostNotFound',
|
||||||
)
|
)
|
||||||
}, [slices])
|
}, [deferredSlices])
|
||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
({item, index}: {item: ThreadItem; index: number}) => {
|
({item, index}: {item: ThreadItem; index: number}) => {
|
||||||
@@ -547,7 +570,7 @@ export function PostThread({uri}: {uri: string}) {
|
|||||||
) : (
|
) : (
|
||||||
<List
|
<List
|
||||||
ref={listRef}
|
ref={listRef}
|
||||||
data={slices}
|
data={deferredSlices}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
onContentSizeChange={platform({
|
onContentSizeChange={platform({
|
||||||
@@ -594,8 +617,10 @@ export function PostThread({uri}: {uri: string}) {
|
|||||||
initialNumToRender={initialNumToRender}
|
initialNumToRender={initialNumToRender}
|
||||||
/**
|
/**
|
||||||
* Default: 21
|
* Default: 21
|
||||||
|
*
|
||||||
|
* Smaller for placeholder data so we don't waste time rendering skeletons
|
||||||
*/
|
*/
|
||||||
windowSize={7}
|
windowSize={thread.state.isPlaceholderData ? 1 : 7}
|
||||||
/**
|
/**
|
||||||
* Default: 10
|
* Default: 10
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user