diff --git a/src/components/Tooltip/index.js b/src/components/Tooltip/index.js
index 0454327de284..398df07649cf 100644
--- a/src/components/Tooltip/index.js
+++ b/src/components/Tooltip/index.js
@@ -1,121 +1,114 @@
import _ from 'underscore';
-import React, {PureComponent} from 'react';
+import React, {memo, useCallback, useEffect, useRef, useState} from 'react';
import {Animated} from 'react-native';
import {BoundsObserver} from '@react-ng/bounds-observer';
import TooltipRenderedOnPageBody from './TooltipRenderedOnPageBody';
import Hoverable from '../Hoverable';
-import withWindowDimensions from '../withWindowDimensions';
import * as tooltipPropTypes from './tooltipPropTypes';
import TooltipSense from './TooltipSense';
import * as DeviceCapabilities from '../../libs/DeviceCapabilities';
-import compose from '../../libs/compose';
-import withLocalize from '../withLocalize';
+import usePrevious from '../../hooks/usePrevious';
+import useLocalize from '../../hooks/useLocalize';
+import useWindowDimensions from '../../hooks/useWindowDimensions';
+
+const hasHoverSupport = DeviceCapabilities.hasHoverSupport();
/**
* A component used to wrap an element intended for displaying a tooltip. The term "tooltip's target" refers to the
* wrapped element, which, upon hover, triggers the tooltip to be shown.
+ * @param {propTypes} props
+ * @returns {ReactNodeLike}
*/
-class Tooltip extends PureComponent {
- constructor(props) {
- super(props);
-
- this.state = {
- // Is tooltip already rendered on the page's body? This happens once.
- isRendered: false,
-
- // Is the tooltip currently visible?
- isVisible: false,
-
- // The distance between the left side of the wrapper view and the left side of the window
- xOffset: 0,
-
- // The distance between the top of the wrapper view and the top of the window
- yOffset: 0,
-
- // The width and height of the wrapper view
- wrapperWidth: 0,
- wrapperHeight: 0,
- };
-
- // Whether the tooltip is first tooltip to activate the TooltipSense
- this.isTooltipSenseInitiator = false;
- this.animation = new Animated.Value(0);
- this.hasHoverSupport = DeviceCapabilities.hasHoverSupport();
-
- this.showTooltip = this.showTooltip.bind(this);
- this.hideTooltip = this.hideTooltip.bind(this);
- this.updateBounds = this.updateBounds.bind(this);
- this.isAnimationCanceled = React.createRef(false);
- }
-
- // eslint-disable-next-line rulesdir/prefer-early-return
- componentDidUpdate(prevProps) {
- // if the tooltip text changed before the initial animation was finished, then the tooltip won't be shown
- // we need to show the tooltip again
- if (this.state.isVisible && this.isAnimationCanceled.current && this.props.text && prevProps.text !== this.props.text) {
- this.isAnimationCanceled.current = false;
- this.showTooltip();
- }
- }
-
- /**
- * Update the tooltip bounding rectangle
- *
- * @param {Object} bounds - updated bounds
- */
- updateBounds(bounds) {
- if (bounds.width === 0) {
- this.setState({isRendered: false});
- }
- this.setState({
- wrapperWidth: bounds.width,
- wrapperHeight: bounds.height,
- xOffset: bounds.x,
- yOffset: bounds.y,
- });
- }
+function Tooltip(props) {
+ const {children, numberOfLines, maxWidth, text, renderTooltipContent, renderTooltipContentKey} = props;
+
+ const {preferredLocale} = useLocalize();
+ const {windowWidth} = useWindowDimensions();
+
+ // Is tooltip already rendered on the page's body? happens once.
+ const [isRendered, setIsRendered] = useState(false);
+ // Is the tooltip currently visible?
+ const [isVisible, setIsVisible] = useState(false);
+ // The distance between the left side of the wrapper view and the left side of the window
+ const [xOffset, setXOffset] = useState(0);
+ // The distance between the top of the wrapper view and the top of the window
+ const [yOffset, setYOffset] = useState(0);
+ // The width and height of the wrapper view
+ const [wrapperWidth, setWrapperWidth] = useState(0);
+ const [wrapperHeight, setWrapperHeight] = useState(0);
+
+ // Whether the tooltip is first tooltip to activate the TooltipSense
+ const isTooltipSenseInitiator = useRef(false);
+ const animation = useRef(new Animated.Value(0));
+ const isAnimationCanceled = useRef(false);
+ const prevText = usePrevious(text);
/**
* Display the tooltip in an animation.
*/
- showTooltip() {
- if (!this.state.isRendered) {
- this.setState({isRendered: true});
+ const showTooltip = useCallback(() => {
+ if (!isRendered) {
+ setIsRendered(true);
}
- this.setState({isVisible: true});
+ setIsVisible(true);
- this.animation.stopAnimation();
+ animation.current.stopAnimation();
// When TooltipSense is active, immediately show the tooltip
if (TooltipSense.isActive()) {
- this.animation.setValue(1);
+ animation.current.setValue(1);
} else {
- this.isTooltipSenseInitiator = true;
- Animated.timing(this.animation, {
+ isTooltipSenseInitiator.current = true;
+ Animated.timing(animation.current, {
toValue: 1,
duration: 140,
delay: 500,
useNativeDriver: false,
}).start(({finished}) => {
- this.isAnimationCanceled.current = !finished;
+ isAnimationCanceled.current = !finished;
});
}
TooltipSense.activate();
- }
+ }, [isRendered]);
+
+ // eslint-disable-next-line rulesdir/prefer-early-return
+ useEffect(() => {
+ // if the tooltip text changed before the initial animation was finished, then the tooltip won't be shown
+ // we need to show the tooltip again
+ if (isVisible && isAnimationCanceled.current && text && prevText !== text) {
+ isAnimationCanceled.current = false;
+ showTooltip();
+ }
+ }, [isVisible, text, prevText, showTooltip]);
+
+ /**
+ * Update the tooltip bounding rectangle
+ *
+ * @param {Object} bounds - updated bounds
+ */
+ const updateBounds = (bounds) => {
+ if (bounds.width === 0) {
+ setIsRendered(false);
+ }
+ setWrapperWidth(bounds.width);
+ setWrapperHeight(bounds.height);
+ setXOffset(bounds.x);
+ setYOffset(bounds.y);
+ };
/**
* Hide the tooltip in an animation.
*/
- hideTooltip() {
- this.animation.stopAnimation();
+ const hideTooltip = () => {
+ animation.current.stopAnimation();
- if (TooltipSense.isActive() && !this.isTooltipSenseInitiator) {
- this.animation.setValue(0);
+ if (TooltipSense.isActive() && !isTooltipSenseInitiator.current) {
+ animation.current.setValue(0);
} else {
// Hide the first tooltip which initiated the TooltipSense with animation
- this.isTooltipSenseInitiator = false;
- Animated.timing(this.animation, {
+ isTooltipSenseInitiator.current = false;
+ Animated.timing(animation.current, {
toValue: 0,
duration: 140,
useNativeDriver: false,
@@ -124,53 +117,51 @@ class Tooltip extends PureComponent {
TooltipSense.deactivate();
- this.setState({isVisible: false});
- }
+ setIsVisible(false);
+ };
- render() {
- // Skip the tooltip and return the children if the text is empty,
- // we don't have a render function or the device does not support hovering
- if ((_.isEmpty(this.props.text) && this.props.renderTooltipContent == null) || !this.hasHoverSupport) {
- return this.props.children;
- }
+ // Skip the tooltip and return the children if the text is empty,
+ // we don't have a render function or the device does not support hovering
+ if ((_.isEmpty(text) && renderTooltipContent == null) || !hasHoverSupport) {
+ return children;
+ }
- return (
- <>
- {this.state.isRendered && (
-
- )}
-
+ {isRendered && (
+
+ )}
+
+
-
- {this.props.children}
-
-
- >
- );
- }
+ {children}
+
+
+ >
+ );
}
Tooltip.propTypes = tooltipPropTypes.propTypes;
Tooltip.defaultProps = tooltipPropTypes.defaultProps;
-export default compose(withWindowDimensions, withLocalize)(Tooltip);
+export default memo(Tooltip);
diff --git a/src/components/Tooltip/tooltipPropTypes.js b/src/components/Tooltip/tooltipPropTypes.js
index f9a1847df242..af18c4cfa412 100644
--- a/src/components/Tooltip/tooltipPropTypes.js
+++ b/src/components/Tooltip/tooltipPropTypes.js
@@ -1,5 +1,4 @@
import PropTypes from 'prop-types';
-import {windowDimensionsPropTypes} from '../withWindowDimensions';
import variables from '../../styles/variables';
import CONST from '../../CONST';
@@ -13,9 +12,6 @@ const propTypes = {
/** Children to wrap with Tooltip. */
children: PropTypes.node.isRequired,
- /** Props inherited from withWindowDimensions */
- ...windowDimensionsPropTypes,
-
/** Any additional amount to manually adjust the horizontal position of the tooltip.
A positive value shifts the tooltip to the right, and a negative value shifts it to the left. */
shiftHorizontal: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),