diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js
index 2e02a794ed03..4af8fd161546 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 from 'react';
-import {Animated} from 'react-native';
+import React, {useCallback, useEffect, useState} from 'react';
+import Animated, {useSharedValue, useAnimatedStyle, withTiming} from 'react-native-reanimated';
import _ from 'underscore';
import InvertedFlatList from '../../../components/InvertedFlatList';
import withDrawerState, {withDrawerPropTypes} from '../../../components/withDrawerState';
@@ -17,7 +17,6 @@ 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';
import withLocalize from '../../../components/withLocalize';
@@ -64,147 +63,132 @@ 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();
- }
+/**
+ * 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 opacity = useSharedValue(0);
+ const animatedStyles = useAnimatedStyle(() => ({
+ opacity: withTiming(opacity.value, {duration: 100}),
+ }));
+ useEffect(() => {
+ opacity.value = 1;
+ }, [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.
* @return {Number}
*/
- calculateInitialNumToRender() {
+ const calculateInitialNumToRender = useCallback(() => {
const minimumReportActionHeight = styles.chatItem.paddingTop + styles.chatItem.paddingBottom
+ variables.fontSizeNormalHeight;
- const availableHeight = this.props.windowHeight
+ const availableHeight = windowHeight
- (CONST.CHAT_FOOTER_MIN_HEIGHT + variables.contentHeaderHeight);
return Math.ceil(availableHeight / minimumReportActionHeight);
- }
+ }, [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}
- */
- 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
- * 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}
*/
- 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 === this.props.newMarkerReportActionID;
+ const shouldDisplayNewMarker = reportAction.reportActionID === 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}
- />
-
- );
- }
-}
+ }, [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 (
+
+ {
+ 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,
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;
+ }
}