diff --git a/src/screens/PostThread/components/ThreadItemAnchor.tsx b/src/screens/PostThread/components/ThreadItemAnchor.tsx index f0eca21224..42574c7408 100644 --- a/src/screens/PostThread/components/ThreadItemAnchor.tsx +++ b/src/screens/PostThread/components/ThreadItemAnchor.tsx @@ -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', { diff --git a/src/screens/PostThread/index.tsx b/src/screens/PostThread/index.tsx index 385879f31e..c3c9405287 100644 --- a/src/screens/PostThread/index.tsx +++ b/src/screens/PostThread/index.tsx @@ -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}) { ) : (