diff --git a/src/components/Hoverable/index.js b/src/components/Hoverable/index.js
index 5cba52db5a7b..2ded0e52e94d 100644
--- a/src/components/Hoverable/index.js
+++ b/src/components/Hoverable/index.js
@@ -1,197 +1,216 @@
 import _ from 'underscore';
-import React, {Component} from 'react';
+import React, {useEffect, useCallback, useState, useRef, useMemo, useImperativeHandle} from 'react';
 import {DeviceEventEmitter} from 'react-native';
 import {propTypes, defaultProps} from './hoverablePropTypes';
 import * as DeviceCapabilities from '../../libs/DeviceCapabilities';
 import CONST from '../../CONST';
 
+/**
+ * Maps the children of a Hoverable component to
+ * - a function that is called with the parameter
+ * - the child itself if it is the only child
+ * @param {Array|Function|ReactNode} children - The children to map.
+ * @param {Object} callbackParam - The parameter to pass to the children function.
+ * @returns {ReactNode} The mapped children.
+ */
+function mapChildren(children, callbackParam) {
+    if (_.isArray(children) && children.length === 1) {
+        return children[0];
+    }
+
+    if (_.isFunction(children)) {
+        return children(callbackParam);
+    }
+
+    return children;
+}
+
+/**
+ * Assigns a ref to an element, either by setting the current property of the ref object or by calling the ref function
+ * @param {Object|Function} ref - The ref object or function.
+ * @param {HTMLElement} el - The element to assign the ref to.
+ */
+function assignRef(ref, el) {
+    if (!ref) {
+        return;
+    }
+
+    if (_.has(ref, 'current')) {
+        // eslint-disable-next-line no-param-reassign
+        ref.current = el;
+    }
+
+    if (_.isFunction(ref)) {
+        ref(el);
+    }
+}
+
 /**
  * It is necessary to create a Hoverable component instead of relying solely on Pressable support for hover state,
  * because nesting Pressables causes issues where the hovered state of the child cannot be easily propagated to the
  * parent. https://github.com/necolas/react-native-web/issues/1875
  */
