From fcb420a77317a7c7dfeaa2fe7cbf6fed939e2015 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 30 Oct 2019 11:14:41 -0300 Subject: [PATCH] [FIX] Remove some unnecessary re-renders on Messagebox (#1341) --- app/containers/MessageBox/CommandPreview.js | 47 ---- .../MessageBox/CommandsPreview/Item.js | 44 ++++ .../MessageBox/CommandsPreview/index.js | 40 ++++ app/containers/MessageBox/Context.js | 4 + .../MessageBox/Mentions/FixedMentionItem.js | 23 ++ .../MessageBox/Mentions/MentionEmoji.js | 34 +++ .../MessageBox/Mentions/MentionItem.js | 87 +++++++ app/containers/MessageBox/Mentions/index.js | 39 ++++ app/containers/MessageBox/ReplyPreview.js | 65 +++--- app/containers/MessageBox/constants.js | 4 + app/containers/MessageBox/index.js | 213 +++--------------- 11 files changed, 341 insertions(+), 259 deletions(-) delete mode 100644 app/containers/MessageBox/CommandPreview.js create mode 100644 app/containers/MessageBox/CommandsPreview/Item.js create mode 100644 app/containers/MessageBox/CommandsPreview/index.js create mode 100644 app/containers/MessageBox/Context.js create mode 100644 app/containers/MessageBox/Mentions/FixedMentionItem.js create mode 100644 app/containers/MessageBox/Mentions/MentionEmoji.js create mode 100644 app/containers/MessageBox/Mentions/MentionItem.js create mode 100644 app/containers/MessageBox/Mentions/index.js create mode 100644 app/containers/MessageBox/constants.js diff --git a/app/containers/MessageBox/CommandPreview.js b/app/containers/MessageBox/CommandPreview.js deleted file mode 100644 index 51cc64e4..00000000 --- a/app/containers/MessageBox/CommandPreview.js +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { TouchableOpacity, ActivityIndicator } from 'react-native'; -import FastImage from 'react-native-fast-image'; - -import styles from './styles'; -import { CustomIcon } from '../../lib/Icons'; -import { COLOR_PRIMARY } from '../../constants/colors'; - -export default class CommandPreview extends React.PureComponent { - static propTypes = { - onPress: PropTypes.func, - item: PropTypes.object - }; - - constructor(props) { - super(props); - this.state = { loading: true }; - } - - render() { - const { onPress, item } = this.props; - const { loading } = this.state; - return ( - onPress(item)} - testID={`command-preview-item${ item.id }`} - > - {item.type === 'image' - ? ( - this.setState({ loading: true })} - onLoad={() => this.setState({ loading: false })} - > - { loading ? : null } - - ) - : - } - - ); - } -} diff --git a/app/containers/MessageBox/CommandsPreview/Item.js b/app/containers/MessageBox/CommandsPreview/Item.js new file mode 100644 index 00000000..580d57bd --- /dev/null +++ b/app/containers/MessageBox/CommandsPreview/Item.js @@ -0,0 +1,44 @@ +import React, { useContext, useState } from 'react'; +import PropTypes from 'prop-types'; +import { TouchableOpacity, ActivityIndicator } from 'react-native'; +import FastImage from 'react-native-fast-image'; + +import styles from '../styles'; +import { CustomIcon } from '../../../lib/Icons'; +import { COLOR_PRIMARY } from '../../../constants/colors'; +import MessageboxContext from '../Context'; + +const Item = ({ item }) => { + const context = useContext(MessageboxContext); + const { onPressCommandPreview } = context; + const [loading, setLoading] = useState(true); + + return ( + onPressCommandPreview(item)} + testID={`command-preview-item${ item.id }`} + > + {item.type === 'image' + ? ( + setLoading(true)} + onLoad={() => setLoading(false)} + > + { loading ? : null } + + ) + : + } + + ); +}; + +Item.propTypes = { + item: PropTypes.object +}; + +export default Item; diff --git a/app/containers/MessageBox/CommandsPreview/index.js b/app/containers/MessageBox/CommandsPreview/index.js new file mode 100644 index 00000000..cecaf7c0 --- /dev/null +++ b/app/containers/MessageBox/CommandsPreview/index.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { FlatList } from 'react-native'; +import PropTypes from 'prop-types'; +import equal from 'deep-equal'; + +import Item from './Item'; +import styles from '../styles'; + +const CommandsPreview = React.memo(({ commandPreview, showCommandPreview }) => { + if (!showCommandPreview) { + return null; + } + return ( + } + keyExtractor={item => item.id} + keyboardShouldPersistTaps='always' + horizontal + showsHorizontalScrollIndicator={false} + /> + ); +}, (prevProps, nextProps) => { + if (prevProps.showCommandPreview !== nextProps.showCommandPreview) { + return false; + } + if (!equal(prevProps.commandPreview, nextProps.commandPreview)) { + return false; + } + return true; +}); + +CommandsPreview.propTypes = { + commandPreview: PropTypes.array, + showCommandPreview: PropTypes.bool +}; + +export default CommandsPreview; diff --git a/app/containers/MessageBox/Context.js b/app/containers/MessageBox/Context.js new file mode 100644 index 00000000..3ed07ad4 --- /dev/null +++ b/app/containers/MessageBox/Context.js @@ -0,0 +1,4 @@ +import React from 'react'; + +const MessageboxContext = React.createContext(); +export default MessageboxContext; diff --git a/app/containers/MessageBox/Mentions/FixedMentionItem.js b/app/containers/MessageBox/Mentions/FixedMentionItem.js new file mode 100644 index 00000000..1b6aabbc --- /dev/null +++ b/app/containers/MessageBox/Mentions/FixedMentionItem.js @@ -0,0 +1,23 @@ +import React from 'react'; +import { TouchableOpacity, Text } from 'react-native'; +import PropTypes from 'prop-types'; + +import styles from '../styles'; +import I18n from '../../../i18n'; + +const FixedMentionItem = ({ item, onPress }) => ( + onPress(item)} + > + {item.username} + {item.username === 'here' ? I18n.t('Notify_active_in_this_room') : I18n.t('Notify_all_in_this_room')} + +); + +FixedMentionItem.propTypes = { + item: PropTypes.object, + onPress: PropTypes.func +}; + +export default FixedMentionItem; diff --git a/app/containers/MessageBox/Mentions/MentionEmoji.js b/app/containers/MessageBox/Mentions/MentionEmoji.js new file mode 100644 index 00000000..a8fe1aa3 --- /dev/null +++ b/app/containers/MessageBox/Mentions/MentionEmoji.js @@ -0,0 +1,34 @@ +import React, { useContext } from 'react'; +import { Text } from 'react-native'; +import PropTypes from 'prop-types'; +import { shortnameToUnicode } from 'emoji-toolkit'; + +import styles from '../styles'; +import MessageboxContext from '../Context'; +import CustomEmoji from '../../EmojiPicker/CustomEmoji'; + +const MentionEmoji = ({ item }) => { + const context = useContext(MessageboxContext); + const { baseUrl } = context; + + if (item.name) { + return ( + + ); + } + return ( + + {shortnameToUnicode(`:${ item }:`)} + + ); +}; + +MentionEmoji.propTypes = { + item: PropTypes.object +}; + +export default MentionEmoji; diff --git a/app/containers/MessageBox/Mentions/MentionItem.js b/app/containers/MessageBox/Mentions/MentionItem.js new file mode 100644 index 00000000..d685d0b2 --- /dev/null +++ b/app/containers/MessageBox/Mentions/MentionItem.js @@ -0,0 +1,87 @@ +import React, { useContext } from 'react'; +import { TouchableOpacity, Text } from 'react-native'; +import PropTypes from 'prop-types'; + +import styles from '../styles'; +import Avatar from '../../Avatar'; +import MessageboxContext from '../Context'; +import FixedMentionItem from './FixedMentionItem'; +import MentionEmoji from './MentionEmoji'; +import { + MENTIONS_TRACKING_TYPE_EMOJIS, + MENTIONS_TRACKING_TYPE_COMMANDS +} from '../constants'; + +const MentionItem = ({ + item, trackingType +}) => { + const context = useContext(MessageboxContext); + const { baseUrl, user, onPressMention } = context; + + const defineTestID = (type) => { + switch (type) { + case MENTIONS_TRACKING_TYPE_EMOJIS: + return `mention-item-${ item.name || item }`; + case MENTIONS_TRACKING_TYPE_COMMANDS: + return `mention-item-${ item.command || item }`; + default: + return `mention-item-${ item.username || item.name || item }`; + } + }; + + const testID = defineTestID(trackingType); + + if (item.username === 'all' || item.username === 'here') { + return ; + } + + let content = ( + <> + + { item.username || item.name || item } + + ); + + if (trackingType === MENTIONS_TRACKING_TYPE_EMOJIS) { + content = ( + <> + + :{ item.name || item }: + + ); + } + + if (trackingType === MENTIONS_TRACKING_TYPE_COMMANDS) { + content = ( + <> + / + { item.command} + + ); + } + + return ( + onPressMention(item)} + testID={testID} + > + {content} + + ); +}; + +MentionItem.propTypes = { + item: PropTypes.object, + trackingType: PropTypes.string +}; + +export default MentionItem; diff --git a/app/containers/MessageBox/Mentions/index.js b/app/containers/MessageBox/Mentions/index.js new file mode 100644 index 00000000..7d369208 --- /dev/null +++ b/app/containers/MessageBox/Mentions/index.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { FlatList } from 'react-native'; +import PropTypes from 'prop-types'; +import equal from 'deep-equal'; + +import styles from '../styles'; +import MentionItem from './MentionItem'; + +const Mentions = React.memo(({ mentions, trackingType }) => { + if (!trackingType) { + return null; + } + return ( + } + keyExtractor={item => item.id || item.username || item.command || item} + keyboardShouldPersistTaps='always' + /> + ); +}, (prevProps, nextProps) => { + if (prevProps.trackingType !== nextProps.trackingType) { + return false; + } + if (!equal(prevProps.mentions, nextProps.mentions)) { + return false; + } + return true; +}); + +Mentions.propTypes = { + mentions: PropTypes.array, + trackingType: PropTypes.string +}; + +export default Mentions; diff --git a/app/containers/MessageBox/ReplyPreview.js b/app/containers/MessageBox/ReplyPreview.js index 06fc074b..85894911 100644 --- a/app/containers/MessageBox/ReplyPreview.js +++ b/app/containers/MessageBox/ReplyPreview.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; import PropTypes from 'prop-types'; import moment from 'moment'; @@ -47,45 +47,38 @@ const styles = StyleSheet.create({ } }); -class ReplyPreview extends Component { - static propTypes = { - useMarkdown: PropTypes.bool, - message: PropTypes.object.isRequired, - Message_TimeFormat: PropTypes.string.isRequired, - close: PropTypes.func.isRequired, - baseUrl: PropTypes.string.isRequired, - username: PropTypes.string.isRequired, - getCustomEmoji: PropTypes.func +const ReplyPreview = React.memo(({ + message, Message_TimeFormat, baseUrl, username, useMarkdown, replying, getCustomEmoji, close +}) => { + if (!replying) { + return null; } - shouldComponentUpdate() { - return false; - } - - close = () => { - const { close } = this.props; - close(); - } - - render() { - const { - message, Message_TimeFormat, baseUrl, username, useMarkdown, getCustomEmoji - } = this.props; - const time = moment(message.ts).format(Message_TimeFormat); - return ( - - - - {message.u.username} - {time} - - + const time = moment(message.ts).format(Message_TimeFormat); + return ( + + + + {message.u.username} + {time} - + - ); - } -} + + + ); +}, (prevProps, nextProps) => prevProps.replying === nextProps.replying); + +ReplyPreview.propTypes = { + replying: PropTypes.bool, + useMarkdown: PropTypes.bool, + message: PropTypes.object.isRequired, + Message_TimeFormat: PropTypes.string.isRequired, + close: PropTypes.func.isRequired, + baseUrl: PropTypes.string.isRequired, + username: PropTypes.string.isRequired, + getCustomEmoji: PropTypes.func +}; const mapStateToProps = state => ({ useMarkdown: state.markdown.useMarkdown, diff --git a/app/containers/MessageBox/constants.js b/app/containers/MessageBox/constants.js new file mode 100644 index 00000000..d5ef03e2 --- /dev/null +++ b/app/containers/MessageBox/constants.js @@ -0,0 +1,4 @@ +export const MENTIONS_TRACKING_TYPE_USERS = '@'; +export const MENTIONS_TRACKING_TYPE_EMOJIS = ':'; +export const MENTIONS_TRACKING_TYPE_COMMANDS = '/'; +export const MENTIONS_COUNT_TO_DISPLAY = 4; diff --git a/app/containers/MessageBox/index.js b/app/containers/MessageBox/index.js index 273a6bb9..cb7feb55 100644 --- a/app/containers/MessageBox/index.js +++ b/app/containers/MessageBox/index.js @@ -1,10 +1,9 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { - View, TextInput, FlatList, Text, TouchableOpacity, Alert, ScrollView + View, TextInput, Alert } from 'react-native'; import { connect } from 'react-redux'; -import { shortnameToUnicode } from 'emoji-toolkit'; import { KeyboardAccessoryView } from 'react-native-keyboard-input'; import ImagePicker from 'react-native-image-crop-picker'; import equal from 'deep-equal'; @@ -16,8 +15,6 @@ import { userTyping as userTypingAction } from '../../actions/room'; import RocketChat from '../../lib/rocketchat'; import styles from './styles'; import database from '../../lib/database'; -import Avatar from '../Avatar'; -import CustomEmoji from '../EmojiPicker/CustomEmoji'; import { emojis } from '../../emojis'; import Recording from './Recording'; import UploadModal from './UploadModal'; @@ -29,13 +26,16 @@ import { COLOR_TEXT_DESCRIPTION } from '../../constants/colors'; import LeftButtons from './LeftButtons'; import RightButtons from './RightButtons'; import { isAndroid } from '../../utils/deviceInfo'; -import CommandPreview from './CommandPreview'; import { canUploadFile } from '../../utils/media'; - -const MENTIONS_TRACKING_TYPE_USERS = '@'; -const MENTIONS_TRACKING_TYPE_EMOJIS = ':'; -const MENTIONS_TRACKING_TYPE_COMMANDS = '/'; -const MENTIONS_COUNT_TO_DISPLAY = 4; +import Mentions from './Mentions'; +import MessageboxContext from './Context'; +import { + MENTIONS_TRACKING_TYPE_EMOJIS, + MENTIONS_TRACKING_TYPE_COMMANDS, + MENTIONS_COUNT_TO_DISPLAY, + MENTIONS_TRACKING_TYPE_USERS +} from './constants'; +import CommandsPreview from './CommandsPreview'; const imagePickerConfig = { cropping: true, @@ -545,7 +545,6 @@ class MessageBox extends Component { } } - showUploadModal = (file) => { this.setState({ file: { ...file, isVisible: true } }); } @@ -726,175 +725,29 @@ class MessageBox extends Component { }); } - renderFixedMentionItem = item => ( - this.onPressMention(item)} - > - {item.username} - {item.username === 'here' ? I18n.t('Notify_active_in_this_room') : I18n.t('Notify_all_in_this_room')} - - ) - - renderMentionEmoji = (item) => { - const { baseUrl } = this.props; - - if (item.name) { - return ( - - ); - } - return ( - - {shortnameToUnicode(`:${ item }:`)} - - ); - } - - renderMentionItem = ({ item }) => { - const { trackingType } = this.state; - const { baseUrl, user } = this.props; - - if (item.username === 'all' || item.username === 'here') { - return this.renderFixedMentionItem(item); - } - const defineTestID = (type) => { - switch (type) { - case MENTIONS_TRACKING_TYPE_EMOJIS: - return `mention-item-${ item.name || item }`; - case MENTIONS_TRACKING_TYPE_COMMANDS: - return `mention-item-${ item.command || item }`; - default: - return `mention-item-${ item.username || item.name || item }`; - } - }; - - const testID = defineTestID(trackingType); - - return ( - this.onPressMention(item)} - testID={testID} - > - - {(() => { - switch (trackingType) { - case MENTIONS_TRACKING_TYPE_EMOJIS: - return ( - <> - {this.renderMentionEmoji(item)} - :{ item.name || item }: - - ); - case MENTIONS_TRACKING_TYPE_COMMANDS: - return ( - <> - / - { item.command} - - ); - default: - return ( - <> - - { item.username || item.name || item } - - ); - } - })() - } - - ); - } - - renderMentions = () => { - const { mentions, trackingType } = this.state; - if (!trackingType) { - return null; - } - return ( - - item.id || item.username || item.command || item} - keyboardShouldPersistTaps='always' - /> - - ); - }; - - renderCommandPreviewItem = ({ item }) => ( - - ); - - renderCommandPreview = () => { - const { commandPreview, showCommandPreview } = this.state; - if (!showCommandPreview) { - return null; - } - return ( - - item.id} - keyboardShouldPersistTaps='always' - horizontal - showsHorizontalScrollIndicator={false} - /> - - ); - } - - renderReplyPreview = () => { - const { - message, replying, replyCancel, user, getCustomEmoji - } = this.props; - if (!replying) { - return null; - } - return ; - }; - renderContent = () => { - const { recording, showEmojiKeyboard, showSend } = this.state; - const { editing } = this.props; + const { + recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview + } = this.state; + const { + editing, message, replying, replyCancel, user, getCustomEmoji + } = this.props; if (recording) { - return (); + return ; } return ( <> - {this.renderCommandPreview()} - {this.renderMentions()} - - {this.renderReplyPreview()} + + + + + this.setState({ file: {} })} submit={this.sendMediaMessage} /> - + ); } }