Re-rendering improvements for like/unlike (#2180)

* Add a few memos

* Memo PostDropdownBtn better

* More memo

* More granularity

* Extract PostContent

* Fix a usage I missed

* oops
zio/stable
dan 2023-12-12 21:50:43 +00:00 committed by GitHub
parent a5e25a7a16
commit 5c701f8e0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 160 additions and 87 deletions

View File

@ -328,7 +328,9 @@ let PostThreadItemLoaded = ({
</View>
<PostDropdownBtn
testID="postDropdownBtn"
post={post}
postAuthor={post.author}
postCid={post.cid}
postUri={post.uri}
record={record}
style={{
paddingVertical: 6,

View File

@ -102,10 +102,6 @@ let FeedItemInner = ({
}): React.ReactNode => {
const {openComposer} = useComposerControls()
const pal = usePalette('default')
const [limitLines, setLimitLines] = useState(
() => countLines(richText.text) >= MAX_POST_LINES,
)
const href = useMemo(() => {
const urip = new AtUri(post.uri)
return makeProfileLink(post.author, 'post', urip.rkey)
@ -134,10 +130,6 @@ let FeedItemInner = ({
})
}, [post, record, openComposer])
const onPressShowMore = React.useCallback(() => {
setLimitLines(false)
}, [setLimitLines])
const outerStyles = [
styles.outer,
pal.view,
@ -286,48 +278,12 @@ let FeedItemInner = ({
</Text>
</View>
)}
<ContentHider
testID="contentHider-post"
moderation={moderation.content}
ignoreMute
childContainerStyle={styles.contentHiderChild}>
<PostAlerts moderation={moderation.content} style={styles.alert} />
{richText.text ? (
<View style={styles.postTextContainer}>
<RichText
testID="postText"
type="post-text"
richText={richText}
lineHeight={1.3}
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
style={s.flex1}
/>
</View>
) : undefined}
{limitLines ? (
<TextLink
text="Show More"
style={pal.link}
onPress={onPressShowMore}
href="#"
/>
) : undefined}
{post.embed ? (
<ContentHider
testID="contentHider-embed"
moderation={moderation.embed}
moderationDecisions={moderation.decisions}
ignoreMute={isEmbedByEmbedder(post.embed, post.author.did)}
ignoreQuoteDecisions
style={styles.embed}>
<PostEmbeds
embed={post.embed}
moderation={moderation.embed}
moderationDecisions={moderation.decisions}
/>
</ContentHider>
) : null}
</ContentHider>
<PostContent
moderation={moderation}
richText={richText}
postEmbed={post.embed}
postAuthor={post.author}
/>
<PostCtrls post={post} record={record} onPressReply={onPressReply} />
</View>
</View>
@ -336,6 +292,73 @@ let FeedItemInner = ({
}
FeedItemInner = memo(FeedItemInner)
let PostContent = ({
moderation,
richText,
postEmbed,
postAuthor,
}: {
moderation: PostModeration
richText: RichTextAPI
postEmbed: AppBskyFeedDefs.PostView['embed']
postAuthor: AppBskyFeedDefs.PostView['author']
}): React.ReactNode => {
const pal = usePalette('default')
const [limitLines, setLimitLines] = useState(
() => countLines(richText.text) >= MAX_POST_LINES,
)
const onPressShowMore = React.useCallback(() => {
setLimitLines(false)
}, [setLimitLines])
return (
<ContentHider
testID="contentHider-post"
moderation={moderation.content}
ignoreMute
childContainerStyle={styles.contentHiderChild}>
<PostAlerts moderation={moderation.content} style={styles.alert} />
{richText.text ? (
<View style={styles.postTextContainer}>
<RichText
testID="postText"
type="post-text"
richText={richText}
lineHeight={1.3}
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
style={s.flex1}
/>
</View>
) : undefined}
{limitLines ? (
<TextLink
text="Show More"
style={pal.link}
onPress={onPressShowMore}
href="#"
/>
) : undefined}
{postEmbed ? (
<ContentHider
testID="contentHider-embed"
moderation={moderation.embed}
moderationDecisions={moderation.decisions}
ignoreMute={isEmbedByEmbedder(postEmbed, postAuthor.did)}
ignoreQuoteDecisions
style={styles.embed}>
<PostEmbeds
embed={postEmbed}
moderation={moderation.embed}
moderationDecisions={moderation.decisions}
/>
</ContentHider>
) : null}
</ContentHider>
)
}
PostContent = memo(PostContent)
const styles = StyleSheet.create({
outer: {
borderTopWidth: 1,

View File

@ -1,4 +1,4 @@
import React from 'react'
import React, {memo} from 'react'
import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native'
import {Text} from './text/Text'
import {TextLinkOnWebOnly} from './Link'
@ -29,7 +29,7 @@ interface PostMetaOpts {
style?: StyleProp<ViewStyle>
}
export function PostMeta(opts: PostMetaOpts) {
let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
const pal = usePalette('default')
const displayName = opts.author.displayName || opts.author.handle
const handle = opts.author.handle
@ -92,6 +92,8 @@ export function PostMeta(opts: PostMetaOpts) {
</View>
)
}
PostMeta = memo(PostMeta)
export {PostMeta}
const styles = StyleSheet.create({
container: {

View File

@ -1,4 +1,4 @@
import React, {useMemo} from 'react'
import React, {memo, useMemo} from 'react'
import {Image, StyleSheet, View} from 'react-native'
import Svg, {Circle, Rect, Path} from 'react-native-svg'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
@ -43,13 +43,13 @@ interface PreviewableUserAvatarProps extends BaseUserAvatarProps {
const BLUR_AMOUNT = isWeb ? 5 : 100
export function DefaultAvatar({
let DefaultAvatar = ({
type,
size,
}: {
type: UserAvatarType
size: number
}) {
}): React.ReactNode => {
if (type === 'algo') {
// Font Awesome Pro 6.4.0 by @fontawesome -https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc.
return (
@ -112,14 +112,16 @@ export function DefaultAvatar({
</Svg>
)
}
DefaultAvatar = memo(DefaultAvatar)
export {DefaultAvatar}
export function UserAvatar({
let UserAvatar = ({
type = 'user',
size,
avatar,
moderation,
usePlainRNImage = false,
}: UserAvatarProps) {
}: UserAvatarProps): React.ReactNode => {
const pal = usePalette('default')
const aviStyle = useMemo(() => {
@ -182,13 +184,15 @@ export function UserAvatar({
</View>
)
}
UserAvatar = memo(UserAvatar)
export {UserAvatar}
export function EditableUserAvatar({
let EditableUserAvatar = ({
type = 'user',
size,
avatar,
onSelectNewAvatar,
}: EditableUserAvatarProps) {
}: EditableUserAvatarProps): React.ReactNode => {
const pal = usePalette('default')
const {_} = useLingui()
const {requestCameraAccessIfNeeded} = useCameraPermission()
@ -323,14 +327,20 @@ export function EditableUserAvatar({
</NativeDropdown>
)
}
EditableUserAvatar = memo(EditableUserAvatar)
export {EditableUserAvatar}
export function PreviewableUserAvatar(props: PreviewableUserAvatarProps) {
let PreviewableUserAvatar = (
props: PreviewableUserAvatarProps,
): React.ReactNode => {
return (
<UserPreviewLink did={props.did} handle={props.handle}>
<UserAvatar {...props} />
</UserPreviewLink>
)
}
PreviewableUserAvatar = memo(PreviewableUserAvatar)
export {PreviewableUserAvatar}
const styles = StyleSheet.create({
editButtonContainer: {

View File

@ -1,8 +1,8 @@
import React from 'react'
import React, {memo} from 'react'
import {Linking, StyleProp, View, ViewStyle} from 'react-native'
import Clipboard from '@react-native-clipboard/clipboard'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {AppBskyFeedDefs, AppBskyFeedPost, AtUri} from '@atproto/api'
import {AppBskyActorDefs, AppBskyFeedPost, AtUri} from '@atproto/api'
import {toShareUrl} from 'lib/strings/url-helpers'
import {useTheme} from 'lib/ThemeContext'
import {shareUrl} from 'lib/sharing'
@ -19,23 +19,26 @@ import {usePostDeleteMutation} from '#/state/queries/post'
import {useMutedThreads, useToggleThreadMute} from '#/state/muted-threads'
import {useLanguagePrefs} from '#/state/preferences'
import {logger} from '#/logger'
import {Shadow} from '#/state/cache/types'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useSession} from '#/state/session'
import {isWeb} from '#/platform/detection'
export function PostDropdownBtn({
let PostDropdownBtn = ({
testID,
post,
postAuthor,
postCid,
postUri,
record,
style,
}: {
testID: string
post: Shadow<AppBskyFeedDefs.PostView>
postAuthor: AppBskyActorDefs.ProfileViewBasic
postCid: string
postUri: string
record: AppBskyFeedPost.Record
style?: StyleProp<ViewStyle>
}) {
}): React.ReactNode => {
const {hasSession, currentAccount} = useSession()
const theme = useTheme()
const {_} = useLingui()
@ -46,13 +49,13 @@ export function PostDropdownBtn({
const toggleThreadMute = useToggleThreadMute()
const postDeleteMutation = usePostDeleteMutation()
const rootUri = record.reply?.root?.uri || post.uri
const rootUri = record.reply?.root?.uri || postUri
const isThreadMuted = mutedThreads.includes(rootUri)
const isAuthor = post.author.did === currentAccount?.did
const isAuthor = postAuthor.did === currentAccount?.did
const href = React.useMemo(() => {
const urip = new AtUri(post.uri)
return makeProfileLink(post.author, 'post', urip.rkey)
}, [post.uri, post.author])
const urip = new AtUri(postUri)
return makeProfileLink(postAuthor, 'post', urip.rkey)
}, [postUri, postAuthor])
const translatorUrl = getTranslatorLink(
record.text,
@ -60,7 +63,7 @@ export function PostDropdownBtn({
)
const onDeletePost = React.useCallback(() => {
postDeleteMutation.mutateAsync({uri: post.uri}).then(
postDeleteMutation.mutateAsync({uri: postUri}).then(
() => {
Toast.show('Post deleted')
},
@ -69,7 +72,7 @@ export function PostDropdownBtn({
Toast.show('Failed to delete post, please try again')
},
)
}, [post, postDeleteMutation])
}, [postUri, postDeleteMutation])
const onToggleThreadMute = React.useCallback(() => {
try {
@ -163,8 +166,8 @@ export function PostDropdownBtn({
onPress() {
openModal({
name: 'report',
uri: post.uri,
cid: post.cid,
uri: postUri,
cid: postCid,
})
},
testID: 'postDropdownReportBtn',
@ -211,3 +214,6 @@ export function PostDropdownBtn({
</EventStopper>
)
}
PostDropdownBtn = memo(PostDropdownBtn)
export {PostDropdownBtn}

View File

@ -1,4 +1,4 @@
import React, {useCallback} from 'react'
import React, {memo, useCallback} from 'react'
import {
StyleProp,
StyleSheet,
@ -27,7 +27,7 @@ import {useComposerControls} from '#/state/shell/composer'
import {Shadow} from '#/state/cache/types'
import {useRequireAuth} from '#/state/session'
export function PostCtrls({
let PostCtrls = ({
big,
post,
record,
@ -39,7 +39,7 @@ export function PostCtrls({
record: AppBskyFeedPost.Record
style?: StyleProp<ViewStyle>
onPressReply: () => void
}) {
}): React.ReactNode => {
const theme = useTheme()
const {openComposer} = useComposerControls()
const {closeModal} = useModalControls()
@ -71,7 +71,14 @@ export function PostCtrls({
likeCount: post.likeCount || 0,
})
}
}, [post, postLikeMutation, postUnlikeMutation])
}, [
post.viewer?.like,
post.uri,
post.cid,
post.likeCount,
postLikeMutation,
postUnlikeMutation,
])
const onRepost = useCallback(() => {
closeModal()
@ -89,7 +96,15 @@ export function PostCtrls({
repostCount: post.repostCount || 0,
})
}
}, [post, closeModal, postRepostMutation, postUnrepostMutation])
}, [
post.uri,
post.cid,
post.viewer?.repost,
post.repostCount,
closeModal,
postRepostMutation,
postUnrepostMutation,
])
const onQuote = useCallback(() => {
closeModal()
@ -103,7 +118,16 @@ export function PostCtrls({
},
})
Haptics.default()
}, [post, record, openComposer, closeModal])
}, [
post.uri,
post.cid,
post.author,
post.indexedAt,
record.text,
openComposer,
closeModal,
])
return (
<View style={[styles.ctrls, style]}>
<TouchableOpacity
@ -179,7 +203,9 @@ export function PostCtrls({
{big ? undefined : (
<PostDropdownBtn
testID="postDropdownBtn"
post={post}
postAuthor={post.author}
postCid={post.cid}
postUri={post.uri}
record={record}
style={styles.ctrlPad}
/>
@ -189,6 +215,8 @@ export function PostCtrls({
</View>
)
}
PostCtrls = memo(PostCtrls)
export {PostCtrls}
const styles = StyleSheet.create({
ctrls: {

View File

@ -1,4 +1,4 @@
import React, {useCallback} from 'react'
import React, {memo, useCallback} from 'react'
import {StyleProp, StyleSheet, TouchableOpacity, ViewStyle} from 'react-native'
import {RepostIcon} from 'lib/icons'
import {s, colors} from 'lib/styles'
@ -17,13 +17,13 @@ interface Props {
onQuote: () => void
}
export const RepostButton = ({
let RepostButton = ({
isReposted,
repostCount,
big,
onRepost,
onQuote,
}: Props) => {
}: Props): React.ReactNode => {
const theme = useTheme()
const {openModal} = useModalControls()
const requireAuth = useRequireAuth()
@ -80,6 +80,8 @@ export const RepostButton = ({
</TouchableOpacity>
)
}
RepostButton = memo(RepostButton)
export {RepostButton}
const styles = StyleSheet.create({
control: {