this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

authored by

Samuel Newman and committed by
GitHub
c06312f0 cc558084

+45 -27
+4 -11
src/screens/PostThread/components/ThreadItemAnchor.tsx
··· 1 - import {memo, useCallback, useMemo} from 'react' 1 + import {memo, useMemo} from 'react' 2 2 import {Text as RNText, View} from 'react-native' 3 3 import { 4 4 AppBskyFeedDefs, ··· 9 9 } from '@atproto/api' 10 10 import {Plural, Trans, useLingui} from '@lingui/react/macro' 11 11 12 + import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 12 13 import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 13 14 import {makeProfileLink} from '#/lib/routes/links' 14 15 import {sanitizeDisplayName} from '#/lib/strings/display-names' ··· 245 246 } 246 247 }, [postSource]) 247 248 248 - const onPressReply = useCallback(() => { 249 + const onPressReply = useNonReactiveCallback(() => { 249 250 openComposer({ 250 251 replyTo: { 251 252 uri: post.uri, ··· 268 269 reqId: postSource.post.reqId, 269 270 }) 270 271 } 271 - }, [ 272 - openComposer, 273 - post, 274 - record, 275 - onPostSuccess, 276 - moderation, 277 - postSource, 278 - feedFeedback, 279 - ]) 272 + }) 280 273 281 274 const onOpenAuthor = () => { 282 275 ax.metric('post:clickthroughAuthor', {
+41 -16
src/screens/PostThread/index.tsx
··· 1 - import {useCallback, useEffect, useMemo, useRef, useState} from 'react' 1 + import { 2 + startTransition, 3 + useCallback, 4 + useEffect, 5 + useMemo, 6 + useRef, 7 + useState, 8 + } from 'react' 2 9 import {useWindowDimensions, View} from 'react-native' 3 10 import Animated, {useAnimatedStyle} from 'react-native-reanimated' 4 11 import {Trans} from '@lingui/react/macro' 5 12 6 13 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 14 + import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 7 15 import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 8 16 import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking' 9 17 import {useFeedFeedback} from '#/state/feed-feedback' ··· 98 106 const trackThreadItemView = usePostViewTracking('PostThreadItem') 99 107 100 108 const {openComposer} = useOpenComposer() 101 - const optimisticOnPostReply = useCallback( 109 + const optimisticOnPostReply = useNonReactiveCallback( 102 110 (payload: OnPostSuccessData) => { 103 111 if (payload) { 104 112 const {replyToUri, posts} = payload ··· 107 115 } 108 116 } 109 117 }, 110 - [thread], 111 118 ) 112 - const onReplyToAnchor = useCallback(() => { 119 + const onReplyToAnchor = useNonReactiveCallback(() => { 113 120 if (anchor?.type !== 'threadPost') { 114 121 return 115 122 } ··· 136 143 reqId: anchorPostSource.post.reqId, 137 144 }) 138 145 } 139 - }, [ 140 - anchor, 141 - openComposer, 142 - optimisticOnPostReply, 143 - anchorPostSource, 144 - feedFeedback, 145 - ]) 146 + }) 146 147 147 148 const isRoot = !!anchor && anchor.value.post.record.reply === undefined 148 149 const canReply = !anchor?.value.post?.viewer?.replyDisabled ··· 389 390 return results 390 391 }, [thread, deferParents, maxParentCount, maxChildrenCount]) 391 392 393 + /** 394 + * Defer rendering reply skeletons so that the anchor post (from cache) 395 + * can paint without being blocked by skeleton layout work. On mount, 396 + * skeletons are filtered out. After the first render, they're added 397 + * back via a low-priority transition. 398 + */ 399 + const [showReplySkeletons, setShowReplySkeletons] = useState(false) 400 + useEffect(() => { 401 + if (thread.state.isPlaceholderData && !showReplySkeletons) { 402 + startTransition(() => { 403 + setShowReplySkeletons(true) 404 + }) 405 + } 406 + }, [thread.state.isPlaceholderData, showReplySkeletons]) 407 + 408 + const deferredSlices = useMemo(() => { 409 + if (showReplySkeletons) return slices 410 + return slices.filter( 411 + item => !(item.type === 'skeleton' && item.item === 'reply'), 412 + ) 413 + }, [slices, showReplySkeletons]) 414 + 392 415 const isTombstoneView = useMemo(() => { 393 - if (slices.length > 1) return false 394 - return slices.every( 416 + if (deferredSlices.length > 1) return false 417 + return deferredSlices.every( 395 418 s => s.type === 'threadPostBlocked' || s.type === 'threadPostNotFound', 396 419 ) 397 - }, [slices]) 420 + }, [deferredSlices]) 398 421 399 422 const renderItem = useCallback( 400 423 ({item, index}: {item: ThreadItem; index: number}) => { ··· 547 570 ) : ( 548 571 <List 549 572 ref={listRef} 550 - data={slices} 573 + data={deferredSlices} 551 574 renderItem={renderItem} 552 575 keyExtractor={keyExtractor} 553 576 onContentSizeChange={platform({ ··· 594 617 initialNumToRender={initialNumToRender} 595 618 /** 596 619 * Default: 21 620 + * 621 + * Smaller for placeholder data so we don't waste time rendering skeletons 597 622 */ 598 - windowSize={7} 623 + windowSize={thread.state.isPlaceholderData ? 1 : 7} 599 624 /** 600 625 * Default: 10 601 626 */