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 * oopszio/stable
parent
a5e25a7a16
commit
5c701f8e0b
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in New Issue