-class Hoverable extends Component {
-    constructor(props) {
-        super(props);
 
-        this.handleVisibilityChange = this.handleVisibilityChange.bind(this);
-        this.checkHover = this.checkHover.bind(this);
+const Hoverable = React.forwardRef(({disabled, onHoverIn, onHoverOut, onMouseEnter, onMouseLeave, children, shouldHandleScroll}, outerRef) => {
+    const [isHovered, setIsHovered] = useState(false);
 
-        this.state = {
-            isHovered: false,
-        };
+    const isScrolling = useRef(false);
+    const isHoveredRef = useRef(false);
+    const ref = useRef(null);
 
-        this.isHoveredRef = false;
-        this.isScrollingRef = false;
-        this.wrapperView = null;
-    }
+    const updateIsHoveredOnScrolling = useCallback(
+        (hovered) => {
+            if (disabled) {
+                return;
+            }
 
-    componentDidMount() {
-        document.addEventListener('visibilitychange', this.handleVisibilityChange);
-        document.addEventListener('mouseover', this.checkHover);
+            isHoveredRef.current = hovered;
 
-        /**
-         * Only add the scrolling listener if the shouldHandleScroll prop is true
-         * and the scrollingListener is not already set.
-         */
-        if (!this.scrollingListener && this.props.shouldHandleScroll) {
-            this.scrollingListener = DeviceEventEmitter.addListener(CONST.EVENTS.SCROLLING, (scrolling) => {
-                /**
-                 * If user has stopped scrolling and the isHoveredRef is true, then we should update the hover state.
-                 */
-                if (!scrolling && this.isHoveredRef) {
-                    this.setState({isHovered: this.isHoveredRef}, this.props.onHoverIn);
-                } else if (scrolling && this.isHoveredRef) {
-                    /**
-                     * If the user has started scrolling and the isHoveredRef is true, then we should set the hover state to false.
-                     * This is to hide the existing hover and reaction bar.
-                     */
-                    this.setState({isHovered: false}, this.props.onHoverOut);
-                }
-                this.isScrollingRef = scrolling;
-            });
-        }
-    }
+            if (shouldHandleScroll && isScrolling.current) {
+                return;
+            }
+            setIsHovered(hovered);
+        },
+        [disabled, shouldHandleScroll],
+    );
+
+    useEffect(() => {
+        const unsetHoveredWhenDocumentIsHidden = () => document.visibilityState === 'hidden' && setIsHovered(false);
+
+        document.addEventListener('visibilitychange', unsetHoveredWhenDocumentIsHidden);
 
-    componentDidUpdate(prevProps) {
-        if (prevProps.disabled === this.props.disabled) {
+        return () => document.removeEventListener('visibilitychange', unsetHoveredWhenDocumentIsHidden);
+    }, []);
+
+    useEffect(() => {
+        if (!shouldHandleScroll) {
             return;
         }
 
-        if (this.props.disabled && this.state.isHovered) {
-            this.setState({isHovered: false});
-        }
-    }
+        const scrollingListener = DeviceEventEmitter.addListener(CONST.EVENTS.SCROLLING, (scrolling) => {
+            isScrolling.current = scrolling;
+            if (!scrolling) {
+                setIsHovered(isHoveredRef.current);
+            }
+        });
 
-    componentWillUnmount() {
-        document.removeEventListener('visibilitychange', this.handleVisibilityChange);
-        document.removeEventListener('mouseover', this.checkHover);
-        if (this.scrollingListener) {
-            this.scrollingListener.remove();
-        }
-    }
+        return () => scrollingListener.remove();
+    }, [shouldHandleScroll]);
 
-    /**
-     * Sets the hover state of this component to true and execute the onHoverIn callback.
-     *
-     * @param {Boolean} isHovered - Whether or not this component is hovered.
-     */
-    setIsHovered(isHovered) {
-        if (this.props.disabled) {
+    useEffect(() => {
+        if (!DeviceCapabilities.hasHoverSupport()) {
             return;
         }
 
         /**
-         * Capture whther or not the user is hovering over the component.
-         * We will use this to determine if we should update the hover state when the user has stopped scrolling.
+         * Checks the hover state of a component and updates it based on the event target.
+         * This is necessary to handle cases where the hover state might get stuck due to an unreliable mouseleave trigger,
+         * such as when an element is removed before the mouseleave event is triggered.
+         * @param {Event} e - The hover event object.
          */
-        this.isHoveredRef = isHovered;
+        const unsetHoveredIfOutside = (e) => {
+            if (!ref.current || !isHovered) {
+                return;
+            }
 
-        /**
-         * If the isScrollingRef is true, then the user is scrolling and we should not update the hover state.
-         */
-        if (this.isScrollingRef && this.props.shouldHandleScroll && !this.state.isHovered) {
-            return;
-        }
+            if (ref.current.contains(e.target)) {
+                return;
+            }
 
-        if (isHovered !== this.state.isHovered) {
-            this.setState({isHovered}, isHovered ? this.props.onHoverIn : this.props.onHoverOut);
-        }
-    }
+            setIsHovered(false);
+        };
 
-    /**
-     * Checks the hover state of a component and updates it based on the event target.
-     * This is necessary to handle cases where the hover state might get stuck due to an unreliable mouseleave trigger,
-     * such as when an element is removed before the mouseleave event is triggered.
-     * @param {Event} e - The hover event object.
-     */
-    checkHover(e) {
-        if (!this.wrapperView || !this.state.isHovered) {
-            return;
-        }
+        document.addEventListener('mouseover', unsetHoveredIfOutside);
 
-        if (this.wrapperView.contains(e.target)) {
-            return;
-        }
-
-        this.setIsHovered(false);
-    }
+        return () => document.removeEventListener('mouseover', unsetHoveredIfOutside);
+    }, [isHovered]);
 
-    handleVisibilityChange() {
-        if (document.visibilityState !== 'hidden') {
+    useEffect(() => {
+        if (!disabled || !isHovered) {
             return;
         }
+        setIsHovered(false);
+    }, [disabled, isHovered]);
 
-        this.setIsHovered(false);
-    }
-
-    render() {
-        let child = this.props.children;
-        if (_.isArray(this.props.children) && this.props.children.length === 1) {
-            child = this.props.children[0];
+    useEffect(() => {
+        if (disabled) {
+            return;
         }
-
-        if (_.isFunction(child)) {
-            child = child(this.state.isHovered);
+        if (onHoverIn && isHovered) {
+            return onHoverIn();
         }
-
-        if (!DeviceCapabilities.hasHoverSupport()) {
-            return child;
+        if (onHoverOut && !isHovered) {
+            return onHoverOut();
         }
-
-        return React.cloneElement(React.Children.only(child), {
-            ref: (el) => {
-                this.wrapperView = el;
-
-                // Call the original ref, if any
-                const {ref} = child;
-                if (_.isFunction(ref)) {
-                    ref(el);
-                    return;
-                }
-
-                if (_.isObject(ref)) {
-                    ref.current = el;
-                }
-            },
-            onMouseEnter: (el) => {
-                if (_.isFunction(this.props.onMouseEnter)) {
-                    this.props.onMouseEnter(el);
-                }
-
-                this.setIsHovered(true);
-
-                if (_.isFunction(child.props.onMouseEnter)) {
-                    child.props.onMouseEnter(el);
-                }
-            },
-            onMouseLeave: (el) => {
-                if (_.isFunction(this.props.onMouseLeave)) {
-                    this.props.onMouseLeave(el);
-                }
-
-                this.setIsHovered(false);
-
-                if (_.isFunction(child.props.onMouseLeave)) {
-                    child.props.onMouseLeave(el);
-                }
-            },
-            onBlur: (el) => {
-                // Check if the blur event occurred due to clicking outside the element
-                // and the wrapperView contains the element that caused the blur and reset isHovered
-                if (!this.wrapperView.contains(el.target) && !this.wrapperView.contains(el.relatedTarget)) {
-                    this.setIsHovered(false);
-                }
-
-                if (_.isFunction(child.props.onBlur)) {
-                    child.props.onBlur(el);
-                }
-            },
-        });
+    }, [disabled, isHovered, onHoverIn, onHoverOut]);
+
+    // Expose inner ref to parent through outerRef. This enable us to use ref both in parent and child.
+    useImperativeHandle(outerRef, () => ref.current, []);
+
+    const child = useMemo(() => React.Children.only(mapChildren(children, isHovered)), [children, isHovered]);
+
+    const enableHoveredOnMouseEnter = useCallback(
+        (el) => {
+            updateIsHoveredOnScrolling(true);
+
+            if (_.isFunction(onMouseEnter)) {
+                onMouseEnter(el);
+            }
+
+            if (_.isFunction(child.props.onMouseEnter)) {
+                child.props.onMouseEnter(el);
+            }
+        },
+        [child.props, onMouseEnter, updateIsHoveredOnScrolling],
+    );
+
+    const disableHoveredOnMouseLeave = useCallback(
+        (el) => {
+            updateIsHoveredOnScrolling(false);
+
+            if (_.isFunction(onMouseLeave)) {
+                onMouseLeave(el);
+            }
+
+            if (_.isFunction(child.props.onMouseLeave)) {
+                child.props.onMouseLeave(el);
+            }
+        },
+        [child.props, onMouseLeave, updateIsHoveredOnScrolling],
+    );
+
+    const disableHoveredOnBlur = useCallback(
+        (el) => {
+            // Check if the blur event occurred due to clicking outside the element
+            // and the wrapperView contains the element that caused the blur and reset isHovered
+            if (!ref.current.contains(el.target) && !ref.current.contains(el.relatedTarget)) {
+                setIsHovered(false);
+            }
+
+            if (_.isFunction(child.props.onBlur)) {
+                child.props.onBlur(el);
+            }
+        },
+        [child.props],
+    );
+
+    if (!DeviceCapabilities.hasHoverSupport()) {
+        return child;
     }
-}
+
+    return React.cloneElement(child, {
+        ref: (el) => {
+            ref.current = el;
+            assignRef(child.ref, el);
+        },
+        onMouseEnter: enableHoveredOnMouseEnter,
+        onMouseLeave: disableHoveredOnMouseLeave,
+        onBlur: disableHoveredOnBlur,
+    });
+});
 
 Hoverable.propTypes = propTypes;
 Hoverable.defaultProps = defaultProps;
+Hoverable.displayName = 'Hoverable';
 
 export default Hoverable;
diff --git a/src/components/Tooltip/BaseTooltip.js b/src/components/Tooltip/BaseTooltip.js
index f8d1cf87fc42..c46988af7dc4 100644
--- a/src/components/Tooltip/BaseTooltip.js
+++ b/src/components/Tooltip/BaseTooltip.js
@@ -88,10 +88,7 @@ function Tooltip({children, numberOfLines, maxWidth, text, renderTooltipContent,
      * Display the tooltip in an animation.
      */
     const showTooltip = useCallback(() => {
-        if (!isRendered) {
-            setIsRendered(true);
-        }
-
+        setIsRendered(true);
         setIsVisible(true);
 
         animation.current.stopAnimation();
@@ -111,7 +108,7 @@ function Tooltip({children, numberOfLines, maxWidth, text, renderTooltipContent,
             });
         }
         TooltipSense.activate();
-    }, [isRendered]);
+    }, []);
 
     // eslint-disable-next-line rulesdir/prefer-early-return
     useEffect(() => {
@@ -132,11 +129,17 @@ function Tooltip({children, numberOfLines, maxWidth, text, renderTooltipContent,
         if (bounds.width === 0) {
             setIsRendered(false);
         }
+        if (!target.current) {
+            return;
+        }
         // Choose a bounding box for the tooltip to target.
         // In the case when the target is a link that has wrapped onto
         // multiple lines, we want to show the tooltip over the part
         // of the link that the user is hovering over.
         const betterBounds = chooseBoundingBox(target.current, initialMousePosition.current.x, initialMousePosition.current.y);
+        if (!betterBounds) {
+            return;
+        }
         setWrapperWidth(betterBounds.width);
         setWrapperHeight(betterBounds.height);
         setXOffset(betterBounds.x);
@@ -146,7 +149,7 @@ function Tooltip({children, numberOfLines, maxWidth, text, renderTooltipContent,
     /**
      * Hide the tooltip in an animation.
      */
-    const hideTooltip = () => {
+    const hideTooltip = useCallback(() => {
         animation.current.stopAnimation();
 
         if (TooltipSense.isActive() && !isTooltipSenseInitiator.current) {
@@ -164,7 +167,7 @@ function Tooltip({children, numberOfLines, maxWidth, text, renderTooltipContent,
         TooltipSense.deactivate();
 
         setIsVisible(false);
-    };
+    }, []);
 
     // 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
