From 15464a621c6b2f126568643d22578c8790638589 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Mon, 27 Mar 2023 18:24:09 +0000 Subject: [PATCH 1/6] ReportActionsList --- src/pages/home/report/ReportActionsList.js | 175 ++++++++++----------- 1 file changed, 82 insertions(+), 93 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index 38d19864ea5a..9de9063266f1 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React from 'react'; +import React, {useCallback, useEffect, useState} from 'react'; import {Animated} from 'react-native'; import _ from 'underscore'; import InvertedFlatList from '../../../components/InvertedFlatList'; @@ -63,42 +63,22 @@ const defaultProps = { isLoadingMoreReportActions: false, }; -class ReportActionsList extends React.Component { - constructor(props) { - super(props); - this.renderItem = this.renderItem.bind(this); - this.keyExtractor = this.keyExtractor.bind(this); - - this.state = { - fadeInAnimation: new Animated.Value(0), - skeletonViewHeight: 0, - }; - } - - componentDidMount() { - this.fadeIn(); - } - - fadeIn() { - Animated.timing(this.state.fadeInAnimation, { - toValue: 1, - duration: 100, - useNativeDriver: true, - }).start(); - } +const ReportActionsList = (props) => { + const [fadeInAnimation] = useState(() => new Animated.Value(0)); + const [skeletonViewHeight, setSkeletonViewHeight] = useState(0); /** * Calculates the ideal number of report actions to render in the first render, based on the screen height and on * the height of the smallest report action possible. * @return {Number} */ - calculateInitialNumToRender() { + const calculateInitialNumToRender = useCallback(() => { const minimumReportActionHeight = styles.chatItem.paddingTop + styles.chatItem.paddingBottom + variables.fontSizeNormalHeight; - const availableHeight = this.props.windowHeight + const availableHeight = props.windowHeight - (CONST.CHAT_FOOTER_MIN_HEIGHT + variables.contentHeaderHeight); return Math.ceil(availableHeight / minimumReportActionHeight); - } + }, [props.windowHeight]); /** * Create a unique key for each action in the FlatList. @@ -108,7 +88,7 @@ class ReportActionsList extends React.Component { * @param {Object} item.action * @return {String} */ - keyExtractor(item) { + function keyExtractor(item) { return item.reportActionID; } @@ -123,87 +103,96 @@ class ReportActionsList extends React.Component { * * @returns {React.Component} */ - renderItem({ + function renderItem({ item: reportAction, index, }) { // When the new indicator should not be displayed we explicitly set it to null - const shouldDisplayNewMarker = reportAction.reportActionID === this.props.newMarkerReportActionID; + const shouldDisplayNewMarker = reportAction.reportActionID === props.newMarkerReportActionID; return ( ); } - render() { - // Native mobile does not render updates flatlist the changes even though component did update called. - // To notify there something changes we can use extraData prop to flatlist - const extraData = (!this.props.isDrawerOpen && this.props.isSmallScreenWidth) ? this.props.newMarkerReportActionID : undefined; - const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(this.props.personalDetails, this.props.report); - return ( - - { - if (this.props.report.isLoadingMoreReportActions) { - return ( - - ); - } - - // Make sure the oldest report action loaded is not the first. This is so we do not show the - // skeleton view above the created action in a newly generated optimistic chat or one with not - // that many comments. - const lastReportAction = _.last(this.props.sortedReportActions) || {}; - if (this.props.report.isLoadingReportActions && lastReportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED) { - return ( - - ); - } - - return null; - }} - keyboardShouldPersistTaps="handled" - onLayout={(event) => { - this.setState({ - skeletonViewHeight: event.nativeEvent.layout.height, - }); - this.props.onLayout(event); - }} - onScroll={this.props.onScroll} - extraData={extraData} - /> - - ); - } -} + const fadeIn = useCallback(() => { + Animated.timing(fadeInAnimation, { + toValue: 1, + duration: 100, + useNativeDriver: true, + }).start(); + }, [fadeInAnimation]); + + useEffect(() => { + fadeIn(); + }, []); + + // Native mobile does not render updates flatlist the changes even though component did update called. + // To notify there something changes we can use extraData prop to flatlist + const extraData = (!props.isDrawerOpen && props.isSmallScreenWidth) ? props.newMarkerReportActionID : undefined; + const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(props.personalDetails, props.report); + return ( + + { + if (props.report.isLoadingMoreReportActions) { + return ( + + ); + } + + // Make sure the oldest report action loaded is not the first. This is so we do not show the + // skeleton view above the created action in a newly generated optimistic chat or one with not + // that many comments. + const lastReportAction = _.last(props.sortedReportActions) || {}; + if (props.report.isLoadingReportActions && lastReportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED) { + return ( + + ); + } + + return null; + }} + keyboardShouldPersistTaps="handled" + onLayout={(event) => { + setSkeletonViewHeight(event.nativeEvent.layout.height); + props.onLayout(event); + }} + onScroll={props.onScroll} + extraData={extraData} + /> + + ); +}; ReportActionsList.propTypes = propTypes; ReportActionsList.defaultProps = defaultProps; +ReportActionsList.displayName = 'ReportActionsList'; export default compose( withDrawerState, From 249a0e14725aba3f77901c6e64176384a0a4f50e Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 28 Mar 2023 12:58:44 +0000 Subject: [PATCH 2/6] Refactor ReportActionsList to use hooks --- src/pages/home/report/ReportActionsList.js | 75 ++++++++++++---------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index 9de9063266f1..c354c845d3ad 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useState} from 'react'; -import {Animated} from 'react-native'; +import Animated, {useSharedValue, useAnimatedStyle, withTiming} from 'react-native-reanimated'; import _ from 'underscore'; import InvertedFlatList from '../../../components/InvertedFlatList'; import withDrawerState, {withDrawerPropTypes} from '../../../components/withDrawerState'; @@ -17,10 +17,15 @@ import participantPropTypes from '../../../components/participantPropTypes'; import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; import reportActionPropTypes from './reportActionPropTypes'; import CONST from '../../../CONST'; -import * as StyleUtils from '../../../styles/StyleUtils'; import reportPropTypes from '../../reportPropTypes'; import networkPropTypes from '../../../components/networkPropTypes'; +// This is a workaround to a reanimated issue -> https://github.com/software-mansion/react-native-reanimated/issues/3355 +if (process.browser) { + // eslint-disable-next-line no-underscore-dangle + window._frameTimestamp = null; +} + const propTypes = { /** Position of the "New" line marker */ newMarkerReportActionID: PropTypes.string, @@ -63,8 +68,26 @@ const defaultProps = { isLoadingMoreReportActions: false, }; +/** + * Create a unique key for each action in the FlatList. + * We use the reportActionID that is a string representation of a random 64-bit int, which should be + * random enough to avoid collisions + * @param {Object} item + * @param {Object} item.action + * @return {String} + */ +function keyExtractor(item) { + return item.reportActionID; +} + const ReportActionsList = (props) => { - const [fadeInAnimation] = useState(() => new Animated.Value(0)); + const opacity = useSharedValue(0); + const animatedStyles = useAnimatedStyle(() => ({ + opacity: withTiming(opacity.value, {duration: 100}), + })); + useEffect(() => { + opacity.value = 1; + }, []); const [skeletonViewHeight, setSkeletonViewHeight] = useState(0); /** @@ -80,17 +103,11 @@ const ReportActionsList = (props) => { return Math.ceil(availableHeight / minimumReportActionHeight); }, [props.windowHeight]); - /** - * Create a unique key for each action in the FlatList. - * We use the reportActionID that is a string representation of a random 64-bit int, which should be - * random enough to avoid collisions - * @param {Object} item - * @param {Object} item.action - * @return {String} - */ - function keyExtractor(item) { - return item.reportActionID; - } + const report = props.report; + const hasOutstandingIOU = props.report.hasOutstandingIOU; + const newMarkerReportActionID = props.newMarkerReportActionID; + const sortedReportActions = props.sortedReportActions; + const mostRecentIOUReportActionID = props.mostRecentIOUReportActionID; /** * Do not move this or make it an anonymous function it is a method @@ -103,43 +120,31 @@ const ReportActionsList = (props) => { * * @returns {React.Component} */ - function renderItem({ + const renderItem = useCallback(({ item: reportAction, index, - }) { + }) => { // When the new indicator should not be displayed we explicitly set it to null - const shouldDisplayNewMarker = reportAction.reportActionID === props.newMarkerReportActionID; + const shouldDisplayNewMarker = reportAction.reportActionID === newMarkerReportActionID; return ( ); - } - - const fadeIn = useCallback(() => { - Animated.timing(fadeInAnimation, { - toValue: 1, - duration: 100, - useNativeDriver: true, - }).start(); - }, [fadeInAnimation]); - - useEffect(() => { - fadeIn(); - }, []); + }, [report, hasOutstandingIOU, newMarkerReportActionID, sortedReportActions, mostRecentIOUReportActionID]); // Native mobile does not render updates flatlist the changes even though component did update called. // To notify there something changes we can use extraData prop to flatlist const extraData = (!props.isDrawerOpen && props.isSmallScreenWidth) ? props.newMarkerReportActionID : undefined; const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(props.personalDetails, props.report); return ( - + Date: Wed, 29 Mar 2023 16:00:38 +0000 Subject: [PATCH 3/6] clarify workaround --- src/pages/home/report/ReportActionsList.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index d38b5157c214..3d584f262123 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -21,7 +21,8 @@ import reportPropTypes from '../../reportPropTypes'; import networkPropTypes from '../../../components/networkPropTypes'; import withLocalize from '../../../components/withLocalize'; -// This is a workaround to a reanimated issue -> https://github.com/software-mansion/react-native-reanimated/issues/3355 +// Workaround to a reanimated issue -> https://github.com/software-mansion/react-native-reanimated/issues/3355 +// We can remove it as soon as we are on > reanimated 3.0.0+ if (process.browser) { // eslint-disable-next-line no-underscore-dangle window._frameTimestamp = null; From a008a9bf46a764267e1d23378a6e6907a6bd97ff Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 30 Mar 2023 13:50:36 +0000 Subject: [PATCH 4/6] add opacity to dependenc --- src/pages/home/report/ReportActionsList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index 3d584f262123..6b1842ef9813 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -89,7 +89,7 @@ const ReportActionsList = (props) => { })); useEffect(() => { opacity.value = 1; - }, []); + }, [opacity]); const [skeletonViewHeight, setSkeletonViewHeight] = useState(0); /** From 4618517b349ef804eb32a1e1082216511201b854 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Mon, 10 Apr 2023 06:15:55 -1000 Subject: [PATCH 5/6] clean up variable and comment --- src/pages/home/report/ReportActionsList.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index 6b1842ef9813..8f4ec46adb9f 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -92,6 +92,8 @@ const ReportActionsList = (props) => { }, [opacity]); const [skeletonViewHeight, setSkeletonViewHeight] = useState(0); + const windowHeight = props.windowHeight; + /** * Calculates the ideal number of report actions to render in the first render, based on the screen height and on * the height of the smallest report action possible. @@ -100,10 +102,10 @@ const ReportActionsList = (props) => { const calculateInitialNumToRender = useCallback(() => { const minimumReportActionHeight = styles.chatItem.paddingTop + styles.chatItem.paddingBottom + variables.fontSizeNormalHeight; - const availableHeight = props.windowHeight + const availableHeight = windowHeight - (CONST.CHAT_FOOTER_MIN_HEIGHT + variables.contentHeaderHeight); return Math.ceil(availableHeight / minimumReportActionHeight); - }, [props.windowHeight]); + }, [windowHeight]); const report = props.report; const hasOutstandingIOU = props.report.hasOutstandingIOU; @@ -112,14 +114,8 @@ const ReportActionsList = (props) => { const mostRecentIOUReportActionID = props.mostRecentIOUReportActionID; /** - * Do not move this or make it an anonymous function it is a method - * so it will not be recreated each time we render an item - * - * See: https://reactnative.dev/docs/optimizing-flatlist-configuration#avoid-anonymous-function-on-renderitem - * * @param {Object} args * @param {Number} args.index - * * @returns {React.Component} */ const renderItem = useCallback(({ From a5ef63d5d1ea9f58b6a5fc72cc8f53a3e83bbad3 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Mon, 10 Apr 2023 11:44:52 -1000 Subject: [PATCH 6/6] move workaround to global setup --- src/pages/home/report/ReportActionsList.js | 7 ------- src/setup/index.js | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index 8f4ec46adb9f..4af8fd161546 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -21,13 +21,6 @@ import reportPropTypes from '../../reportPropTypes'; import networkPropTypes from '../../../components/networkPropTypes'; import withLocalize from '../../../components/withLocalize'; -// Workaround to a reanimated issue -> https://github.com/software-mansion/react-native-reanimated/issues/3355 -// We can remove it as soon as we are on > reanimated 3.0.0+ -if (process.browser) { - // eslint-disable-next-line no-underscore-dangle - window._frameTimestamp = null; -} - const propTypes = { /** Position of the "New" line marker */ newMarkerReportActionID: PropTypes.string, diff --git a/src/setup/index.js b/src/setup/index.js index 352417242ade..eae7036331c9 100644 --- a/src/setup/index.js +++ b/src/setup/index.js @@ -53,4 +53,11 @@ export default function () { // Perform any other platform-specific setup platformSetup(); + + // Workaround to a reanimated issue -> https://github.com/software-mansion/react-native-reanimated/issues/3355 + // We can remove it as soon as we are on > reanimated 3.0.0+ + if (process.browser) { + // eslint-disable-next-line no-underscore-dangle + window._frameTimestamp = null; + } }