From 65cac317dc3e062ab07a0366c6fd80d2b9010242 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Tue, 25 Jul 2023 13:27:22 -0700
Subject: [PATCH 01/37] Rename dropZoneID prop to follow conventions

---
 src/components/DragAndDrop/DropZone/index.js       | 4 ++--
 src/components/DragAndDrop/dragAndDropPropTypes.js | 4 ++--
 src/components/DragAndDrop/index.js                | 4 ++--
 src/pages/home/report/ReportActionCompose.js       | 4 ++--
 src/pages/home/report/ReportDropUI.js              | 2 +-
 src/stories/DragAndDrop.stories.js                 | 6 +++---
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/components/DragAndDrop/DropZone/index.js b/src/components/DragAndDrop/DropZone/index.js
index 525e5a8c1484..a073009e22d5 100644
--- a/src/components/DragAndDrop/DropZone/index.js
+++ b/src/components/DragAndDrop/DropZone/index.js
@@ -12,7 +12,7 @@ const propTypes = {
     children: PropTypes.node.isRequired,
 
     /** Required for drag and drop to properly detect dropzone */
-    dropZoneId: PropTypes.string.isRequired,
+    dropZoneID: PropTypes.string.isRequired,
 };
 
 function DropZone(props) {
@@ -21,7 +21,7 @@ function DropZone(props) {
             <View style={[styles.fullScreenTransparentOverlay, styles.alignItemsCenter, styles.justifyContentCenter]}>{props.children}</View>
             {/* Necessary for blocking events on content which can publish unwanted dragleave even if we are inside dropzone  */}
             <View
-                nativeID={props.dropZoneId}
+                nativeID={props.dropZoneID}
                 style={styles.dropZoneTopInvisibleOverlay}
             />
         </Portal>
diff --git a/src/components/DragAndDrop/dragAndDropPropTypes.js b/src/components/DragAndDrop/dragAndDropPropTypes.js
index f0b56caeb3f7..b7e84e2994bf 100644
--- a/src/components/DragAndDrop/dragAndDropPropTypes.js
+++ b/src/components/DragAndDrop/dragAndDropPropTypes.js
@@ -17,8 +17,8 @@ export default {
     shouldAcceptDrop: PropTypes.func,
 
     /** Id of the element on which we want to detect drag */
-    dropZoneId: PropTypes.string.isRequired,
+    dropZoneID: PropTypes.string.isRequired,
 
     /** Id of the element which is shown while drag is active */
-    activeDropZoneId: PropTypes.string.isRequired,
+    activeDropZoneID: PropTypes.string.isRequired,
 };
diff --git a/src/components/DragAndDrop/index.js b/src/components/DragAndDrop/index.js
index 5c5d17a4a272..71fa18c733b5 100644
--- a/src/components/DragAndDrop/index.js
+++ b/src/components/DragAndDrop/index.js
@@ -83,7 +83,7 @@ class DragAndDrop extends React.Component {
     }
 
     addEventListeners() {
-        this.dropZone = document.getElementById(this.props.dropZoneId);
+        this.dropZone = document.getElementById(this.props.dropZoneID);
         this.dropZoneRect = this.calculateDropZoneClientReact();
         document.addEventListener('dragover', this.dropZoneDragListener);
         document.addEventListener('dragenter', this.dropZoneDragListener);
@@ -154,7 +154,7 @@ class DragAndDrop extends React.Component {
                         event.clientX <= this.dropZoneRect.left ||
                         event.clientX >= this.dropZoneRect.right ||
                         // Cancel drag when file manager is on top of the drop zone area - works only on chromium
-                        (event.target.getAttribute('id') === this.props.activeDropZoneId && !event.relatedTarget)
+                        (event.target.getAttribute('id') === this.props.activeDropZoneID && !event.relatedTarget)
                     ) {
                         this.dropZoneDragState = 'dragleave';
                         this.props.onDragLeave(event);
diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js
index 8fc3eb1bb30d..12945125b6a5 100644
--- a/src/pages/home/report/ReportActionCompose.js
+++ b/src/pages/home/report/ReportActionCompose.js
@@ -1119,8 +1119,8 @@ class ReportActionCompose extends React.Component {
                                     </AttachmentPicker>
                                     <View style={[containerComposeStyles, styles.textInputComposeBorder]}>
                                         <DragAndDrop
-                                            dropZoneId={this.props.dragAndDropId}
-                                            activeDropZoneId={CONST.REPORT.ACTIVE_DROP_NATIVE_ID + this.props.reportID}
+                                            dropZoneID={this.props.dragAndDropId}
+                                            activeDropZoneID={CONST.REPORT.ACTIVE_DROP_NATIVE_ID + this.props.reportID}
                                             onDragEnter={() => {
                                                 this.setState({isDraggingOver: true});
                                             }}
diff --git a/src/pages/home/report/ReportDropUI.js b/src/pages/home/report/ReportDropUI.js
index f59d0a31135d..6e0e6ef68ce4 100644
--- a/src/pages/home/report/ReportDropUI.js
+++ b/src/pages/home/report/ReportDropUI.js
@@ -16,7 +16,7 @@ function ReportDropUI(props) {
     return (
         <DropZone
             dropZoneViewHolderName={CONST.REPORT.DROP_HOST_NAME}
-            dropZoneId={CONST.REPORT.ACTIVE_DROP_NATIVE_ID}
+            dropZoneID={CONST.REPORT.ACTIVE_DROP_NATIVE_ID}
         >
             <View style={styles.mb3}>
                 <Icon
diff --git a/src/stories/DragAndDrop.stories.js b/src/stories/DragAndDrop.stories.js
index 0398e5af3ac2..ece5394f49e8 100644
--- a/src/stories/DragAndDrop.stories.js
+++ b/src/stories/DragAndDrop.stories.js
@@ -27,8 +27,8 @@ function Default() {
             {/* DragAndDrop does not need to render drop area as children since it is connected to it via id, which gives us flexibility to bring DragAndDrop where your
             draggingOver state is located */}
             <DragAndDrop
-                dropZoneId="dropId"
-                activeDropZoneId="activeDropZoneId"
+                dropZoneID="dropId"
+                activeDropZoneID="activeDropZoneID"
                 onDragEnter={() => {
                     setDraggingOver(true);
                 }}
@@ -75,7 +75,7 @@ function Default() {
             {draggingOver && (
                 <DropZone
                     dropZoneViewHolderName="portalHost"
-                    dropZoneId="activeDropZoneId"
+                    dropZoneID="activeDropZoneID"
                 />
             )}
         </PortalProvider>

From 9851ad1fb85a9e31570ad39c74b1ced4261aa9f1 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Tue, 25 Jul 2023 13:28:45 -0700
Subject: [PATCH 02/37] Add prop types on default components

---
 src/components/DragAndDrop/index.native.js | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/components/DragAndDrop/index.native.js b/src/components/DragAndDrop/index.native.js
index 4c00e56130d4..2f2419b5f06e 100644
--- a/src/components/DragAndDrop/index.native.js
+++ b/src/components/DragAndDrop/index.native.js
@@ -1,5 +1,8 @@
+import DragAndDropPropTypes from './dragAndDropPropTypes';
+
 const DragAndDrop = (props) => props.children;
 
+DragAndDrop.propTypes = DragAndDropPropTypes;
 DragAndDrop.displayName = 'DragAndDrop';
 
 export default DragAndDrop;

From f5f874312a2d6c21c8edd8ba38222e939ee80c1f Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Tue, 25 Jul 2023 13:32:23 -0700
Subject: [PATCH 03/37] Rename disabled prop to isDisabled

---
 .../DragAndDrop/dragAndDropPropTypes.js         |  6 ++++++
 src/components/DragAndDrop/index.js             | 17 +++++------------
 src/pages/home/report/ReportActionCompose.js    |  2 +-
 3 files changed, 12 insertions(+), 13 deletions(-)

diff --git a/src/components/DragAndDrop/dragAndDropPropTypes.js b/src/components/DragAndDrop/dragAndDropPropTypes.js
index b7e84e2994bf..ccd6b7440e28 100644
--- a/src/components/DragAndDrop/dragAndDropPropTypes.js
+++ b/src/components/DragAndDrop/dragAndDropPropTypes.js
@@ -21,4 +21,10 @@ export default {
 
     /** Id of the element which is shown while drag is active */
     activeDropZoneID: PropTypes.string.isRequired,
+
+    /** Whether drag & drop should be disabled */
+    isDisabled: PropTypes.bool,
+
+    /** Rendered child component */
+    children: PropTypes.node.isRequired,
 };
diff --git a/src/components/DragAndDrop/index.js b/src/components/DragAndDrop/index.js
index 71fa18c733b5..b89289d9d2c7 100644
--- a/src/components/DragAndDrop/index.js
+++ b/src/components/DragAndDrop/index.js
@@ -17,12 +17,6 @@ const propTypes = {
 
     /** Guard for accepting drops in drop zone. Drag event is passed to this function as first parameter. This prop is necessary to be inlined to satisfy the linter */
     shouldAcceptDrop: PropTypes.func,
-
-    /** Whether drag & drop should be disabled */
-    disabled: PropTypes.bool,
-
-    /** Rendered child component */
-    children: PropTypes.node.isRequired,
 };
 
 const defaultProps = {
@@ -37,7 +31,7 @@ const defaultProps = {
         }
         return false;
     },
-    disabled: false,
+    isDisabled: false,
 };
 
 class DragAndDrop extends React.Component {
@@ -57,18 +51,17 @@ class DragAndDrop extends React.Component {
     }
 
     componentDidMount() {
-        if (this.props.disabled) {
+        if (this.props.isDisabled) {
             return;
         }
         this.addEventListeners();
     }
 
     componentDidUpdate(prevProps) {
-        const isDisabled = this.props.disabled;
-        if (this.props.isFocused === prevProps.isFocused && isDisabled === prevProps.disabled) {
+        if (this.props.isFocused === prevProps.isFocused && this.props.isDisabled === prevProps.isDisabled) {
             return;
         }
-        if (!this.props.isFocused || isDisabled) {
+        if (!this.props.isFocused || this.props.isDisabled) {
             this.removeEventListeners();
         } else {
             this.addEventListeners();
@@ -76,7 +69,7 @@ class DragAndDrop extends React.Component {
     }
 
     componentWillUnmount() {
-        if (this.props.disabled) {
+        if (this.props.isDisabled) {
             return;
         }
         this.removeEventListeners();
diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js
index 12945125b6a5..0c4601f90645 100644
--- a/src/pages/home/report/ReportActionCompose.js
+++ b/src/pages/home/report/ReportActionCompose.js
@@ -1140,7 +1140,7 @@ class ReportActionCompose extends React.Component {
 
                                                 this.setState({isDraggingOver: false});
                                             }}
-                                            disabled={this.props.disabled}
+                                            isDisabled={this.props.disabled}
                                         >
                                             <Composer
                                                 checkComposerVisibility={() => this.checkComposerVisibility()}

From faa216ba14c9777d2e806ef41ed618a829def0bf Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Tue, 25 Jul 2023 13:40:04 -0700
Subject: [PATCH 04/37] Create useEffectOnPageLoad hook

---
 src/hooks/useEffectOnPageLoad/index.js        | 20 +++++++++++++++++++
 src/hooks/useEffectOnPageLoad/index.native.js |  3 +++
 2 files changed, 23 insertions(+)
 create mode 100644 src/hooks/useEffectOnPageLoad/index.js
 create mode 100644 src/hooks/useEffectOnPageLoad/index.native.js

diff --git a/src/hooks/useEffectOnPageLoad/index.js b/src/hooks/useEffectOnPageLoad/index.js
new file mode 100644
index 000000000000..a7338fe55a6b
--- /dev/null
+++ b/src/hooks/useEffectOnPageLoad/index.js
@@ -0,0 +1,20 @@
+import {useEffect, useRef} from 'react';
+
+export default function useEffectOnPageLoad(onPageLoad, dependencies = []) {
+    const onPageLoadRef = useRef(onPageLoad);
+    onPageLoadRef.current = onPageLoad;
+
+    useEffect(() => {
+        function onPageLoadCallback() {
+            onPageLoadRef.current();
+        }
+
+        if (document.readyState === 'complete') {
+            onPageLoadCallback();
+        } else {
+            window.addEventListener('load', onPageLoadCallback);
+            return () => window.removeEventListener('load', onPageLoadCallback);
+        }
+        // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, dependencies);
+}
diff --git a/src/hooks/useEffectOnPageLoad/index.native.js b/src/hooks/useEffectOnPageLoad/index.native.js
new file mode 100644
index 000000000000..0c55bf66d3e6
--- /dev/null
+++ b/src/hooks/useEffectOnPageLoad/index.native.js
@@ -0,0 +1,3 @@
+import {useEffect} from 'react';
+
+export default useEffect;

From 931043bd19e06ff557972250613ea11c477d88ec Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Tue, 25 Jul 2023 13:48:32 -0700
Subject: [PATCH 05/37] Convert DragAndDrop to functional component

---
 src/components/DragAndDrop/index.js | 282 ++++++++++++----------------
 1 file changed, 115 insertions(+), 167 deletions(-)

diff --git a/src/components/DragAndDrop/index.js b/src/components/DragAndDrop/index.js
index b89289d9d2c7..12a2195aa0cf 100644
--- a/src/components/DragAndDrop/index.js
+++ b/src/components/DragAndDrop/index.js
@@ -1,190 +1,138 @@
-import React from 'react';
-import PropTypes from 'prop-types';
+import {useEffect, useRef, useCallback} from 'react';
 import _ from 'underscore';
-
-import variables from '../../styles/variables';
+import {useIsFocused} from '@react-navigation/native';
 import DragAndDropPropTypes from './dragAndDropPropTypes';
-import withNavigationFocus from '../withNavigationFocus';
+import useEffectOnPageLoad from '../../hooks/useEffectOnPageLoad';
+import useWindowDimensions from '../../hooks/useWindowDimensions';
 
 const COPY_DROP_EFFECT = 'copy';
 const NONE_DROP_EFFECT = 'none';
+const DRAG_ENTER_EVENT = 'dragenter';
+const DRAG_LEAVE_EVENT = 'dragleave';
+const DROP_EVENT = 'drop';
+
+/**
+ * @param {Event} event – drag event
+ * @returns {Boolean}
+ */
+function shouldAcceptDrop(event) {
+    return _.some(event.dataTransfer.types, (type) => type === 'Files');
+}
 
-const propTypes = {
-    ...DragAndDropPropTypes,
-
-    /** Callback to fire when a file has being dragged over the text input & report body. This prop is necessary to be inlined to satisfy the linter */
-    onDragOver: DragAndDropPropTypes.onDragOver,
-
-    /** Guard for accepting drops in drop zone. Drag event is passed to this function as first parameter. This prop is necessary to be inlined to satisfy the linter */
-    shouldAcceptDrop: PropTypes.func,
-};
-
-const defaultProps = {
-    onDragOver: () => {},
-    shouldAcceptDrop: (e) => {
-        if (e.dataTransfer.types) {
-            for (let i = 0; i < e.dataTransfer.types.length; i++) {
-                if (e.dataTransfer.types[i] === 'Files') {
-                    return true;
-                }
-            }
-        }
-        return false;
-    },
-    isDisabled: false,
-};
-
-class DragAndDrop extends React.Component {
-    constructor(props) {
-        super(props);
-
-        this.throttledDragOverHandler = _.throttle(this.dragOverHandler.bind(this), 100);
-        this.throttledDragNDropWindowResizeListener = _.throttle(this.dragNDropWindowResizeListener.bind(this), 100);
-        this.dropZoneDragHandler = this.dropZoneDragHandler.bind(this);
-        this.dropZoneDragListener = this.dropZoneDragListener.bind(this);
-
-        /*
-        Last detected drag state on the dropzone -> we start with dragleave since user is not dragging initially.
-        This state is updated when drop zone is left/entered entirely(not taking the children in the account) or entire window is left
-        */
-        this.dropZoneDragState = 'dragleave';
-    }
+function DragAndDrop({onDragEnter, onDragLeave, onDrop, dropZoneID, activeDropZoneID, children, isDisabled = false}) {
+    const isFocused = useIsFocused();
+    const {windowWidth, isSmallScreenWidth} = useWindowDimensions();
 
-    componentDidMount() {
-        if (this.props.isDisabled) {
-            return;
-        }
-        this.addEventListeners();
-    }
-
-    componentDidUpdate(prevProps) {
-        if (this.props.isFocused === prevProps.isFocused && this.props.isDisabled === prevProps.isDisabled) {
-            return;
-        }
-        if (!this.props.isFocused || this.props.isDisabled) {
-            this.removeEventListeners();
-        } else {
-            this.addEventListeners();
-        }
-    }
+    const dropZone = useRef(null);
+    const dropZoneRect = useRef(null);
 
-    componentWillUnmount() {
-        if (this.props.isDisabled) {
-            return;
+    useEffect(() => {
+        const dropZoneElement = document.getElementById(dropZoneID);
+        if (!dropZoneElement) {
+            throw new Error('Error: element specified by dropZoneID not found');
         }
-        this.removeEventListeners();
-    }
-
-    addEventListeners() {
-        this.dropZone = document.getElementById(this.props.dropZoneID);
-        this.dropZoneRect = this.calculateDropZoneClientReact();
-        document.addEventListener('dragover', this.dropZoneDragListener);
-        document.addEventListener('dragenter', this.dropZoneDragListener);
-        document.addEventListener('dragleave', this.dropZoneDragListener);
-        document.addEventListener('drop', this.dropZoneDragListener);
-        window.addEventListener('resize', this.throttledDragNDropWindowResizeListener);
-    }
-
-    removeEventListeners() {
-        document.removeEventListener('dragover', this.dropZoneDragListener);
-        document.removeEventListener('dragenter', this.dropZoneDragListener);
-        document.removeEventListener('dragleave', this.dropZoneDragListener);
-        document.removeEventListener('drop', this.dropZoneDragListener);
-        window.removeEventListener('resize', this.throttledDragNDropWindowResizeListener);
-    }
-
-    /**
-     * @param {Object} event native Event
+        dropZone.current = dropZoneElement;
+    }, [dropZoneID]);
+
+    useEffectOnPageLoad(
+        () =>
+            _.throttle(() => {
+                const boundingClientRect = dropZone.current.getBoundingClientRect();
+                dropZoneRect.current = {
+                    width: boundingClientRect.width,
+                    left: isSmallScreenWidth ? 0 : boundingClientRect.left,
+                    right: isSmallScreenWidth ? windowWidth : boundingClientRect.right,
+                    top: boundingClientRect.top,
+                    bottom: boundingClientRect.bottom,
+                };
+            }),
+        [windowWidth, isSmallScreenWidth],
+    );
+
+    /*
+     * Last detected drag state on the dropzone -> we start with dragleave since user is not dragging initially.
+     * This state is updated when drop zone is left/entered entirely(not taking the children in the account) or entire window is left
      */
-    dragOverHandler(event) {
-        this.props.onDragOver(event);
-    }
-
-    dragNDropWindowResizeListener() {
-        // Update bounding client rect on window resize
-        this.dropZoneRect = this.calculateDropZoneClientReact();
-    }
-
-    calculateDropZoneClientReact() {
-        const boundingClientRect = this.dropZone.getBoundingClientRect();
+    const dropZoneDragState = useRef(DRAG_LEAVE_EVENT);
 
-        // Handle edge case when we are under responsive breakpoint the browser doesn't normalize rect.left to 0 and rect.right to window.innerWidth
-        return {
-            width: boundingClientRect.width,
-            left: window.innerWidth <= variables.mobileResponsiveWidthBreakpoint ? 0 : boundingClientRect.left,
-            right: window.innerWidth <= variables.mobileResponsiveWidthBreakpoint ? window.innerWidth : boundingClientRect.right,
-            top: boundingClientRect.top,
-            bottom: boundingClientRect.bottom,
-        };
-    }
-
-    /**
-     * @param {Object} event native Event
-     */
-    dropZoneDragHandler(event) {
-        // Setting dropEffect for dragover is required for '+' icon on certain platforms/browsers (eg. Safari)
-        switch (event.type) {
-            case 'dragover':
-                // Continuous event -> can hurt performance, be careful when subscribing
-                // eslint-disable-next-line no-param-reassign
-                event.dataTransfer.dropEffect = COPY_DROP_EFFECT;
-                this.throttledDragOverHandler(event);
-                break;
-            case 'dragenter':
-                // Avoid reporting onDragEnter for children views -> not performant
-                if (this.dropZoneDragState === 'dragleave') {
-                    this.dropZoneDragState = 'dragenter';
-                    // eslint-disable-next-line no-param-reassign
-                    event.dataTransfer.dropEffect = COPY_DROP_EFFECT;
-                    this.props.onDragEnter(event);
-                }
-                break;
-            case 'dragleave':
-                if (this.dropZoneDragState === 'dragenter') {
-                    if (
-                        event.clientY <= this.dropZoneRect.top ||
-                        event.clientY >= this.dropZoneRect.bottom ||
-                        event.clientX <= this.dropZoneRect.left ||
-                        event.clientX >= this.dropZoneRect.right ||
-                        // Cancel drag when file manager is on top of the drop zone area - works only on chromium
-                        (event.target.getAttribute('id') === this.props.activeDropZoneID && !event.relatedTarget)
-                    ) {
-                        this.dropZoneDragState = 'dragleave';
-                        this.props.onDragLeave(event);
-                    }
-                }
-                break;
-            case 'drop':
-                this.dropZoneDragState = 'dragleave';
-                this.props.onDrop(event);
-                break;
-            default:
-                break;
+    // If this component is out of focus or disabled, reset the drag state back to the default
+    useEffect(() => {
+        if (isFocused && !isDisabled) {
+            return;
         }
-    }
+        dropZoneDragState.current = DRAG_LEAVE_EVENT;
+    }, [isFocused, isDisabled]);
 
     /**
      * Handles all types of drag-N-drop events on the drop zone associated with composer
      *
      * @param {Object} event native Event
      */
-    dropZoneDragListener(event) {
-        event.preventDefault();
+    const dropZoneDragHandler = useCallback(
+        (event) => {
+            if (!isFocused || isDisabled) {
+                return;
+            }
 
-        if (this.dropZone.contains(event.target) && this.props.shouldAcceptDrop(event)) {
-            this.dropZoneDragHandler(event);
-        } else {
-            // eslint-disable-next-line no-param-reassign
-            event.dataTransfer.dropEffect = NONE_DROP_EFFECT;
-        }
-    }
+            event.preventDefault();
+
+            if (dropZone.current.contains(event.target) && shouldAcceptDrop(event)) {
+                switch (event.type) {
+                    case DRAG_ENTER_EVENT:
+                        // Avoid reporting onDragEnter for children views -> not performant
+                        if (dropZoneDragState.current === DRAG_LEAVE_EVENT) {
+                            dropZoneDragState.current = DRAG_ENTER_EVENT;
+                            // eslint-disable-next-line no-param-reassign
+                            event.dataTransfer.dropEffect = COPY_DROP_EFFECT;
+                            onDragEnter(event);
+                        }
+                        break;
+                    case DRAG_LEAVE_EVENT:
+                        if (dropZoneDragState.current === DRAG_ENTER_EVENT) {
+                            if (
+                                event.clientY <= dropZoneRect.current.top ||
+                                event.clientY >= dropZoneRect.current.bottom ||
+                                event.clientX <= dropZoneRect.current.left ||
+                                event.clientX >= dropZoneRect.current.right ||
+                                // Cancel drag when file manager is on top of the drop zone area - works only on chromium
+                                (event.target.getAttribute('id') === activeDropZoneID && !event.relatedTarget)
+                            ) {
+                                dropZoneDragState.current = DRAG_LEAVE_EVENT;
+                                onDragLeave(event);
+                            }
+                        }
+                        break;
+                    case DROP_EVENT:
+                        dropZoneDragState.current = DRAG_LEAVE_EVENT;
+                        onDrop(event);
+                        break;
+                    default:
+                        break;
+                }
+            } else {
+                // eslint-disable-next-line no-param-reassign
+                event.dataTransfer.dropEffect = NONE_DROP_EFFECT;
+            }
+        },
+        [isFocused, isDisabled, onDragEnter, onDragLeave, activeDropZoneID, onDrop],
+    );
+
+    useEffect(() => {
+        document.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
+        document.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
+        document.addEventListener(DROP_EVENT, dropZoneDragHandler);
+        return () => {
+            document.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
+            document.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
+            document.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
+            document.removeEventListener(DROP_EVENT, dropZoneDragHandler);
+        };
+    }, [dropZoneDragHandler]);
 
-    render() {
-        return this.props.children;
-    }
+    return children;
 }
 
-DragAndDrop.propTypes = propTypes;
-DragAndDrop.defaultProps = defaultProps;
+DragAndDrop.propTypes = DragAndDropPropTypes;
+DragAndDrop.displayName = 'DragAndDrop';
 
-export default withNavigationFocus(DragAndDrop);
+export default DragAndDrop;

From 76654259485146c57c5ec02119ec12a4c38104a9 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Tue, 25 Jul 2023 14:27:37 -0700
Subject: [PATCH 06/37] Handle dragOver event to fix onDrop

---
 src/components/DragAndDrop/index.js | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/components/DragAndDrop/index.js b/src/components/DragAndDrop/index.js
index 12a2195aa0cf..1cc6985bb993 100644
--- a/src/components/DragAndDrop/index.js
+++ b/src/components/DragAndDrop/index.js
@@ -8,6 +8,7 @@ import useWindowDimensions from '../../hooks/useWindowDimensions';
 const COPY_DROP_EFFECT = 'copy';
 const NONE_DROP_EFFECT = 'none';
 const DRAG_ENTER_EVENT = 'dragenter';
+const DRAG_OVER_EVENT = 'dragover';
 const DRAG_LEAVE_EVENT = 'dragleave';
 const DROP_EVENT = 'drop';
 
@@ -78,6 +79,9 @@ function DragAndDrop({onDragEnter, onDragLeave, onDrop, dropZoneID, activeDropZo
 
             if (dropZone.current.contains(event.target) && shouldAcceptDrop(event)) {
                 switch (event.type) {
+                    case DRAG_OVER_EVENT:
+                        // Nothing needed here, just needed to preventDefault in order for the drop event to fire later
+                        break;
                     case DRAG_ENTER_EVENT:
                         // Avoid reporting onDragEnter for children views -> not performant
                         if (dropZoneDragState.current === DRAG_LEAVE_EVENT) {
@@ -118,10 +122,14 @@ function DragAndDrop({onDragEnter, onDragLeave, onDrop, dropZoneID, activeDropZo
     );
 
     useEffect(() => {
+        // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired:
+        // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome
+        document.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
         document.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
         document.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
         document.addEventListener(DROP_EVENT, dropZoneDragHandler);
         return () => {
+            document.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
             document.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
             document.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
             document.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);

From 401e2c65d68ce42c759d54038b36791ccea6620c Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Tue, 25 Jul 2023 14:28:09 -0700
Subject: [PATCH 07/37] Fix passing throttled function to useEffectOnPageLoad

---
 src/components/DragAndDrop/index.js | 22 ++++++++++------------
 1 file changed, 10 insertions(+), 12 deletions(-)

diff --git a/src/components/DragAndDrop/index.js b/src/components/DragAndDrop/index.js
index 1cc6985bb993..d6a739c8991d 100644
--- a/src/components/DragAndDrop/index.js
+++ b/src/components/DragAndDrop/index.js
@@ -36,18 +36,16 @@ function DragAndDrop({onDragEnter, onDragLeave, onDrop, dropZoneID, activeDropZo
     }, [dropZoneID]);
 
     useEffectOnPageLoad(
-        () =>
-            _.throttle(() => {
-                const boundingClientRect = dropZone.current.getBoundingClientRect();
-                dropZoneRect.current = {
-                    width: boundingClientRect.width,
-                    left: isSmallScreenWidth ? 0 : boundingClientRect.left,
-                    right: isSmallScreenWidth ? windowWidth : boundingClientRect.right,
-                    top: boundingClientRect.top,
-                    bottom: boundingClientRect.bottom,
-                };
-            }),
-        [windowWidth, isSmallScreenWidth],
+        _.throttle(() => {
+            const boundingClientRect = dropZone.current.getBoundingClientRect();
+            dropZoneRect.current = {
+                width: boundingClientRect.width,
+                left: isSmallScreenWidth ? 0 : boundingClientRect.left,
+                right: isSmallScreenWidth ? windowWidth : boundingClientRect.right,
+                top: boundingClientRect.top,
+                bottom: boundingClientRect.bottom,
+            };
+        }, [windowWidth, isSmallScreenWidth]),
     );
 
     /*

From 1918bfada5ef81eea7d6a89abcb5d5a1c801a134 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Tue, 25 Jul 2023 14:33:31 -0700
Subject: [PATCH 08/37] Fix yet another whoops with the throttled function

---
 src/components/DragAndDrop/index.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/components/DragAndDrop/index.js b/src/components/DragAndDrop/index.js
index d6a739c8991d..f05cb69b16d2 100644
--- a/src/components/DragAndDrop/index.js
+++ b/src/components/DragAndDrop/index.js
@@ -45,7 +45,8 @@ function DragAndDrop({onDragEnter, onDragLeave, onDrop, dropZoneID, activeDropZo
                 top: boundingClientRect.top,
                 bottom: boundingClientRect.bottom,
             };
-        }, [windowWidth, isSmallScreenWidth]),
+        }, 100),
+        [windowWidth, isSmallScreenWidth],
     );
 
     /*

From e0bd6f76b33c316123b3badfe62c8acd5f9fb5fb Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 26 Jul 2023 22:51:32 -0700
Subject: [PATCH 09/37] Re-implement DragAndDrop with context

---
 .../Consumer/dragAndDropConsumerPropTypes.js  |  15 ++
 src/components/DragAndDrop/Consumer/index.js  |  36 +++++
 .../DragAndDrop/Consumer/index.native.js      |  10 ++
 .../Provider/dragAndDropProviderPropTypes.js  |  15 ++
 src/components/DragAndDrop/Provider/index.js  | 145 ++++++++++++++++++
 .../DragAndDrop/Provider/index.native.js      |  10 ++
 src/components/DragAndDrop/Utils/index.js     |  71 +++++++++
 .../DragAndDrop/Utils/index.native.js         |   7 +
 .../DragAndDrop/dragAndDropPropTypes.js       |  30 ----
 src/components/DragAndDrop/index.js           | 145 ------------------
 src/components/DragAndDrop/index.native.js    |   8 -
 src/pages/home/ReportScreen.js                | 116 +++++++-------
 src/pages/home/report/ReportActionCompose.js  | 118 ++++++--------
 src/pages/home/report/ReportDropUI.js         |  27 ++--
 src/pages/home/report/ReportFooter.js         |   4 +-
 15 files changed, 438 insertions(+), 319 deletions(-)
 create mode 100644 src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js
 create mode 100644 src/components/DragAndDrop/Consumer/index.js
 create mode 100644 src/components/DragAndDrop/Consumer/index.native.js
 create mode 100644 src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js
 create mode 100644 src/components/DragAndDrop/Provider/index.js
 create mode 100644 src/components/DragAndDrop/Provider/index.native.js
 create mode 100644 src/components/DragAndDrop/Utils/index.js
 create mode 100644 src/components/DragAndDrop/Utils/index.native.js
 delete mode 100644 src/components/DragAndDrop/dragAndDropPropTypes.js
 delete mode 100644 src/components/DragAndDrop/index.js
 delete mode 100644 src/components/DragAndDrop/index.native.js

diff --git a/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js b/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js
new file mode 100644
index 000000000000..4351a1f9a8b7
--- /dev/null
+++ b/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js
@@ -0,0 +1,15 @@
+import PropTypes from 'prop-types';
+
+export default {
+    /** Children to render inside this component. */
+    children: PropTypes.node.isRequired,
+
+    /** String ID to identify the dropZone. It should match the dropZoneID of the associated provider. */
+    dropZoneID: PropTypes.string.isRequired,
+
+    /** String name to identify the dropZone Portal. Multiple dropZoneIDs may share a single dropZoneHostName. */
+    dropZoneHostName: PropTypes.string.isRequired,
+
+    /** Function to execute when an item is dropped in the drop zone. */
+    onDrop: PropTypes.func.isRequired,
+};
diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js
new file mode 100644
index 000000000000..c9c7712de8a8
--- /dev/null
+++ b/src/components/DragAndDrop/Consumer/index.js
@@ -0,0 +1,36 @@
+import _ from 'underscore';
+import React, {useContext, useEffect, useRef} from 'react';
+import {Portal} from '@gorhom/portal';
+import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes';
+import DNDUtils from '../Utils';
+
+function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) {
+    const DragAndDropContext = DNDUtils.getDragAndDropContext(dropZoneID);
+    const {isDraggingOver, dropZoneRect} = useContext(DragAndDropContext);
+
+    const onDropRef = useRef(onDrop);
+    onDropRef.current = onDrop;
+    useEffect(() => {
+        // Internal function ensures that we only register the onDrop listener once for this consumer,
+        // even if the onDrop function passed in changes
+        const onDropCallback = () => {
+            if (!onDropRef.current) {
+                return;
+            }
+            onDropRef.current();
+        };
+        DNDUtils.registerOnDropCallback(dropZoneID, onDropCallback);
+        return () => DNDUtils.deregisterOnDropCallback(dropZoneID, onDropCallback);
+    }, [dropZoneID]);
+
+    if (!isDraggingOver) {
+        return null;
+    }
+
+    return <Portal hostName={dropZoneHostName}>{_.isFunction(children) ? children(dropZoneRect) : children}</Portal>;
+}
+
+DragAndDropConsumer.propTypes = dragAndDropConsumerPropTypes;
+DragAndDropConsumer.displayName = 'DragAndDropConsumer';
+
+export default DragAndDropConsumer;
diff --git a/src/components/DragAndDrop/Consumer/index.native.js b/src/components/DragAndDrop/Consumer/index.native.js
new file mode 100644
index 000000000000..fd15e5de87e0
--- /dev/null
+++ b/src/components/DragAndDrop/Consumer/index.native.js
@@ -0,0 +1,10 @@
+import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes';
+
+function DragAndDropConsumer({children}) {
+    return children;
+}
+
+DragAndDropConsumer.propTypes = dragAndDropConsumerPropTypes;
+DragAndDropConsumer.displayName = 'DragAndDropConsumer';
+
+export default DragAndDropConsumer;
diff --git a/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js b/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js
new file mode 100644
index 000000000000..f1461a098f67
--- /dev/null
+++ b/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js
@@ -0,0 +1,15 @@
+import PropTypes from 'prop-types';
+
+export default {
+    /** Children to render inside this component. */
+    children: PropTypes.node.isRequired,
+
+    /** ID for the drop zone. */
+    dropZoneID: PropTypes.string.isRequired,
+
+    /** ID for the Portal host where content can be rendered by consumers. */
+    dropZoneHostName: PropTypes.string.isRequired,
+
+    /** Should this dropZone be disabled? */
+    isDisabled: PropTypes.bool,
+};
diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js
new file mode 100644
index 000000000000..bfdcb98eeaf4
--- /dev/null
+++ b/src/components/DragAndDrop/Provider/index.js
@@ -0,0 +1,145 @@
+import _ from 'underscore';
+import React, {useCallback, useEffect, useRef, useState} from 'react';
+import {View} from 'react-native';
+import {useIsFocused} from '@react-navigation/native';
+import {PortalHost} from '@gorhom/portal';
+import useWindowDimensions from '../../../hooks/useWindowDimensions';
+import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes';
+import DNDUtils from '../Utils';
+
+const COPY_DROP_EFFECT = 'copy';
+const NONE_DROP_EFFECT = 'none';
+const DRAG_ENTER_EVENT = 'dragenter';
+const DRAG_OVER_EVENT = 'dragover';
+const DRAG_LEAVE_EVENT = 'dragleave';
+const DROP_EVENT = 'drop';
+
+/**
+ * @param {Event} event – drag event
+ * @returns {Boolean}
+ */
+function shouldAcceptDrop(event) {
+    return _.some(event.dataTransfer.types, (type) => type === 'Files');
+}
+
+function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled = false}) {
+    const DragAndDropContext = DNDUtils.getDragAndDropContext(dropZoneID, true);
+    useEffect(
+        () => () => {
+            DNDUtils.deleteDragAndDropContext(dropZoneID);
+        },
+        [dropZoneID],
+    );
+
+    const isFocused = useIsFocused();
+    const {windowWidth, windowHeight} = useWindowDimensions();
+
+    const dropZone = useRef(null);
+
+    const [isDraggingOver, setIsDraggingOver] = useState(false);
+    const [dropZoneRect, setDropZoneRect] = useState({});
+
+    // If this component is out of focus or disabled, reset the drag state back to the default
+    useEffect(() => {
+        if (isFocused && !isDisabled) {
+            return;
+        }
+        setIsDraggingOver(false);
+    }, [isFocused, isDisabled]);
+
+    // Measure the position of the drop zone
+    useEffect(
+        _.throttle(() => {
+            if (!dropZone.current) {
+                return;
+            }
+            dropZone.current.measureInWindow((x, y, width, height) => {
+                setDropZoneRect({
+                    left: x,
+                    top: y,
+                    right: x + width,
+                    bottom: y + height,
+                });
+            });
+        }, 100),
+        [windowWidth, windowHeight],
+    );
+
+    /**
+     * Handles all types of drag-N-drop events on the drop zone associated with composer
+     *
+     * @param {Object} event native Event
+     */
+    const dropZoneDragHandler = useCallback(
+        (event) => {
+            if (!isFocused || isDisabled) {
+                return;
+            }
+
+            event.preventDefault();
+
+            if (dropZone.current.contains(event.target) && shouldAcceptDrop(event)) {
+                switch (event.type) {
+                    case DRAG_OVER_EVENT:
+                        // Nothing needed here, just needed to preventDefault in order for the drop event to fire later
+                        break;
+                    case DRAG_ENTER_EVENT:
+                        if (isDraggingOver) {
+                            return;
+                        }
+                        // eslint-disable-next-line no-param-reassign
+                        event.dataTransfer.dropEffect = COPY_DROP_EFFECT;
+                        setIsDraggingOver(true);
+                        break;
+                    case DRAG_LEAVE_EVENT:
+                        if (
+                            !isDraggingOver ||
+                            !(event.clientY <= dropZoneRect.top || event.clientY >= dropZoneRect.bottom || event.clientX <= dropZoneRect.left || event.clientX >= dropZoneRect.right)
+                        ) {
+                            return;
+                        }
+
+                        setIsDraggingOver(false);
+                        break;
+                    case DROP_EVENT:
+                        setIsDraggingOver(false);
+                        DNDUtils.executeOnDropCallbacks(event, dropZoneID);
+                        break;
+                    default:
+                        break;
+                }
+            } else {
+                // eslint-disable-next-line no-param-reassign
+                event.dataTransfer.dropEffect = NONE_DROP_EFFECT;
+            }
+        },
+        [isFocused, isDisabled, isDraggingOver, dropZoneRect.top, dropZoneRect.bottom, dropZoneRect.left, dropZoneRect.right, dropZoneID],
+    );
+
+    useEffect(() => {
+        // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired:
+        // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome
+        document.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
+        document.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
+        document.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
+        document.addEventListener(DROP_EVENT, dropZoneDragHandler);
+        return () => {
+            document.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
+            document.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
+            document.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
+            document.removeEventListener(DROP_EVENT, dropZoneDragHandler);
+        };
+    }, [dropZoneDragHandler]);
+
+    return (
+        <DragAndDropContext.Provider value={{isDraggingOver, dropZoneRect}}>
+            <View ref={(e) => (dropZone.current = e)}>{children}</View>
+            <PortalHost name={dropZoneHostName} />
+        </DragAndDropContext.Provider>
+    );
+}
+
+DragAndDropProvider.propTypes = dragAndDropProviderPropTypes;
+DragAndDropProvider.displayName = 'DragAndDropProvider';
+
+export default DragAndDropProvider;
diff --git a/src/components/DragAndDrop/Provider/index.native.js b/src/components/DragAndDrop/Provider/index.native.js
new file mode 100644
index 000000000000..f2539e37dc8d
--- /dev/null
+++ b/src/components/DragAndDrop/Provider/index.native.js
@@ -0,0 +1,10 @@
+import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes';
+
+function DragAndDropProvider({children}) {
+    return children;
+}
+
+DragAndDropProvider.propTypes = dragAndDropProviderPropTypes;
+DragAndDropProvider.displayName = 'DragAndDropProvider';
+
+export default DragAndDropProvider;
diff --git a/src/components/DragAndDrop/Utils/index.js b/src/components/DragAndDrop/Utils/index.js
new file mode 100644
index 000000000000..cc9c34c5abbc
--- /dev/null
+++ b/src/components/DragAndDrop/Utils/index.js
@@ -0,0 +1,71 @@
+import {createContext} from 'react';
+
+const dragAndDropContexts = {};
+const onDropCallbacks = {};
+
+/**
+ * @param {String} dropZoneID
+ * @param {Boolean} [shouldCreateNewContext] – should this create a new context for the dropZoneID if one does not already exist?
+ * @returns {Object}
+ */
+function getDragAndDropContext(dropZoneID, shouldCreateNewContext = false) {
+    if (dropZoneID in dragAndDropContexts) {
+        return dragAndDropContexts[dropZoneID];
+    }
+
+    if (!shouldCreateNewContext) {
+        throw new Error(`Could not find DragAndDrop context with dropZoneID: ${dropZoneID}`);
+    }
+
+    dragAndDropContexts[dropZoneID] = createContext({});
+    return dragAndDropContexts[dropZoneID];
+}
+
+function deleteDragAndDropContext(dropZoneID) {
+    delete dragAndDropContexts[dropZoneID];
+}
+
+/**
+ * @param {String} dropZoneID
+ * @param {Function} callback
+ */
+function registerOnDropCallback(dropZoneID, callback) {
+    if (!(dropZoneID in onDropCallbacks)) {
+        onDropCallbacks[dropZoneID] = [];
+    }
+    onDropCallbacks[dropZoneID].push(callback);
+}
+
+/**
+ * @param {String} dropZoneID
+ * @param {Function} callback
+ */
+function deregisterOnDropCallback(dropZoneID, callback) {
+    if (!(dropZoneID in onDropCallbacks)) {
+        return;
+    }
+    const index = onDropCallbacks[dropZoneID].indexOf(callback);
+    if (index < 0) {
+        return;
+    }
+    onDropCallbacks[dropZoneID] = onDropCallbacks[dropZoneID].splice(index, 1);
+}
+
+/**
+ * @param {Event} event – onDrop event
+ * @param {String} dropZoneID – dropZoneID that triggered the event
+ */
+function executeOnDropCallbacks(event, dropZoneID) {
+    if (!(dropZoneID in onDropCallbacks)) {
+        return;
+    }
+    onDropCallbacks[dropZoneID].forEach((callback) => callback(event));
+}
+
+export default {
+    getDragAndDropContext,
+    deleteDragAndDropContext,
+    registerOnDropCallback,
+    deregisterOnDropCallback,
+    executeOnDropCallbacks,
+};
diff --git a/src/components/DragAndDrop/Utils/index.native.js b/src/components/DragAndDrop/Utils/index.native.js
new file mode 100644
index 000000000000..24ca8384d5c4
--- /dev/null
+++ b/src/components/DragAndDrop/Utils/index.native.js
@@ -0,0 +1,7 @@
+export default {
+    getDragAndDropContext: () => {},
+    deleteDragAndDropContext: () => {},
+    registerOnDropCallback: () => {},
+    deregisterOnDropCallback: () => {},
+    executeOnDropCallbacks: () => {},
+};
diff --git a/src/components/DragAndDrop/dragAndDropPropTypes.js b/src/components/DragAndDrop/dragAndDropPropTypes.js
deleted file mode 100644
index ccd6b7440e28..000000000000
--- a/src/components/DragAndDrop/dragAndDropPropTypes.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import PropTypes from 'prop-types';
-
-export default {
-    /** Callback to fire when a file has being dragged over the text input & report body */
-    onDragOver: PropTypes.func,
-
-    /** Callback to fire when a file has been dragged into the text input & report body */
-    onDragEnter: PropTypes.func.isRequired,
-
-    /** Callback to fire when the user is no longer dragging over the text input & report body */
-    onDragLeave: PropTypes.func.isRequired,
-
-    /** Callback to fire when a file is dropped on the text input & report body */
-    onDrop: PropTypes.func.isRequired,
-
-    /** Guard for accepting drops in drop zone. Drag event is passed to this function as first parameter */
-    shouldAcceptDrop: PropTypes.func,
-
-    /** Id of the element on which we want to detect drag */
-    dropZoneID: PropTypes.string.isRequired,
-
-    /** Id of the element which is shown while drag is active */
-    activeDropZoneID: PropTypes.string.isRequired,
-
-    /** Whether drag & drop should be disabled */
-    isDisabled: PropTypes.bool,
-
-    /** Rendered child component */
-    children: PropTypes.node.isRequired,
-};
diff --git a/src/components/DragAndDrop/index.js b/src/components/DragAndDrop/index.js
deleted file mode 100644
index f05cb69b16d2..000000000000
--- a/src/components/DragAndDrop/index.js
+++ /dev/null
@@ -1,145 +0,0 @@
-import {useEffect, useRef, useCallback} from 'react';
-import _ from 'underscore';
-import {useIsFocused} from '@react-navigation/native';
-import DragAndDropPropTypes from './dragAndDropPropTypes';
-import useEffectOnPageLoad from '../../hooks/useEffectOnPageLoad';
-import useWindowDimensions from '../../hooks/useWindowDimensions';
-
-const COPY_DROP_EFFECT = 'copy';
-const NONE_DROP_EFFECT = 'none';
-const DRAG_ENTER_EVENT = 'dragenter';
-const DRAG_OVER_EVENT = 'dragover';
-const DRAG_LEAVE_EVENT = 'dragleave';
-const DROP_EVENT = 'drop';
-
-/**
- * @param {Event} event – drag event
- * @returns {Boolean}
- */
-function shouldAcceptDrop(event) {
-    return _.some(event.dataTransfer.types, (type) => type === 'Files');
-}
-
-function DragAndDrop({onDragEnter, onDragLeave, onDrop, dropZoneID, activeDropZoneID, children, isDisabled = false}) {
-    const isFocused = useIsFocused();
-    const {windowWidth, isSmallScreenWidth} = useWindowDimensions();
-
-    const dropZone = useRef(null);
-    const dropZoneRect = useRef(null);
-
-    useEffect(() => {
-        const dropZoneElement = document.getElementById(dropZoneID);
-        if (!dropZoneElement) {
-            throw new Error('Error: element specified by dropZoneID not found');
-        }
-        dropZone.current = dropZoneElement;
-    }, [dropZoneID]);
-
-    useEffectOnPageLoad(
-        _.throttle(() => {
-            const boundingClientRect = dropZone.current.getBoundingClientRect();
-            dropZoneRect.current = {
-                width: boundingClientRect.width,
-                left: isSmallScreenWidth ? 0 : boundingClientRect.left,
-                right: isSmallScreenWidth ? windowWidth : boundingClientRect.right,
-                top: boundingClientRect.top,
-                bottom: boundingClientRect.bottom,
-            };
-        }, 100),
-        [windowWidth, isSmallScreenWidth],
-    );
-
-    /*
-     * Last detected drag state on the dropzone -> we start with dragleave since user is not dragging initially.
-     * This state is updated when drop zone is left/entered entirely(not taking the children in the account) or entire window is left
-     */
-    const dropZoneDragState = useRef(DRAG_LEAVE_EVENT);
-
-    // If this component is out of focus or disabled, reset the drag state back to the default
-    useEffect(() => {
-        if (isFocused && !isDisabled) {
-            return;
-        }
-        dropZoneDragState.current = DRAG_LEAVE_EVENT;
-    }, [isFocused, isDisabled]);
-
-    /**
-     * Handles all types of drag-N-drop events on the drop zone associated with composer
-     *
-     * @param {Object} event native Event
-     */
-    const dropZoneDragHandler = useCallback(
-        (event) => {
-            if (!isFocused || isDisabled) {
-                return;
-            }
-
-            event.preventDefault();
-
-            if (dropZone.current.contains(event.target) && shouldAcceptDrop(event)) {
-                switch (event.type) {
-                    case DRAG_OVER_EVENT:
-                        // Nothing needed here, just needed to preventDefault in order for the drop event to fire later
-                        break;
-                    case DRAG_ENTER_EVENT:
-                        // Avoid reporting onDragEnter for children views -> not performant
-                        if (dropZoneDragState.current === DRAG_LEAVE_EVENT) {
-                            dropZoneDragState.current = DRAG_ENTER_EVENT;
-                            // eslint-disable-next-line no-param-reassign
-                            event.dataTransfer.dropEffect = COPY_DROP_EFFECT;
-                            onDragEnter(event);
-                        }
-                        break;
-                    case DRAG_LEAVE_EVENT:
-                        if (dropZoneDragState.current === DRAG_ENTER_EVENT) {
-                            if (
-                                event.clientY <= dropZoneRect.current.top ||
-                                event.clientY >= dropZoneRect.current.bottom ||
-                                event.clientX <= dropZoneRect.current.left ||
-                                event.clientX >= dropZoneRect.current.right ||
-                                // Cancel drag when file manager is on top of the drop zone area - works only on chromium
-                                (event.target.getAttribute('id') === activeDropZoneID && !event.relatedTarget)
-                            ) {
-                                dropZoneDragState.current = DRAG_LEAVE_EVENT;
-                                onDragLeave(event);
-                            }
-                        }
-                        break;
-                    case DROP_EVENT:
-                        dropZoneDragState.current = DRAG_LEAVE_EVENT;
-                        onDrop(event);
-                        break;
-                    default:
-                        break;
-                }
-            } else {
-                // eslint-disable-next-line no-param-reassign
-                event.dataTransfer.dropEffect = NONE_DROP_EFFECT;
-            }
-        },
-        [isFocused, isDisabled, onDragEnter, onDragLeave, activeDropZoneID, onDrop],
-    );
-
-    useEffect(() => {
-        // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired:
-        // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome
-        document.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
-        document.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
-        document.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
-        document.addEventListener(DROP_EVENT, dropZoneDragHandler);
-        return () => {
-            document.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
-            document.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
-            document.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
-            document.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
-            document.removeEventListener(DROP_EVENT, dropZoneDragHandler);
-        };
-    }, [dropZoneDragHandler]);
-
-    return children;
-}
-
-DragAndDrop.propTypes = DragAndDropPropTypes;
-DragAndDrop.displayName = 'DragAndDrop';
-
-export default DragAndDrop;
diff --git a/src/components/DragAndDrop/index.native.js b/src/components/DragAndDrop/index.native.js
deleted file mode 100644
index 2f2419b5f06e..000000000000
--- a/src/components/DragAndDrop/index.native.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import DragAndDropPropTypes from './dragAndDropPropTypes';
-
-const DragAndDrop = (props) => props.children;
-
-DragAndDrop.propTypes = DragAndDropPropTypes;
-DragAndDrop.displayName = 'DragAndDrop';
-
-export default DragAndDrop;
diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js
index 5d825a528bdf..28649a291115 100644
--- a/src/pages/home/ReportScreen.js
+++ b/src/pages/home/ReportScreen.js
@@ -38,6 +38,7 @@ import MoneyReportHeader from '../../components/MoneyReportHeader';
 import * as ComposerActions from '../../libs/actions/Composer';
 import ReportScreenContext from './ReportScreenContext';
 import TaskHeaderActionButton from '../../components/TaskHeaderActionButton';
+import DragAndDropProvider from '../../components/DragAndDrop/Provider';
 
 const propTypes = {
     /** Navigation route context info provided by react navigation */
@@ -139,7 +140,7 @@ class ReportScreen extends React.Component {
         this.reactionListRef = React.createRef();
 
         // We need unique ID for drag and drop to work properly with stack navigator.
-        this.dragAndDropId = CONST.REPORT.DROP_NATIVE_ID + ReportUtils.generateReportID();
+        this.dragAndDropID = CONST.REPORT.DROP_NATIVE_ID + ReportUtils.generateReportID();
     }
 
     componentDidMount() {
@@ -349,67 +350,70 @@ class ReportScreen extends React.Component {
                                 shouldShowCloseButton
                             />
                         )}
-                        <View
-                            nativeID={this.dragAndDropId}
-                            style={[styles.flex1, styles.justifyContentEnd, styles.overflowHidden]}
-                            onLayout={(event) => {
-                                // Rounding this value for comparison because they can look like this: 411.9999694824219
-                                const skeletonViewContainerHeight = Math.round(event.nativeEvent.layout.height);
-
-                                // Only set state when the height changes to avoid unnecessary renders
-                                if (reportActionsListViewHeight === skeletonViewContainerHeight) return;
-
-                                // The height can be 0 if the component unmounts - we are not interested in this value and want to know how much space it
-                                // takes up so we can set the skeleton view container height.
-                                if (skeletonViewContainerHeight === 0) {
-                                    return;
-                                }
-                                reportActionsListViewHeight = skeletonViewContainerHeight;
-                                this.setState({skeletonViewContainerHeight});
-                            }}
+                        <DragAndDropProvider
+                            dropZoneID={this.dragAndDropID}
+                            dropZoneHostName={CONST.REPORT.DROP_HOST_NAME}
+                            isDisabled={!this.isReportReadyForDisplay()}
                         >
-                            {this.isReportReadyForDisplay() && !isLoadingInitialReportActions && !isLoading && (
-                                <ReportActionsView
-                                    reportActions={this.props.reportActions}
-                                    report={this.props.report}
-                                    isComposerFullSize={this.props.isComposerFullSize}
-                                    parentViewHeight={this.state.skeletonViewContainerHeight}
-                                    policy={policy}
-                                />
-                            )}
-
-                            {/* Note: The report should be allowed to mount even if the initial report actions are not loaded. If we prevent rendering the report while they are loading then
-                            we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */}
-                            {(!this.isReportReadyForDisplay() || isLoadingInitialReportActions || isLoading) && (
-                                <ReportActionsSkeletonView containerHeight={this.state.skeletonViewContainerHeight} />
-                            )}
-
-                            {this.isReportReadyForDisplay() && (
-                                <>
-                                    <ReportFooter
-                                        errors={addWorkspaceRoomOrChatErrors}
-                                        pendingAction={addWorkspaceRoomOrChatPendingAction}
-                                        isOffline={this.props.network.isOffline}
+                            <View
+                                style={[styles.flex1, styles.justifyContentEnd, styles.overflowHidden]}
+                                onLayout={(event) => {
+                                    // Rounding this value for comparison because they can look like this: 411.9999694824219
+                                    const skeletonViewContainerHeight = Math.round(event.nativeEvent.layout.height);
+
+                                    // Only set state when the height changes to avoid unnecessary renders
+                                    if (reportActionsListViewHeight === skeletonViewContainerHeight) return;
+
+                                    // The height can be 0 if the component unmounts - we are not interested in this value and want to know how much space it
+                                    // takes up so we can set the skeleton view container height.
+                                    if (skeletonViewContainerHeight === 0) {
+                                        return;
+                                    }
+                                    reportActionsListViewHeight = skeletonViewContainerHeight;
+                                    this.setState({skeletonViewContainerHeight});
+                                }}
+                            >
+                                {this.isReportReadyForDisplay() && !isLoadingInitialReportActions && !isLoading && (
+                                    <ReportActionsView
                                         reportActions={this.props.reportActions}
                                         report={this.props.report}
                                         isComposerFullSize={this.props.isComposerFullSize}
-                                        onSubmitComment={this.onSubmitComment}
-                                        policies={this.props.policies}
-                                        dragAndDropId={this.dragAndDropId}
+                                        parentViewHeight={this.state.skeletonViewContainerHeight}
+                                        policy={policy}
                                     />
-                                </>
-                            )}
-
-                            {!this.isReportReadyForDisplay() && (
-                                <ReportFooter
-                                    shouldDisableCompose
-                                    isOffline={this.props.network.isOffline}
-                                    dragAndDropId={this.dragAndDropId}
-                                />
-                            )}
+                                )}
 
-                            <PortalHost name={CONST.REPORT.DROP_HOST_NAME} />
-                        </View>
+                                {/* Note: The report should be allowed to mount even if the initial report actions are not loaded. If we prevent rendering the report while they are loading then
+                            we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */}
+                                {(!this.isReportReadyForDisplay() || isLoadingInitialReportActions || isLoading) && (
+                                    <ReportActionsSkeletonView containerHeight={this.state.skeletonViewContainerHeight} />
+                                )}
+
+                                {this.isReportReadyForDisplay() && (
+                                    <>
+                                        <ReportFooter
+                                            errors={addWorkspaceRoomOrChatErrors}
+                                            pendingAction={addWorkspaceRoomOrChatPendingAction}
+                                            isOffline={this.props.network.isOffline}
+                                            reportActions={this.props.reportActions}
+                                            report={this.props.report}
+                                            isComposerFullSize={this.props.isComposerFullSize}
+                                            onSubmitComment={this.onSubmitComment}
+                                            policies={this.props.policies}
+                                            dragAndDropID={this.dragAndDropID}
+                                        />
+                                    </>
+                                )}
+
+                                {!this.isReportReadyForDisplay() && (
+                                    <ReportFooter
+                                        shouldDisableCompose
+                                        isOffline={this.props.network.isOffline}
+                                        dragAndDropID={this.dragAndDropID}
+                                    />
+                                )}
+                            </View>
+                        </DragAndDropProvider>
                     </FullPageNotFoundView>
                 </ScreenWrapper>
             </ReportScreenContext.Provider>
diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js
index 0c4601f90645..11395b091169 100644
--- a/src/pages/home/report/ReportActionCompose.js
+++ b/src/pages/home/report/ReportActionCompose.js
@@ -39,7 +39,6 @@ import withNavigation from '../../../components/withNavigation';
 import * as EmojiUtils from '../../../libs/EmojiUtils';
 import * as UserUtils from '../../../libs/UserUtils';
 import ReportDropUI from './ReportDropUI';
-import DragAndDrop from '../../../components/DragAndDrop';
 import reportPropTypes from '../../reportPropTypes';
 import EmojiSuggestions from '../../../components/EmojiSuggestions';
 import MentionSuggestions from '../../../components/MentionSuggestions';
@@ -119,7 +118,7 @@ const propTypes = {
     pendingAction: PropTypes.oneOf(['add', 'update', 'delete']),
 
     /** Unique id for nativeId in DragAndDrop */
-    dragAndDropId: PropTypes.string.isRequired,
+    dragAndDropID: PropTypes.string.isRequired,
 
     ...windowDimensionsPropTypes,
     ...withLocalizePropTypes,
@@ -225,7 +224,6 @@ class ReportActionCompose extends React.Component {
             textInputShouldClear: false,
             isCommentEmpty: props.comment.length === 0,
             isMenuVisible: false,
-            isDraggingOver: false,
             selection: {
                 start: isMobileSafari && !this.shouldAutoFocus ? 0 : props.comment.length,
                 end: isMobileSafari && !this.shouldAutoFocus ? 0 : props.comment.length,
@@ -979,7 +977,7 @@ class ReportActionCompose extends React.Component {
         // Prevents focusing and showing the keyboard while the drawer is covering the chat.
         const isBlockedFromConcierge = ReportUtils.chatIncludesConcierge(this.props.report) && User.isBlockedFromConcierge(this.props.blockedFromConcierge);
         const inputPlaceholder = this.getInputPlaceholder();
-        const shouldUseFocusedColor = !isBlockedFromConcierge && !this.props.disabled && (this.state.isFocused || this.state.isDraggingOver);
+        const shouldUseFocusedColor = !isBlockedFromConcierge && !this.props.disabled && this.state.isFocused;
         const hasExceededMaxCommentLength = this.state.hasExceededMaxCommentLength;
         const isFullComposerAvailable = this.state.isFullComposerAvailable && !_.isEmpty(this.state.value);
         const hasReportRecipient = _.isObject(reportRecipient) && !_.isEmpty(reportRecipient);
@@ -1118,75 +1116,60 @@ class ReportActionCompose extends React.Component {
                                         )}
                                     </AttachmentPicker>
                                     <View style={[containerComposeStyles, styles.textInputComposeBorder]}>
-                                        <DragAndDrop
-                                            dropZoneID={this.props.dragAndDropId}
-                                            activeDropZoneID={CONST.REPORT.ACTIVE_DROP_NATIVE_ID + this.props.reportID}
-                                            onDragEnter={() => {
-                                                this.setState({isDraggingOver: true});
+                                        <Composer
+                                            checkComposerVisibility={() => this.checkComposerVisibility()}
+                                            autoFocus={this.shouldAutoFocus}
+                                            multiline
+                                            ref={this.setTextInputRef}
+                                            textAlignVertical="top"
+                                            placeholder={inputPlaceholder}
+                                            placeholderTextColor={themeColors.placeholderText}
+                                            onChangeText={(comment) => this.updateComment(comment, true)}
+                                            onKeyPress={this.triggerHotkeyActions}
+                                            style={[styles.textInputCompose, this.props.isComposerFullSize ? styles.textInputFullCompose : styles.flex4]}
+                                            maxLines={maxComposerLines}
+                                            onFocus={() => this.setIsFocused(true)}
+                                            onBlur={() => {
+                                                this.setIsFocused(false);
+                                                this.resetSuggestions();
                                             }}
-                                            onDragLeave={() => {
-                                                this.setState({isDraggingOver: false});
+                                            onClick={() => {
+                                                this.shouldBlockEmojiCalc = false;
+                                                this.shouldBlockMentionCalc = false;
                                             }}
-                                            onDrop={(e) => {
-                                                e.preventDefault();
-                                                if (this.state.isAttachmentPreviewActive) {
-                                                    this.setState({isDraggingOver: false});
+                                            onPasteFile={displayFileInModal}
+                                            shouldClear={this.state.textInputShouldClear}
+                                            onClear={() => this.setTextInputShouldClear(false)}
+                                            isDisabled={isBlockedFromConcierge || this.props.disabled}
+                                            selection={this.state.selection}
+                                            onSelectionChange={this.onSelectionChange}
+                                            isFullComposerAvailable={isFullComposerAvailable}
+                                            setIsFullComposerAvailable={this.setIsFullComposerAvailable}
+                                            isComposerFullSize={this.props.isComposerFullSize}
+                                            value={this.state.value}
+                                            numberOfLines={this.props.numberOfLines}
+                                            onNumberOfLinesChange={this.updateNumberOfLines}
+                                            shouldCalculateCaretPosition
+                                            onLayout={(e) => {
+                                                const composerHeight = e.nativeEvent.layout.height;
+                                                if (this.state.composerHeight === composerHeight) {
                                                     return;
                                                 }
-
-                                                const file = lodashGet(e, ['dataTransfer', 'files', 0]);
-
-                                                displayFileInModal(file);
-
-                                                this.setState({isDraggingOver: false});
+                                                this.setState({composerHeight});
                                             }}
-                                            isDisabled={this.props.disabled}
-                                        >
-                                            <Composer
-                                                checkComposerVisibility={() => this.checkComposerVisibility()}
-                                                autoFocus={this.shouldAutoFocus}
-                                                multiline
-                                                ref={this.setTextInputRef}
-                                                textAlignVertical="top"
-                                                placeholder={inputPlaceholder}
-                                                placeholderTextColor={themeColors.placeholderText}
-                                                onChangeText={(comment) => this.updateComment(comment, true)}
-                                                onKeyPress={this.triggerHotkeyActions}
-                                                style={[styles.textInputCompose, this.props.isComposerFullSize ? styles.textInputFullCompose : styles.flex4]}
-                                                maxLines={maxComposerLines}
-                                                onFocus={() => this.setIsFocused(true)}
-                                                onBlur={() => {
-                                                    this.setIsFocused(false);
-                                                    this.resetSuggestions();
-                                                }}
-                                                onClick={() => {
-                                                    this.shouldBlockEmojiCalc = false;
-                                                    this.shouldBlockMentionCalc = false;
-                                                }}
-                                                onPasteFile={displayFileInModal}
-                                                shouldClear={this.state.textInputShouldClear}
-                                                onClear={() => this.setTextInputShouldClear(false)}
-                                                isDisabled={isBlockedFromConcierge || this.props.disabled}
-                                                selection={this.state.selection}
-                                                onSelectionChange={this.onSelectionChange}
-                                                isFullComposerAvailable={isFullComposerAvailable}
-                                                setIsFullComposerAvailable={this.setIsFullComposerAvailable}
-                                                isComposerFullSize={this.props.isComposerFullSize}
-                                                value={this.state.value}
-                                                numberOfLines={this.props.numberOfLines}
-                                                onNumberOfLinesChange={this.updateNumberOfLines}
-                                                shouldCalculateCaretPosition
-                                                onLayout={(e) => {
-                                                    const composerHeight = e.nativeEvent.layout.height;
-                                                    if (this.state.composerHeight === composerHeight) {
-                                                        return;
-                                                    }
-                                                    this.setState({composerHeight});
-                                                }}
-                                                onScroll={() => this.setShouldShowSuggestionMenuToFalse()}
-                                            />
-                                        </DragAndDrop>
+                                            onScroll={() => this.setShouldShowSuggestionMenuToFalse()}
+                                        />
                                     </View>
+                                    {/* <ReportDropUI */}
+                                    {/*    dropZoneID={this.props.dragAndDropID} */}
+                                    {/*    onDrop={(e) => { */}
+                                    {/*        if (this.state.isAttachmentPreviewActive) { */}
+                                    {/*            return; */}
+                                    {/*        } */}
+                                    {/*        const file = lodashGet(e, ['dataTransfer', 'files', 0]); */}
+                                    {/*        displayFileInModal(file); */}
+                                    {/*    }} */}
+                                    {/* /> */}
                                 </>
                             )}
                         </AttachmentModal>
@@ -1236,7 +1219,6 @@ class ReportActionCompose extends React.Component {
                         />
                     </View>
                 </OfflineWithFeedback>
-                {this.state.isDraggingOver && <ReportDropUI />}
                 {!_.isEmpty(this.state.suggestedEmojis) && this.state.shouldShowEmojiSuggestionMenu && (
                     <ArrowKeyFocusManager
                         focusedIndex={this.state.highlightedEmojiIndex}
diff --git a/src/pages/home/report/ReportDropUI.js b/src/pages/home/report/ReportDropUI.js
index 6e0e6ef68ce4..f9dbefbdd3e3 100644
--- a/src/pages/home/report/ReportDropUI.js
+++ b/src/pages/home/report/ReportDropUI.js
@@ -1,22 +1,29 @@
 import React from 'react';
 import {View} from 'react-native';
-import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
+import PropTypes from 'prop-types';
 import styles from '../../../styles/styles';
 import Text from '../../../components/Text';
 import CONST from '../../../CONST';
-import DropZone from '../../../components/DragAndDrop/DropZone';
+import DragAndDropConsumer from '../../../components/DragAndDrop/Consumer';
 import Icon from '../../../components/Icon';
 import * as Expensicons from '../../../components/Icon/Expensicons';
+import useLocalize from '../../../hooks/useLocalize';
 
 const propTypes = {
-    ...withLocalizePropTypes,
+    /** DropZoneID for the current report. */
+    dropZoneID: PropTypes.string.isRequired,
+
+    /** Callback to execute when a file is dropped. */
+    onDrop: PropTypes.func.isRequired,
 };
 
-function ReportDropUI(props) {
+function ReportDropUI({dropZoneID, onDrop}) {
+    const {translate} = useLocalize();
     return (
-        <DropZone
-            dropZoneViewHolderName={CONST.REPORT.DROP_HOST_NAME}
-            dropZoneID={CONST.REPORT.ACTIVE_DROP_NATIVE_ID}
+        <DragAndDropConsumer
+            dropZoneID={dropZoneID}
+            dropZoneHostName={CONST.REPORT.DROP_HOST_NAME}
+            onDrop={onDrop}
         >
             <View style={styles.mb3}>
                 <Icon
@@ -25,12 +32,12 @@ function ReportDropUI(props) {
                     height={100}
                 />
             </View>
-            <Text style={[styles.textHeadline]}>{props.translate('reportActionCompose.dropToUpload')}</Text>
-        </DropZone>
+            <Text style={[styles.textHeadline]}>{translate('reportActionCompose.dropToUpload')}</Text>
+        </DragAndDropConsumer>
     );
 }
 
 ReportDropUI.displayName = 'ReportDropUI';
 ReportDropUI.propTypes = propTypes;
 
-export default withLocalize(ReportDropUI);
+export default ReportDropUI;
diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js
index f37c0247fada..471e47cb2b0a 100644
--- a/src/pages/home/report/ReportFooter.js
+++ b/src/pages/home/report/ReportFooter.js
@@ -45,7 +45,7 @@ const propTypes = {
     shouldDisableCompose: PropTypes.bool,
 
     /** Unique id for nativeId in DragAndDrop */
-    dragAndDropId: PropTypes.string.isRequired,
+    dragAndDropID: PropTypes.string.isRequired,
 
     ...windowDimensionsPropTypes,
 };
@@ -95,7 +95,7 @@ function ReportFooter(props) {
                             pendingAction={props.pendingAction}
                             isComposerFullSize={props.isComposerFullSize}
                             disabled={props.shouldDisableCompose}
-                            dragAndDropId={props.dragAndDropId}
+                            dragAndDropID={props.dragAndDropID}
                         />
                     </SwipeableView>
                 </View>

From 6be2fe0de46f536251f531ee363e02ed79c6b373 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 26 Jul 2023 23:24:40 -0700
Subject: [PATCH 10/37] Create useThrottledEffect hook

---
 src/components/DragAndDrop/Provider/index.js |  8 +++++---
 src/hooks/useThrottledEffect.js              | 15 +++++++++++++++
 2 files changed, 20 insertions(+), 3 deletions(-)
 create mode 100644 src/hooks/useThrottledEffect.js

diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js
index bfdcb98eeaf4..c824a14bf83a 100644
--- a/src/components/DragAndDrop/Provider/index.js
+++ b/src/components/DragAndDrop/Provider/index.js
@@ -4,6 +4,7 @@ import {View} from 'react-native';
 import {useIsFocused} from '@react-navigation/native';
 import {PortalHost} from '@gorhom/portal';
 import useWindowDimensions from '../../../hooks/useWindowDimensions';
+import useThrottledEffect from '../../../hooks/useThrottledEffect';
 import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes';
 import DNDUtils from '../Utils';
 
@@ -48,8 +49,8 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
     }, [isFocused, isDisabled]);
 
     // Measure the position of the drop zone
-    useEffect(
-        _.throttle(() => {
+    useThrottledEffect(
+        () => {
             if (!dropZone.current) {
                 return;
             }
@@ -61,7 +62,8 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
                     bottom: y + height,
                 });
             });
-        }, 100),
+        },
+        100,
         [windowWidth, windowHeight],
     );
 
diff --git a/src/hooks/useThrottledEffect.js b/src/hooks/useThrottledEffect.js
new file mode 100644
index 000000000000..099b4b02dcc0
--- /dev/null
+++ b/src/hooks/useThrottledEffect.js
@@ -0,0 +1,15 @@
+import _ from 'underscore';
+import {useEffect, useRef, useCallback} from 'react';
+
+export default function useThrottledEffect(effect, rateLimit, dependencies) {
+    const callback = useRef(effect);
+    callback.current = effect;
+
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+    const throttledEffect = useCallback(
+        _.throttle(() => effect.current(), rateLimit),
+        [rateLimit],
+    );
+
+    useEffect(throttledEffect, [throttledEffect, ...dependencies]);
+}

From 23f2d4fd17bb51b3f5d4b957beece11e7c7d0338 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 26 Jul 2023 23:26:29 -0700
Subject: [PATCH 11/37] remove unused import

---
 src/pages/home/ReportScreen.js               |  1 -
 src/pages/home/report/ReportActionCompose.js | 20 ++++++++++----------
 2 files changed, 10 insertions(+), 11 deletions(-)

diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js
index 28649a291115..4bea13035ae5 100644
--- a/src/pages/home/ReportScreen.js
+++ b/src/pages/home/ReportScreen.js
@@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
 import {View} from 'react-native';
 import lodashGet from 'lodash/get';
 import _ from 'underscore';
-import {PortalHost} from '@gorhom/portal';
 import styles from '../../styles/styles';
 import ScreenWrapper from '../../components/ScreenWrapper';
 import HeaderView from './HeaderView';
diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js
index 11395b091169..84c512249812 100644
--- a/src/pages/home/report/ReportActionCompose.js
+++ b/src/pages/home/report/ReportActionCompose.js
@@ -1160,16 +1160,16 @@ class ReportActionCompose extends React.Component {
                                             onScroll={() => this.setShouldShowSuggestionMenuToFalse()}
                                         />
                                     </View>
-                                    {/* <ReportDropUI */}
-                                    {/*    dropZoneID={this.props.dragAndDropID} */}
-                                    {/*    onDrop={(e) => { */}
-                                    {/*        if (this.state.isAttachmentPreviewActive) { */}
-                                    {/*            return; */}
-                                    {/*        } */}
-                                    {/*        const file = lodashGet(e, ['dataTransfer', 'files', 0]); */}
-                                    {/*        displayFileInModal(file); */}
-                                    {/*    }} */}
-                                    {/* /> */}
+                                    <ReportDropUI
+                                        dropZoneID={this.props.dragAndDropID}
+                                        onDrop={(e) => {
+                                            if (this.state.isAttachmentPreviewActive) {
+                                                return;
+                                            }
+                                            const file = lodashGet(e, ['dataTransfer', 'files', 0]);
+                                            displayFileInModal(file);
+                                        }}
+                                    />
                                 </>
                             )}
                         </AttachmentModal>

From 5949c3e28b3d931ffb010916eef97ac4eecf52ff Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 26 Jul 2023 23:31:24 -0700
Subject: [PATCH 12/37] Add flex 1 style to fix display

---
 src/components/DragAndDrop/Provider/index.js | 8 +++++++-
 src/hooks/useThrottledEffect.js              | 7 ++++++-
 2 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js
index c824a14bf83a..0c07b4b33501 100644
--- a/src/components/DragAndDrop/Provider/index.js
+++ b/src/components/DragAndDrop/Provider/index.js
@@ -7,6 +7,7 @@ import useWindowDimensions from '../../../hooks/useWindowDimensions';
 import useThrottledEffect from '../../../hooks/useThrottledEffect';
 import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes';
 import DNDUtils from '../Utils';
+import styles from '../../../styles/styles';
 
 const COPY_DROP_EFFECT = 'copy';
 const NONE_DROP_EFFECT = 'none';
@@ -135,7 +136,12 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
 
     return (
         <DragAndDropContext.Provider value={{isDraggingOver, dropZoneRect}}>
-            <View ref={(e) => (dropZone.current = e)}>{children}</View>
+            <View
+                ref={(e) => (dropZone.current = e)}
+                style={styles.flex1}
+            >
+                {children}
+            </View>
             <PortalHost name={dropZoneHostName} />
         </DragAndDropContext.Provider>
     );
diff --git a/src/hooks/useThrottledEffect.js b/src/hooks/useThrottledEffect.js
index 099b4b02dcc0..3032beec325f 100644
--- a/src/hooks/useThrottledEffect.js
+++ b/src/hooks/useThrottledEffect.js
@@ -7,7 +7,12 @@ export default function useThrottledEffect(effect, rateLimit, dependencies) {
 
     // eslint-disable-next-line react-hooks/exhaustive-deps
     const throttledEffect = useCallback(
-        _.throttle(() => effect.current(), rateLimit),
+        _.throttle(() => {
+            if (!_.isFunction(effect.current)) {
+                return;
+            }
+            return effect.current();
+        }, rateLimit),
         [rateLimit],
     );
 

From 2094ca0907d64a1cbe9c56525046939edf7381b3 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Wed, 26 Jul 2023 23:55:37 -0700
Subject: [PATCH 13/37] Fix overlay style

---
 src/components/DragAndDrop/Consumer/index.js |  8 ++++++-
 src/components/DragAndDrop/Provider/index.js |  4 ++++
 src/pages/home/report/ReportDropUI.js        | 16 +++++++-------
 src/styles/styles.js                         | 22 +++++++-------------
 src/styles/themes/default.js                 |  2 --
 5 files changed, 28 insertions(+), 24 deletions(-)

diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js
index c9c7712de8a8..4d4a1806b567 100644
--- a/src/components/DragAndDrop/Consumer/index.js
+++ b/src/components/DragAndDrop/Consumer/index.js
@@ -1,8 +1,10 @@
 import _ from 'underscore';
 import React, {useContext, useEffect, useRef} from 'react';
+import {View} from 'react-native';
 import {Portal} from '@gorhom/portal';
 import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes';
 import DNDUtils from '../Utils';
+import styles from '../../../styles/styles';
 
 function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) {
     const DragAndDropContext = DNDUtils.getDragAndDropContext(dropZoneID);
@@ -27,7 +29,11 @@ function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) {
         return null;
     }
 
-    return <Portal hostName={dropZoneHostName}>{_.isFunction(children) ? children(dropZoneRect) : children}</Portal>;
+    return (
+        <Portal hostName={dropZoneHostName}>
+            <View style={[styles.fullScreen]}>{_.isFunction(children) ? children(dropZoneRect) : children}</View>
+        </Portal>
+    );
 }
 
 DragAndDropConsumer.propTypes = dragAndDropConsumerPropTypes;
diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js
index 0c07b4b33501..9ee5f69a8e00 100644
--- a/src/components/DragAndDrop/Provider/index.js
+++ b/src/components/DragAndDrop/Provider/index.js
@@ -87,6 +87,7 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
                         // Nothing needed here, just needed to preventDefault in order for the drop event to fire later
                         break;
                     case DRAG_ENTER_EVENT:
+                        console.log('RORY_DEBUG drag enter');
                         if (isDraggingOver) {
                             return;
                         }
@@ -95,6 +96,7 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
                         setIsDraggingOver(true);
                         break;
                     case DRAG_LEAVE_EVENT:
+                        console.log('RORY_DEBUG drag leave');
                         if (
                             !isDraggingOver ||
                             !(event.clientY <= dropZoneRect.top || event.clientY >= dropZoneRect.bottom || event.clientX <= dropZoneRect.left || event.clientX >= dropZoneRect.right)
@@ -134,6 +136,8 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
         };
     }, [dropZoneDragHandler]);
 
+    console.log('RORY_DEBUG DragAndDropProvider rendering w/ context:', {dropZoneID, isDraggingOver, dropZoneRect});
+
     return (
         <DragAndDropContext.Provider value={{isDraggingOver, dropZoneRect}}>
             <View
diff --git a/src/pages/home/report/ReportDropUI.js b/src/pages/home/report/ReportDropUI.js
index f9dbefbdd3e3..f9edf5577d8b 100644
--- a/src/pages/home/report/ReportDropUI.js
+++ b/src/pages/home/report/ReportDropUI.js
@@ -25,14 +25,16 @@ function ReportDropUI({dropZoneID, onDrop}) {
             dropZoneHostName={CONST.REPORT.DROP_HOST_NAME}
             onDrop={onDrop}
         >
-            <View style={styles.mb3}>
-                <Icon
-                    src={Expensicons.DragAndDrop}
-                    width={100}
-                    height={100}
-                />
+            <View style={[styles.reportDropOverlay, styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]}>
+                <View style={styles.mb3}>
+                    <Icon
+                        src={Expensicons.DragAndDrop}
+                        width={100}
+                        height={100}
+                    />
+                </View>
+                <Text style={[styles.textHeadline]}>{translate('reportActionCompose.dropToUpload')}</Text>
             </View>
-            <Text style={[styles.textHeadline]}>{translate('reportActionCompose.dropToUpload')}</Text>
         </DragAndDropConsumer>
     );
 }
diff --git a/src/styles/styles.js b/src/styles/styles.js
index 0c6c0f4cde78..b02ed8cd8077 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -3156,30 +3156,24 @@ const styles = {
         marginLeft: 6,
     },
 
-    fullScreenTransparentOverlay: {
+    fullScreen: {
         position: 'absolute',
-        width: '100%',
-        height: '100%',
         top: 0,
         left: 0,
         right: 0,
         bottom: 0,
-        backgroundColor: themeColors.dropUIBG,
-        zIndex: 2,
     },
 
-    dropZoneTopInvisibleOverlay: {
-        position: 'absolute',
-        width: '100%',
-        height: '100%',
-        top: 0,
-        left: 0,
-        right: 0,
-        bottom: 0,
-        backgroundColor: themeColors.dropTransparentOverlay,
+    invisibleOverlay: {
+        backgroundColor: themeColors.transparent,
         zIndex: 1000,
     },
 
+    reportDropOverlay: {
+        backgroundColor: themeColors.dropUIBG,
+        zIndex: 2,
+    },
+
     cardSection: {
         backgroundColor: themeColors.cardBG,
         borderRadius: variables.componentBorderRadiusCard,
diff --git a/src/styles/themes/default.js b/src/styles/themes/default.js
index 43f1ff083d94..884a31a03301 100644
--- a/src/styles/themes/default.js
+++ b/src/styles/themes/default.js
@@ -64,7 +64,6 @@ const darkTheme = {
     heroCard: colors.blue,
     uploadPreviewActivityIndicator: colors.greenHighlightBackground,
     dropUIBG: 'rgba(6,27,9,0.92)',
-    dropTransparentOverlay: 'rgba(255,255,255,0)',
     checkBox: colors.green,
     pickerOptionsTextColor: colors.white,
     imageCropBackgroundColor: colors.greenIcons,
@@ -121,7 +120,6 @@ const oldTheme = {
     heroCard: colors.blue,
     uploadPreviewActivityIndicator: colors.gray1,
     dropUIBG: 'rgba(6,27,9,0.92)',
-    dropTransparentOverlay: 'rgba(255,255,255,0)',
     cardBG: colors.gray1,
     cardBorder: colors.gray1,
     checkBox: colors.blue,

From f5afe54f81578caa67a78be448f06e6d537edc2b Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 01:21:00 -0700
Subject: [PATCH 14/37] Remove throttled effect hook so measure can happen
 onLayout

---
 src/components/DragAndDrop/Consumer/index.js |  2 +-
 src/components/DragAndDrop/Provider/index.js | 17 +++++++++--------
 src/hooks/useThrottledEffect.js              | 20 --------------------
 3 files changed, 10 insertions(+), 29 deletions(-)
 delete mode 100644 src/hooks/useThrottledEffect.js

diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js
index 4d4a1806b567..d22e4b2dee65 100644
--- a/src/components/DragAndDrop/Consumer/index.js
+++ b/src/components/DragAndDrop/Consumer/index.js
@@ -31,7 +31,7 @@ function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) {
 
     return (
         <Portal hostName={dropZoneHostName}>
-            <View style={[styles.fullScreen]}>{_.isFunction(children) ? children(dropZoneRect) : children}</View>
+            <View style={styles.fullScreen}>{_.isFunction(children) ? children(dropZoneRect) : children}</View>
         </Portal>
     );
 }
diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js
index 9ee5f69a8e00..deca707f9c5a 100644
--- a/src/components/DragAndDrop/Provider/index.js
+++ b/src/components/DragAndDrop/Provider/index.js
@@ -4,7 +4,6 @@ import {View} from 'react-native';
 import {useIsFocused} from '@react-navigation/native';
 import {PortalHost} from '@gorhom/portal';
 import useWindowDimensions from '../../../hooks/useWindowDimensions';
-import useThrottledEffect from '../../../hooks/useThrottledEffect';
 import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes';
 import DNDUtils from '../Utils';
 import styles from '../../../styles/styles';
@@ -49,9 +48,9 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
         setIsDraggingOver(false);
     }, [isFocused, isDisabled]);
 
-    // Measure the position of the drop zone
-    useThrottledEffect(
-        () => {
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+    const measureDropZone = useCallback(
+        _.throttle(() => {
             if (!dropZone.current) {
                 return;
             }
@@ -63,11 +62,13 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
                     bottom: y + height,
                 });
             });
-        },
-        100,
-        [windowWidth, windowHeight],
+        }, 100),
+        [],
     );
 
+    // Remeasure the position of the drop zone when the window resizes
+    useEffect(measureDropZone, [windowWidth, windowHeight, measureDropZone]);
+
     /**
      * Handles all types of drag-N-drop events on the drop zone associated with composer
      *
@@ -87,7 +88,6 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
                         // Nothing needed here, just needed to preventDefault in order for the drop event to fire later
                         break;
                     case DRAG_ENTER_EVENT:
-                        console.log('RORY_DEBUG drag enter');
                         if (isDraggingOver) {
                             return;
                         }
@@ -142,6 +142,7 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
         <DragAndDropContext.Provider value={{isDraggingOver, dropZoneRect}}>
             <View
                 ref={(e) => (dropZone.current = e)}
+                onLayout={measureDropZone}
                 style={styles.flex1}
             >
                 {children}
diff --git a/src/hooks/useThrottledEffect.js b/src/hooks/useThrottledEffect.js
deleted file mode 100644
index 3032beec325f..000000000000
--- a/src/hooks/useThrottledEffect.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import _ from 'underscore';
-import {useEffect, useRef, useCallback} from 'react';
-
-export default function useThrottledEffect(effect, rateLimit, dependencies) {
-    const callback = useRef(effect);
-    callback.current = effect;
-
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-    const throttledEffect = useCallback(
-        _.throttle(() => {
-            if (!_.isFunction(effect.current)) {
-                return;
-            }
-            return effect.current();
-        }, rateLimit),
-        [rateLimit],
-    );
-
-    useEffect(throttledEffect, [throttledEffect, ...dependencies]);
-}

From 989f5fa0dfce7d4eba184afa187209371fae250b Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 03:04:31 -0700
Subject: [PATCH 15/37] Fix drag state by nesting portal inside transparent
 overlay and implementing counter

---
 src/components/DragAndDrop/Provider/index.js | 45 ++++++++++++--------
 1 file changed, 27 insertions(+), 18 deletions(-)

diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js
index deca707f9c5a..d4469b373281 100644
--- a/src/components/DragAndDrop/Provider/index.js
+++ b/src/components/DragAndDrop/Provider/index.js
@@ -36,6 +36,7 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
     const {windowWidth, windowHeight} = useWindowDimensions();
 
     const dropZone = useRef(null);
+    const dragCounter = useRef(0);
 
     const [isDraggingOver, setIsDraggingOver] = useState(false);
     const [dropZoneRect, setDropZoneRect] = useState({});
@@ -45,6 +46,7 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
         if (isFocused && !isDisabled) {
             return;
         }
+        dragCounter.current = 0;
         setIsDraggingOver(false);
     }, [isFocused, isDisabled]);
 
@@ -82,12 +84,13 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
 
             event.preventDefault();
 
-            if (dropZone.current.contains(event.target) && shouldAcceptDrop(event)) {
+            if (shouldAcceptDrop(event)) {
                 switch (event.type) {
                     case DRAG_OVER_EVENT:
                         // Nothing needed here, just needed to preventDefault in order for the drop event to fire later
                         break;
                     case DRAG_ENTER_EVENT:
+                        dragCounter.current++;
                         if (isDraggingOver) {
                             return;
                         }
@@ -96,17 +99,15 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
                         setIsDraggingOver(true);
                         break;
                     case DRAG_LEAVE_EVENT:
-                        console.log('RORY_DEBUG drag leave');
-                        if (
-                            !isDraggingOver ||
-                            !(event.clientY <= dropZoneRect.top || event.clientY >= dropZoneRect.bottom || event.clientX <= dropZoneRect.left || event.clientX >= dropZoneRect.right)
-                        ) {
+                        dragCounter.current--;
+                        if (!isDraggingOver || dragCounter.current > 0) {
                             return;
                         }
 
                         setIsDraggingOver(false);
                         break;
                     case DROP_EVENT:
+                        dragCounter.current = 0;
                         setIsDraggingOver(false);
                         DNDUtils.executeOnDropCallbacks(event, dropZoneID);
                         break;
@@ -118,26 +119,32 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
                 event.dataTransfer.dropEffect = NONE_DROP_EFFECT;
             }
         },
-        [isFocused, isDisabled, isDraggingOver, dropZoneRect.top, dropZoneRect.bottom, dropZoneRect.left, dropZoneRect.right, dropZoneID],
+        [isFocused, isDisabled, isDraggingOver, dropZoneID],
     );
 
     useEffect(() => {
+        if (!dropZone.current) {
+            return;
+        }
+
         // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired:
         // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome
-        document.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
-        document.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
-        document.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
-        document.addEventListener(DROP_EVENT, dropZoneDragHandler);
+        dropZone.current.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
+        dropZone.current.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
+        dropZone.current.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
+        dropZone.current.addEventListener(DROP_EVENT, dropZoneDragHandler);
         return () => {
-            document.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
-            document.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
-            document.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
-            document.removeEventListener(DROP_EVENT, dropZoneDragHandler);
+            if (!dropZone.current) {
+                return;
+            }
+
+            dropZone.current.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
+            dropZone.current.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
+            dropZone.current.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
+            dropZone.current.removeEventListener(DROP_EVENT, dropZoneDragHandler);
         };
     }, [dropZoneDragHandler]);
 
-    console.log('RORY_DEBUG DragAndDropProvider rendering w/ context:', {dropZoneID, isDraggingOver, dropZoneRect});
-
     return (
         <DragAndDropContext.Provider value={{isDraggingOver, dropZoneRect}}>
             <View
@@ -145,9 +152,11 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
                 onLayout={measureDropZone}
                 style={styles.flex1}
             >
+                <View style={[styles.fullScreen, styles.invisibleOverlay]}>
+                    <PortalHost name={dropZoneHostName} />
+                </View>
                 {children}
             </View>
-            <PortalHost name={dropZoneHostName} />
         </DragAndDropContext.Provider>
     );
 }

From c9a16801051feec2a0acb60942a8636070fe4d8b Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 03:08:52 -0700
Subject: [PATCH 16/37] Simplify by removing measurement

---
 src/components/DragAndDrop/Consumer/index.js | 11 ++------
 src/components/DragAndDrop/Provider/index.js | 27 +-------------------
 2 files changed, 3 insertions(+), 35 deletions(-)

diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js
index d22e4b2dee65..a27c09d495a4 100644
--- a/src/components/DragAndDrop/Consumer/index.js
+++ b/src/components/DragAndDrop/Consumer/index.js
@@ -1,14 +1,11 @@
-import _ from 'underscore';
 import React, {useContext, useEffect, useRef} from 'react';
-import {View} from 'react-native';
 import {Portal} from '@gorhom/portal';
 import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes';
 import DNDUtils from '../Utils';
-import styles from '../../../styles/styles';
 
 function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) {
     const DragAndDropContext = DNDUtils.getDragAndDropContext(dropZoneID);
-    const {isDraggingOver, dropZoneRect} = useContext(DragAndDropContext);
+    const {isDraggingOver} = useContext(DragAndDropContext);
 
     const onDropRef = useRef(onDrop);
     onDropRef.current = onDrop;
@@ -29,11 +26,7 @@ function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) {
         return null;
     }
 
-    return (
-        <Portal hostName={dropZoneHostName}>
-            <View style={styles.fullScreen}>{_.isFunction(children) ? children(dropZoneRect) : children}</View>
-        </Portal>
-    );
+    return <Portal hostName={dropZoneHostName}>{children}</Portal>;
 }
 
 DragAndDropConsumer.propTypes = dragAndDropConsumerPropTypes;
diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js
index d4469b373281..f8b7bba536bf 100644
--- a/src/components/DragAndDrop/Provider/index.js
+++ b/src/components/DragAndDrop/Provider/index.js
@@ -3,7 +3,6 @@ import React, {useCallback, useEffect, useRef, useState} from 'react';
 import {View} from 'react-native';
 import {useIsFocused} from '@react-navigation/native';
 import {PortalHost} from '@gorhom/portal';
-import useWindowDimensions from '../../../hooks/useWindowDimensions';
 import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes';
 import DNDUtils from '../Utils';
 import styles from '../../../styles/styles';
@@ -33,13 +32,11 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
     );
 
     const isFocused = useIsFocused();
-    const {windowWidth, windowHeight} = useWindowDimensions();
 
     const dropZone = useRef(null);
     const dragCounter = useRef(0);
 
     const [isDraggingOver, setIsDraggingOver] = useState(false);
-    const [dropZoneRect, setDropZoneRect] = useState({});
 
     // If this component is out of focus or disabled, reset the drag state back to the default
     useEffect(() => {
@@ -50,27 +47,6 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
         setIsDraggingOver(false);
     }, [isFocused, isDisabled]);
 
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-    const measureDropZone = useCallback(
-        _.throttle(() => {
-            if (!dropZone.current) {
-                return;
-            }
-            dropZone.current.measureInWindow((x, y, width, height) => {
-                setDropZoneRect({
-                    left: x,
-                    top: y,
-                    right: x + width,
-                    bottom: y + height,
-                });
-            });
-        }, 100),
-        [],
-    );
-
-    // Remeasure the position of the drop zone when the window resizes
-    useEffect(measureDropZone, [windowWidth, windowHeight, measureDropZone]);
-
     /**
      * Handles all types of drag-N-drop events on the drop zone associated with composer
      *
@@ -146,10 +122,9 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
     }, [dropZoneDragHandler]);
 
     return (
-        <DragAndDropContext.Provider value={{isDraggingOver, dropZoneRect}}>
+        <DragAndDropContext.Provider value={{isDraggingOver}}>
             <View
                 ref={(e) => (dropZone.current = e)}
-                onLayout={measureDropZone}
                 style={styles.flex1}
             >
                 <View style={[styles.fullScreen, styles.invisibleOverlay]}>

From 58b31c9aa9918e4122dd54f6cff5f8144bab8b8f Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 03:11:17 -0700
Subject: [PATCH 17/37] Remove unused useEffectOnPageLoad hook

---
 src/hooks/useEffectOnPageLoad/index.js        | 20 -------------------
 src/hooks/useEffectOnPageLoad/index.native.js |  3 ---
 2 files changed, 23 deletions(-)
 delete mode 100644 src/hooks/useEffectOnPageLoad/index.js
 delete mode 100644 src/hooks/useEffectOnPageLoad/index.native.js

diff --git a/src/hooks/useEffectOnPageLoad/index.js b/src/hooks/useEffectOnPageLoad/index.js
deleted file mode 100644
index a7338fe55a6b..000000000000
--- a/src/hooks/useEffectOnPageLoad/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import {useEffect, useRef} from 'react';
-
-export default function useEffectOnPageLoad(onPageLoad, dependencies = []) {
-    const onPageLoadRef = useRef(onPageLoad);
-    onPageLoadRef.current = onPageLoad;
-
-    useEffect(() => {
-        function onPageLoadCallback() {
-            onPageLoadRef.current();
-        }
-
-        if (document.readyState === 'complete') {
-            onPageLoadCallback();
-        } else {
-            window.addEventListener('load', onPageLoadCallback);
-            return () => window.removeEventListener('load', onPageLoadCallback);
-        }
-        // eslint-disable-next-line react-hooks/exhaustive-deps
-    }, dependencies);
-}
diff --git a/src/hooks/useEffectOnPageLoad/index.native.js b/src/hooks/useEffectOnPageLoad/index.native.js
deleted file mode 100644
index 0c55bf66d3e6..000000000000
--- a/src/hooks/useEffectOnPageLoad/index.native.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import {useEffect} from 'react';
-
-export default useEffect;

From a0f6afa34a2b051e163a92a708103177df4dc4fb Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 08:52:56 -0700
Subject: [PATCH 18/37] Remove now-unused DropZone component

---
 src/components/DragAndDrop/DropZone/index.js  | 34 -------------------
 .../DragAndDrop/DropZone/index.native.js      |  5 ---
 2 files changed, 39 deletions(-)
 delete mode 100644 src/components/DragAndDrop/DropZone/index.js
 delete mode 100644 src/components/DragAndDrop/DropZone/index.native.js

diff --git a/src/components/DragAndDrop/DropZone/index.js b/src/components/DragAndDrop/DropZone/index.js
deleted file mode 100644
index a073009e22d5..000000000000
--- a/src/components/DragAndDrop/DropZone/index.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import React from 'react';
-import {View} from 'react-native';
-import {Portal} from '@gorhom/portal';
-import PropTypes from 'prop-types';
-import styles from '../../../styles/styles';
-
-const propTypes = {
-    /** Name for a drop zone view holder which gives us the flexibility to mount drop zone wherever we want. The holder view can be implemented as PortalHost */
-    dropZoneViewHolderName: PropTypes.string.isRequired,
-
-    /** Drop zone content */
-    children: PropTypes.node.isRequired,
-
-    /** Required for drag and drop to properly detect dropzone */
-    dropZoneID: PropTypes.string.isRequired,
-};
-
-function DropZone(props) {
-    return (
-        <Portal hostName={props.dropZoneViewHolderName}>
-            <View style={[styles.fullScreenTransparentOverlay, styles.alignItemsCenter, styles.justifyContentCenter]}>{props.children}</View>
-            {/* Necessary for blocking events on content which can publish unwanted dragleave even if we are inside dropzone  */}
-            <View
-                nativeID={props.dropZoneID}
-                style={styles.dropZoneTopInvisibleOverlay}
-            />
-        </Portal>
-    );
-}
-
-DropZone.displayName = 'DropZone';
-DropZone.propTypes = propTypes;
-
-export default DropZone;
diff --git a/src/components/DragAndDrop/DropZone/index.native.js b/src/components/DragAndDrop/DropZone/index.native.js
deleted file mode 100644
index 4e76267469b0..000000000000
--- a/src/components/DragAndDrop/DropZone/index.native.js
+++ /dev/null
@@ -1,5 +0,0 @@
-const DropZone = (props) => props.children;
-
-DropZone.displayName = 'DropZone';
-
-export default DropZone;

From 807d81934c9aeaed2098ce4cb5bc3426f0b5a114 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 10:38:14 -0700
Subject: [PATCH 19/37] Correctly pass event to subscriber

---
 src/components/DragAndDrop/Consumer/index.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js
index a27c09d495a4..041995661b68 100644
--- a/src/components/DragAndDrop/Consumer/index.js
+++ b/src/components/DragAndDrop/Consumer/index.js
@@ -12,11 +12,11 @@ function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) {
     useEffect(() => {
         // Internal function ensures that we only register the onDrop listener once for this consumer,
         // even if the onDrop function passed in changes
-        const onDropCallback = () => {
+        const onDropCallback = (e) => {
             if (!onDropRef.current) {
                 return;
             }
-            onDropRef.current();
+            onDropRef.current(e);
         };
         DNDUtils.registerOnDropCallback(dropZoneID, onDropCallback);
         return () => DNDUtils.deregisterOnDropCallback(dropZoneID, onDropCallback);

From 48ce5e4c70d1ced8d9a176bf203e07852b67b14b Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 11:54:58 -0700
Subject: [PATCH 20/37] Fix storybook

---
 .storybook/preview.js                        |  3 +-
 .storybook/webpack.config.js                 |  1 +
 __mocks__/@react-navigation/native/index.js  |  7 ++
 src/components/DragAndDrop/Provider/index.js |  7 +-
 src/stories/DragAndDrop.stories.js           | 95 +++++++++-----------
 5 files changed, 57 insertions(+), 56 deletions(-)
 create mode 100644 __mocks__/@react-navigation/native/index.js

diff --git a/.storybook/preview.js b/.storybook/preview.js
index 2926e8f8c818..7ccfd74e0e45 100644
--- a/.storybook/preview.js
+++ b/.storybook/preview.js
@@ -1,6 +1,7 @@
 import React from 'react';
 import Onyx from 'react-native-onyx';
 import {SafeAreaProvider} from 'react-native-safe-area-context';
+import {PortalProvider} from '@gorhom/portal';
 import './fonts.css';
 import ComposeProviders from '../src/components/ComposeProviders';
 import HTMLEngineProvider from '../src/components/HTMLEngineProvider';
@@ -14,7 +15,7 @@ Onyx.init({
 
 const decorators = [
     (Story) => (
-        <ComposeProviders components={[OnyxProvider, LocaleContextProvider, HTMLEngineProvider, SafeAreaProvider]}>
+        <ComposeProviders components={[OnyxProvider, LocaleContextProvider, HTMLEngineProvider, SafeAreaProvider, PortalProvider]}>
             <Story />
         </ComposeProviders>
     ),
diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js
index 6c39661c76f9..3c5dbb41ea2c 100644
--- a/.storybook/webpack.config.js
+++ b/.storybook/webpack.config.js
@@ -27,6 +27,7 @@ module.exports = ({config}) => {
         'react-native$': '@expensify/react-native-web',
         'react-native-web': '@expensify/react-native-web',
         '@react-native-community/netinfo': path.resolve(__dirname, '../__mocks__/@react-native-community/netinfo.js'),
+        '@react-navigation/native': path.resolve(__dirname, '../__mocks__/@react-navigation/native'),
     };
 
     // Necessary to overwrite the values in the existing DefinePlugin hardcoded to the Config staging values
diff --git a/__mocks__/@react-navigation/native/index.js b/__mocks__/@react-navigation/native/index.js
new file mode 100644
index 000000000000..7d26028217af
--- /dev/null
+++ b/__mocks__/@react-navigation/native/index.js
@@ -0,0 +1,7 @@
+function useIsFocused() {
+    return true;
+}
+
+export * from '@react-navigation/core';
+export * from '@react-navigation/native';
+export {useIsFocused};
diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js
index f8b7bba536bf..0bcc53788b1b 100644
--- a/src/components/DragAndDrop/Provider/index.js
+++ b/src/components/DragAndDrop/Provider/index.js
@@ -125,9 +125,12 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
         <DragAndDropContext.Provider value={{isDraggingOver}}>
             <View
                 ref={(e) => (dropZone.current = e)}
-                style={styles.flex1}
+                style={[styles.flex1, styles.w100, styles.h100]}
             >
-                <View style={[styles.fullScreen, styles.invisibleOverlay]}>
+                <View
+                    style={[styles.fullScreen, styles.invisibleOverlay]}
+                    nativeID={dropZoneHostName}
+                >
                     <PortalHost name={dropZoneHostName} />
                 </View>
                 {children}
diff --git a/src/stories/DragAndDrop.stories.js b/src/stories/DragAndDrop.stories.js
index ece5394f49e8..ef5b4fbdf1de 100644
--- a/src/stories/DragAndDrop.stories.js
+++ b/src/stories/DragAndDrop.stories.js
@@ -1,10 +1,9 @@
 import React, {useState} from 'react';
 import {View, Image} from 'react-native';
-import {PortalHost, PortalProvider} from '@gorhom/portal';
 import lodashGet from 'lodash/get';
 import Text from '../components/Text';
-import DragAndDrop from '../components/DragAndDrop';
-import DropZone from '../components/DragAndDrop/DropZone';
+import DragAndDropProvider from '../components/DragAndDrop/Provider';
+import DragAndDropConsumer from '../components/DragAndDrop/Consumer';
 import styles from '../styles/styles';
 
 /**
@@ -14,52 +13,34 @@ import styles from '../styles/styles';
  */
 const story = {
     title: 'Components/DragAndDrop',
-    component: DragAndDrop,
+    component: DragAndDropConsumer,
 };
 
-function Default() {
-    const [draggingOver, setDraggingOver] = useState(false);
-
-    const [fileUrl, setFileUrl] = useState('');
+const DROP_ZONE_ID = 'dropID';
+const DROP_ZONE_HOST_NAME = 'dropZoneHostName';
 
+function Default() {
+    const [fileURL, setFileURL] = useState('');
     return (
-        <PortalProvider>
-            {/* DragAndDrop does not need to render drop area as children since it is connected to it via id, which gives us flexibility to bring DragAndDrop where your
-            draggingOver state is located */}
-            <DragAndDrop
-                dropZoneID="dropId"
-                activeDropZoneID="activeDropZoneID"
-                onDragEnter={() => {
-                    setDraggingOver(true);
-                }}
-                onDragLeave={() => {
-                    setDraggingOver(false);
-                }}
-                onDrop={(e) => {
-                    const file = lodashGet(e, ['dataTransfer', 'files', 0]);
-                    if (file && file.type.includes('image')) {
-                        setFileUrl(URL.createObjectURL(file));
-                    }
-                    setDraggingOver(false);
-                }}
+        <View
+            style={[
+                {
+                    width: 500,
+                    height: 500,
+                    backgroundColor: 'beige',
+                },
+                styles.alignItemsCenter,
+                styles.justifyContentCenter,
+            ]}
+        >
+            <DragAndDropProvider
+                dropZoneID={DROP_ZONE_ID}
+                dropZoneHostName={DROP_ZONE_HOST_NAME}
             >
-                <View
-                    style={[
-                        {
-                            width: 200,
-                            height: 200,
-                            backgroundColor: 'beige',
-                            borderColor: 'black',
-                            borderWidth: 1,
-                        },
-                        styles.alignItemsCenter,
-                        styles.justifyContentCenter,
-                    ]}
-                    nativeID="dropId"
-                >
-                    {fileUrl ? (
+                <View style={[styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]}>
+                    {fileURL ? (
                         <Image
-                            source={{uri: fileUrl}}
+                            source={{uri: fileURL}}
                             style={{
                                 width: 200,
                                 height: 200,
@@ -68,17 +49,25 @@ function Default() {
                     ) : (
                         <Text color="black">Drop a picture here!</Text>
                     )}
-                    {/* Portals give us flexibility to render active drag overlay regardless of your react component structure */}
-                    <PortalHost name="portalHost" />
                 </View>
-            </DragAndDrop>
-            {draggingOver && (
-                <DropZone
-                    dropZoneViewHolderName="portalHost"
-                    dropZoneID="activeDropZoneID"
-                />
-            )}
-        </PortalProvider>
+                <DragAndDropConsumer
+                    dropZoneID={DROP_ZONE_ID}
+                    dropZoneHostName={DROP_ZONE_HOST_NAME}
+                    onDrop={(e) => {
+                        const file = lodashGet(e, ['dataTransfer', 'files', 0]);
+                        if (file && file.type.includes('image')) {
+                            const reader = new FileReader();
+                            reader.addEventListener('load', () => setFileURL(reader.result));
+                            reader.readAsDataURL(file);
+                        }
+                    }}
+                >
+                    <View style={[styles.w100, styles.h100, styles.alignItemsCenter, styles.justifyContentCenter, {backgroundColor: 'white'}]}>
+                        <Text color="black">Release to upload file</Text>
+                    </View>
+                </DragAndDropConsumer>
+            </DragAndDropProvider>
+        </View>
     );
 }
 

From 0a09b5b0e953f872ea82cfb97189188c8465a220 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 12:01:08 -0700
Subject: [PATCH 21/37] Get rid of unnecessary hostName

---
 src/CONST.js                                              | 3 +--
 .../DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js  | 3 ---
 src/components/DragAndDrop/Consumer/index.js              | 4 ++--
 .../DragAndDrop/Provider/dragAndDropProviderPropTypes.js  | 3 ---
 src/components/DragAndDrop/Provider/index.js              | 6 +++---
 src/pages/home/ReportScreen.js                            | 8 +-------
 src/pages/home/report/ReportActionCompose.js              | 4 ----
 src/pages/home/report/ReportDropUI.js                     | 8 ++------
 src/pages/home/report/ReportFooter.js                     | 4 ----
 src/stories/DragAndDrop.stories.js                        | 7 +------
 10 files changed, 10 insertions(+), 40 deletions(-)

diff --git a/src/CONST.js b/src/CONST.js
index af25f43d47c0..796a95ce3716 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -480,8 +480,7 @@ const CONST = {
         PERSONAL_DETAIL: 'personalDetail',
     },
     REPORT: {
-        DROP_HOST_NAME: 'ReportDropZone',
-        DROP_NATIVE_ID: 'report-dropzone',
+        DROP_ZONE_ID: 'report-dropzone',
         ACTIVE_DROP_NATIVE_ID: 'report-dropzone',
         MAXIMUM_PARTICIPANTS: 8,
         SPLIT_REPORTID: '-2',
diff --git a/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js b/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js
index 4351a1f9a8b7..d039c766b10a 100644
--- a/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js
+++ b/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js
@@ -7,9 +7,6 @@ export default {
     /** String ID to identify the dropZone. It should match the dropZoneID of the associated provider. */
     dropZoneID: PropTypes.string.isRequired,
 
-    /** String name to identify the dropZone Portal. Multiple dropZoneIDs may share a single dropZoneHostName. */
-    dropZoneHostName: PropTypes.string.isRequired,
-
     /** Function to execute when an item is dropped in the drop zone. */
     onDrop: PropTypes.func.isRequired,
 };
diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js
index 041995661b68..886d10b37fc7 100644
--- a/src/components/DragAndDrop/Consumer/index.js
+++ b/src/components/DragAndDrop/Consumer/index.js
@@ -3,7 +3,7 @@ import {Portal} from '@gorhom/portal';
 import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes';
 import DNDUtils from '../Utils';
 
-function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) {
+function DragAndDropConsumer({children, dropZoneID, onDrop}) {
     const DragAndDropContext = DNDUtils.getDragAndDropContext(dropZoneID);
     const {isDraggingOver} = useContext(DragAndDropContext);
 
@@ -26,7 +26,7 @@ function DragAndDropConsumer({children, dropZoneID, dropZoneHostName, onDrop}) {
         return null;
     }
 
-    return <Portal hostName={dropZoneHostName}>{children}</Portal>;
+    return <Portal hostName={dropZoneID}>{children}</Portal>;
 }
 
 DragAndDropConsumer.propTypes = dragAndDropConsumerPropTypes;
diff --git a/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js b/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js
index f1461a098f67..e33a47474517 100644
--- a/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js
+++ b/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js
@@ -7,9 +7,6 @@ export default {
     /** ID for the drop zone. */
     dropZoneID: PropTypes.string.isRequired,
 
-    /** ID for the Portal host where content can be rendered by consumers. */
-    dropZoneHostName: PropTypes.string.isRequired,
-
     /** Should this dropZone be disabled? */
     isDisabled: PropTypes.bool,
 };
diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js
index 0bcc53788b1b..40b2b17d021a 100644
--- a/src/components/DragAndDrop/Provider/index.js
+++ b/src/components/DragAndDrop/Provider/index.js
@@ -22,7 +22,7 @@ function shouldAcceptDrop(event) {
     return _.some(event.dataTransfer.types, (type) => type === 'Files');
 }
 
-function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled = false}) {
+function DragAndDropProvider({children, dropZoneID, isDisabled = false}) {
     const DragAndDropContext = DNDUtils.getDragAndDropContext(dropZoneID, true);
     useEffect(
         () => () => {
@@ -129,9 +129,9 @@ function DragAndDropProvider({children, dropZoneID, dropZoneHostName, isDisabled
             >
                 <View
                     style={[styles.fullScreen, styles.invisibleOverlay]}
-                    nativeID={dropZoneHostName}
+                    nativeID={dropZoneID}
                 >
-                    <PortalHost name={dropZoneHostName} />
+                    <PortalHost name={dropZoneID} />
                 </View>
                 {children}
             </View>
diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js
index 4bea13035ae5..829629fbc106 100644
--- a/src/pages/home/ReportScreen.js
+++ b/src/pages/home/ReportScreen.js
@@ -137,9 +137,6 @@ class ReportScreen extends React.Component {
 
         this.flatListRef = React.createRef();
         this.reactionListRef = React.createRef();
-
-        // We need unique ID for drag and drop to work properly with stack navigator.
-        this.dragAndDropID = CONST.REPORT.DROP_NATIVE_ID + ReportUtils.generateReportID();
     }
 
     componentDidMount() {
@@ -350,8 +347,7 @@ class ReportScreen extends React.Component {
                             />
                         )}
                         <DragAndDropProvider
-                            dropZoneID={this.dragAndDropID}
-                            dropZoneHostName={CONST.REPORT.DROP_HOST_NAME}
+                            dropZoneID={CONST.REPORT.DROP_ZONE_ID}
                             isDisabled={!this.isReportReadyForDisplay()}
                         >
                             <View
@@ -399,7 +395,6 @@ class ReportScreen extends React.Component {
                                             isComposerFullSize={this.props.isComposerFullSize}
                                             onSubmitComment={this.onSubmitComment}
                                             policies={this.props.policies}
-                                            dragAndDropID={this.dragAndDropID}
                                         />
                                     </>
                                 )}
@@ -408,7 +403,6 @@ class ReportScreen extends React.Component {
                                     <ReportFooter
                                         shouldDisableCompose
                                         isOffline={this.props.network.isOffline}
-                                        dragAndDropID={this.dragAndDropID}
                                     />
                                 )}
                             </View>
diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js
index 84c512249812..f5affeb60882 100644
--- a/src/pages/home/report/ReportActionCompose.js
+++ b/src/pages/home/report/ReportActionCompose.js
@@ -117,9 +117,6 @@ const propTypes = {
     /** The type of action that's pending  */
     pendingAction: PropTypes.oneOf(['add', 'update', 'delete']),
 
-    /** Unique id for nativeId in DragAndDrop */
-    dragAndDropID: PropTypes.string.isRequired,
-
     ...windowDimensionsPropTypes,
     ...withLocalizePropTypes,
     ...withCurrentUserPersonalDetailsPropTypes,
@@ -1161,7 +1158,6 @@ class ReportActionCompose extends React.Component {
                                         />
                                     </View>
                                     <ReportDropUI
-                                        dropZoneID={this.props.dragAndDropID}
                                         onDrop={(e) => {
                                             if (this.state.isAttachmentPreviewActive) {
                                                 return;
diff --git a/src/pages/home/report/ReportDropUI.js b/src/pages/home/report/ReportDropUI.js
index f9edf5577d8b..201ecadbc6e2 100644
--- a/src/pages/home/report/ReportDropUI.js
+++ b/src/pages/home/report/ReportDropUI.js
@@ -10,19 +10,15 @@ import * as Expensicons from '../../../components/Icon/Expensicons';
 import useLocalize from '../../../hooks/useLocalize';
 
 const propTypes = {
-    /** DropZoneID for the current report. */
-    dropZoneID: PropTypes.string.isRequired,
-
     /** Callback to execute when a file is dropped. */
     onDrop: PropTypes.func.isRequired,
 };
 
-function ReportDropUI({dropZoneID, onDrop}) {
+function ReportDropUI({onDrop}) {
     const {translate} = useLocalize();
     return (
         <DragAndDropConsumer
-            dropZoneID={dropZoneID}
-            dropZoneHostName={CONST.REPORT.DROP_HOST_NAME}
+            dropZoneID={CONST.REPORT.DROP_ZONE_ID}
             onDrop={onDrop}
         >
             <View style={[styles.reportDropOverlay, styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]}>
diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js
index 471e47cb2b0a..c03e47dd531e 100644
--- a/src/pages/home/report/ReportFooter.js
+++ b/src/pages/home/report/ReportFooter.js
@@ -44,9 +44,6 @@ const propTypes = {
     /** Whether user interactions should be disabled */
     shouldDisableCompose: PropTypes.bool,
 
-    /** Unique id for nativeId in DragAndDrop */
-    dragAndDropID: PropTypes.string.isRequired,
-
     ...windowDimensionsPropTypes,
 };
 
@@ -95,7 +92,6 @@ function ReportFooter(props) {
                             pendingAction={props.pendingAction}
                             isComposerFullSize={props.isComposerFullSize}
                             disabled={props.shouldDisableCompose}
-                            dragAndDropID={props.dragAndDropID}
                         />
                     </SwipeableView>
                 </View>
diff --git a/src/stories/DragAndDrop.stories.js b/src/stories/DragAndDrop.stories.js
index ef5b4fbdf1de..e57c349a6fe5 100644
--- a/src/stories/DragAndDrop.stories.js
+++ b/src/stories/DragAndDrop.stories.js
@@ -17,7 +17,6 @@ const story = {
 };
 
 const DROP_ZONE_ID = 'dropID';
-const DROP_ZONE_HOST_NAME = 'dropZoneHostName';
 
 function Default() {
     const [fileURL, setFileURL] = useState('');
@@ -33,10 +32,7 @@ function Default() {
                 styles.justifyContentCenter,
             ]}
         >
-            <DragAndDropProvider
-                dropZoneID={DROP_ZONE_ID}
-                dropZoneHostName={DROP_ZONE_HOST_NAME}
-            >
+            <DragAndDropProvider dropZoneID={DROP_ZONE_ID}>
                 <View style={[styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]}>
                     {fileURL ? (
                         <Image
@@ -52,7 +48,6 @@ function Default() {
                 </View>
                 <DragAndDropConsumer
                     dropZoneID={DROP_ZONE_ID}
-                    dropZoneHostName={DROP_ZONE_HOST_NAME}
                     onDrop={(e) => {
                         const file = lodashGet(e, ['dataTransfer', 'files', 0]);
                         if (file && file.type.includes('image')) {

From da885c4d85941d631536f5120c35ab5b34923016 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 13:48:29 -0700
Subject: [PATCH 22/37] Create useDragAndDrop hook to DRY things up

---
 src/components/DragAndDrop/Provider/index.js | 105 ++---------------
 src/hooks/useDragAndDrop.js                  | 117 +++++++++++++++++++
 2 files changed, 126 insertions(+), 96 deletions(-)
 create mode 100644 src/hooks/useDragAndDrop.js

diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js
index 40b2b17d021a..850c45342a27 100644
--- a/src/components/DragAndDrop/Provider/index.js
+++ b/src/components/DragAndDrop/Provider/index.js
@@ -1,18 +1,11 @@
 import _ from 'underscore';
-import React, {useCallback, useEffect, useRef, useState} from 'react';
+import React, {useEffect, useRef} from 'react';
 import {View} from 'react-native';
-import {useIsFocused} from '@react-navigation/native';
 import {PortalHost} from '@gorhom/portal';
 import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes';
 import DNDUtils from '../Utils';
 import styles from '../../../styles/styles';
-
-const COPY_DROP_EFFECT = 'copy';
-const NONE_DROP_EFFECT = 'none';
-const DRAG_ENTER_EVENT = 'dragenter';
-const DRAG_OVER_EVENT = 'dragover';
-const DRAG_LEAVE_EVENT = 'dragleave';
-const DROP_EVENT = 'drop';
+import useDragAndDrop from '../../../hooks/useDragAndDrop';
 
 /**
  * @param {Event} event – drag event
@@ -31,95 +24,15 @@ function DragAndDropProvider({children, dropZoneID, isDisabled = false}) {
         [dropZoneID],
     );
 
-    const isFocused = useIsFocused();
-
     const dropZone = useRef(null);
-    const dragCounter = useRef(0);
-
-    const [isDraggingOver, setIsDraggingOver] = useState(false);
-
-    // If this component is out of focus or disabled, reset the drag state back to the default
-    useEffect(() => {
-        if (isFocused && !isDisabled) {
-            return;
-        }
-        dragCounter.current = 0;
-        setIsDraggingOver(false);
-    }, [isFocused, isDisabled]);
-
-    /**
-     * Handles all types of drag-N-drop events on the drop zone associated with composer
-     *
-     * @param {Object} event native Event
-     */
-    const dropZoneDragHandler = useCallback(
-        (event) => {
-            if (!isFocused || isDisabled) {
-                return;
-            }
-
-            event.preventDefault();
-
-            if (shouldAcceptDrop(event)) {
-                switch (event.type) {
-                    case DRAG_OVER_EVENT:
-                        // Nothing needed here, just needed to preventDefault in order for the drop event to fire later
-                        break;
-                    case DRAG_ENTER_EVENT:
-                        dragCounter.current++;
-                        if (isDraggingOver) {
-                            return;
-                        }
-                        // eslint-disable-next-line no-param-reassign
-                        event.dataTransfer.dropEffect = COPY_DROP_EFFECT;
-                        setIsDraggingOver(true);
-                        break;
-                    case DRAG_LEAVE_EVENT:
-                        dragCounter.current--;
-                        if (!isDraggingOver || dragCounter.current > 0) {
-                            return;
-                        }
-
-                        setIsDraggingOver(false);
-                        break;
-                    case DROP_EVENT:
-                        dragCounter.current = 0;
-                        setIsDraggingOver(false);
-                        DNDUtils.executeOnDropCallbacks(event, dropZoneID);
-                        break;
-                    default:
-                        break;
-                }
-            } else {
-                // eslint-disable-next-line no-param-reassign
-                event.dataTransfer.dropEffect = NONE_DROP_EFFECT;
-            }
+    const {isDraggingOver} = useDragAndDrop({
+        dropZoneElement: dropZone.current,
+        onDrop: (event) => {
+            DNDUtils.executeOnDropCallbacks(event, dropZoneID);
         },
-        [isFocused, isDisabled, isDraggingOver, dropZoneID],
-    );
-
-    useEffect(() => {
-        if (!dropZone.current) {
-            return;
-        }
-
-        // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired:
-        // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome
-        dropZone.current.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
-        dropZone.current.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
-        dropZone.current.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
-        dropZone.current.addEventListener(DROP_EVENT, dropZoneDragHandler);
-        return () => {
-            if (!dropZone.current) {
-                return;
-            }
-
-            dropZone.current.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
-            dropZone.current.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
-            dropZone.current.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
-            dropZone.current.removeEventListener(DROP_EVENT, dropZoneDragHandler);
-        };
-    }, [dropZoneDragHandler]);
+        shouldAcceptDrop,
+        isDisabled,
+    });
 
     return (
         <DragAndDropContext.Provider value={{isDraggingOver}}>
diff --git a/src/hooks/useDragAndDrop.js b/src/hooks/useDragAndDrop.js
new file mode 100644
index 000000000000..50179a6fe885
--- /dev/null
+++ b/src/hooks/useDragAndDrop.js
@@ -0,0 +1,117 @@
+import _ from 'underscore';
+import {useEffect, useRef, useState, useCallback} from 'react';
+import {useIsFocused} from '@react-navigation/native';
+
+const COPY_DROP_EFFECT = 'copy';
+const NONE_DROP_EFFECT = 'none';
+const DRAG_ENTER_EVENT = 'dragenter';
+const DRAG_OVER_EVENT = 'dragover';
+const DRAG_LEAVE_EVENT = 'dragleave';
+const DROP_EVENT = 'drop';
+
+export default function useDragAndDrop({dropZoneElement, onDrop = () => {}, shouldAllowDrop = true, isDisabled = false, shouldAcceptDrop = () => true}) {
+    const isFocused = useIsFocused();
+    const [isDraggingOver, setIsDraggingOver] = useState(false);
+
+    // This solution is borrowed from this SO: https://stackoverflow.com/questions/7110353/html5-dragleave-fired-when-hovering-a-child-element
+    // This is necessary because dragging over children will cause dragleave to execute on the parent.
+    // You can think of this counter as a stack. When a child is hovered over we push an element onto the stack.
+    // Then we only process the dragleave event if the count is 0, because it means that the last element (the parent) has been popped off the stack.
+    const dragCounter = useRef(0);
+
+    // If this component is out of focus or disabled, reset the drag state back to the default
+    useEffect(() => {
+        if (isFocused && !isDisabled) {
+            return;
+        }
+        dragCounter.current = 0;
+        setIsDraggingOver(false);
+    }, [isFocused, isDisabled]);
+
+    const onDropRef = useRef(onDrop);
+    onDropRef.current = onDrop;
+    const onDropCallback = useCallback((event) => {
+        if (!_.isFunction(onDropRef.current)) {
+            return;
+        }
+        onDropRef.current(event);
+    }, []);
+
+    const setDropEffect = useCallback(
+        (event) => {
+            // eslint-disable-next-line no-param-reassign
+            event.dataTransfer.dropEffect = shouldAllowDrop && shouldAcceptDrop(event) ? COPY_DROP_EFFECT : NONE_DROP_EFFECT;
+        },
+        [shouldAllowDrop, shouldAcceptDrop],
+    );
+
+    /**
+     * Handles all types of drag-N-drop events on the drop zone associated with composer
+     *
+     * @param {Object} event native Event
+     */
+    const dropZoneDragHandler = useCallback(
+        (event) => {
+            if (!isFocused || isDisabled) {
+                return;
+            }
+
+            event.preventDefault();
+
+            switch (event.type) {
+                case DRAG_OVER_EVENT:
+                    setDropEffect(event);
+                    break;
+                case DRAG_ENTER_EVENT:
+                    dragCounter.current++;
+                    setDropEffect(event);
+                    if (isDraggingOver) {
+                        return;
+                    }
+                    setIsDraggingOver(true);
+                    break;
+                case DRAG_LEAVE_EVENT:
+                    dragCounter.current--;
+                    if (!isDraggingOver || dragCounter.current > 0) {
+                        return;
+                    }
+
+                    setIsDraggingOver(false);
+                    break;
+                case DROP_EVENT:
+                    dragCounter.current = 0;
+                    setIsDraggingOver(false);
+                    onDropCallback(event);
+                    break;
+                default:
+                    break;
+            }
+        },
+        [isFocused, isDisabled, setDropEffect, isDraggingOver, onDropCallback],
+    );
+
+    useEffect(() => {
+        if (!dropZoneElement) {
+            return;
+        }
+
+        // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired:
+        // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome
+        dropZoneElement.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
+        dropZoneElement.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
+        dropZoneElement.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
+        dropZoneElement.addEventListener(DROP_EVENT, dropZoneDragHandler);
+        return () => {
+            if (!dropZoneElement) {
+                return;
+            }
+
+            dropZoneElement.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
+            dropZoneElement.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
+            dropZoneElement.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
+            dropZoneElement.removeEventListener(DROP_EVENT, dropZoneDragHandler);
+        };
+    }, [dropZoneElement, dropZoneDragHandler]);
+
+    return {isDraggingOver};
+}

From ebfb46ce2eecadb0eade20ebe30bd9fc504c4540 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 13:49:44 -0700
Subject: [PATCH 23/37] Add missing JSDoc

---
 src/hooks/useDragAndDrop.js | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/hooks/useDragAndDrop.js b/src/hooks/useDragAndDrop.js
index 50179a6fe885..01777347b3c8 100644
--- a/src/hooks/useDragAndDrop.js
+++ b/src/hooks/useDragAndDrop.js
@@ -9,6 +9,14 @@ const DRAG_OVER_EVENT = 'dragover';
 const DRAG_LEAVE_EVENT = 'dragleave';
 const DROP_EVENT = 'drop';
 
+/**
+ * @param {Element} dropZoneElement
+ * @param {Function} [onDrop]
+ * @param {Boolean} [shouldAllowDrop]
+ * @param {Boolean} [isDisabled]
+ * @param {Function} [shouldAcceptDrop]
+ * @returns {{isDraggingOver: Boolean}}
+ */
 export default function useDragAndDrop({dropZoneElement, onDrop = () => {}, shouldAllowDrop = true, isDisabled = false, shouldAcceptDrop = () => true}) {
     const isFocused = useIsFocused();
     const [isDraggingOver, setIsDraggingOver] = useState(false);

From d510e1eb5a1ca8c96741981bc7e52c369469d8bb Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 14:18:21 -0700
Subject: [PATCH 24/37] useDragAndDrop in NoDropZone

---
 .../DragAndDrop/NoDropZone/index.js           | 37 +++++++++----------
 src/hooks/useDragAndDrop.js                   |  5 ++-
 2 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/src/components/DragAndDrop/NoDropZone/index.js b/src/components/DragAndDrop/NoDropZone/index.js
index f562796ce09a..22d36199ed18 100644
--- a/src/components/DragAndDrop/NoDropZone/index.js
+++ b/src/components/DragAndDrop/NoDropZone/index.js
@@ -1,29 +1,28 @@
-import {useEffect} from 'react';
+import React, {useRef} from 'react';
+import {View} from 'react-native';
 import PropTypes from 'prop-types';
+import styles from '../../../styles/styles';
+import useDragAndDrop from '../../../hooks/useDragAndDrop';
 
 const propTypes = {
     /** Content */
     children: PropTypes.node.isRequired,
 };
 
-function NoDropZone(props) {
-    function handleDrag(event) {
-        event.preventDefault();
-        // eslint-disable-next-line no-param-reassign
-        event.dataTransfer.dropEffect = 'none';
-    }
-
-    useEffect(() => {
-        document.addEventListener('dragenter', handleDrag);
-        document.addEventListener('dragover', handleDrag);
-
-        return () => {
-            document.removeEventListener('dragenter', handleDrag);
-            document.removeEventListener('dragover', handleDrag);
-        };
-    }, []);
-
-    return props.children;
+function NoDropZone({children}) {
+    const noDropZone = useRef(null);
+    useDragAndDrop({
+        dropZoneElement: noDropZone.current,
+        shouldAllowDrop: false,
+    });
+    return (
+        <View
+            ref={(e) => (noDropZone.current = e)}
+            style={[styles.fullScreen]}
+        >
+            {children}
+        </View>
+    );
 }
 
 NoDropZone.displayName = 'NoDropZone';
diff --git a/src/hooks/useDragAndDrop.js b/src/hooks/useDragAndDrop.js
index 01777347b3c8..3459a7985e20 100644
--- a/src/hooks/useDragAndDrop.js
+++ b/src/hooks/useDragAndDrop.js
@@ -47,8 +47,11 @@ export default function useDragAndDrop({dropZoneElement, onDrop = () => {}, shou
 
     const setDropEffect = useCallback(
         (event) => {
+            const effect = shouldAllowDrop && shouldAcceptDrop(event) ? COPY_DROP_EFFECT : NONE_DROP_EFFECT;
             // eslint-disable-next-line no-param-reassign
-            event.dataTransfer.dropEffect = shouldAllowDrop && shouldAcceptDrop(event) ? COPY_DROP_EFFECT : NONE_DROP_EFFECT;
+            event.dataTransfer.dropEffect = effect;
+            // eslint-disable-next-line no-param-reassign
+            event.dataTransfer.effectAllowed = effect;
         },
         [shouldAllowDrop, shouldAcceptDrop],
     );

From d140024fff4ecfaedd75d811a9447d1ddf7a1de8 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 15:21:27 -0700
Subject: [PATCH 25/37] Fix ref inconsistencies which were making listeners get
 unsubscribed

---
 .../DragAndDrop/NoDropZone/index.js           |  2 +-
 src/components/DragAndDrop/Provider/index.js  |  2 +-
 src/hooks/useDragAndDrop.js                   | 26 +++++++++----------
 3 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/src/components/DragAndDrop/NoDropZone/index.js b/src/components/DragAndDrop/NoDropZone/index.js
index 22d36199ed18..5573136a3bc7 100644
--- a/src/components/DragAndDrop/NoDropZone/index.js
+++ b/src/components/DragAndDrop/NoDropZone/index.js
@@ -12,7 +12,7 @@ const propTypes = {
 function NoDropZone({children}) {
     const noDropZone = useRef(null);
     useDragAndDrop({
-        dropZoneElement: noDropZone.current,
+        dropZone: noDropZone,
         shouldAllowDrop: false,
     });
     return (
diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js
index 850c45342a27..ee2f4a797f76 100644
--- a/src/components/DragAndDrop/Provider/index.js
+++ b/src/components/DragAndDrop/Provider/index.js
@@ -26,7 +26,7 @@ function DragAndDropProvider({children, dropZoneID, isDisabled = false}) {
 
     const dropZone = useRef(null);
     const {isDraggingOver} = useDragAndDrop({
-        dropZoneElement: dropZone.current,
+        dropZone,
         onDrop: (event) => {
             DNDUtils.executeOnDropCallbacks(event, dropZoneID);
         },
diff --git a/src/hooks/useDragAndDrop.js b/src/hooks/useDragAndDrop.js
index 3459a7985e20..323f230cc66f 100644
--- a/src/hooks/useDragAndDrop.js
+++ b/src/hooks/useDragAndDrop.js
@@ -10,14 +10,14 @@ const DRAG_LEAVE_EVENT = 'dragleave';
 const DROP_EVENT = 'drop';
 
 /**
- * @param {Element} dropZoneElement
+ * @param {Object} dropZone – ref to the dropZone component
  * @param {Function} [onDrop]
  * @param {Boolean} [shouldAllowDrop]
  * @param {Boolean} [isDisabled]
  * @param {Function} [shouldAcceptDrop]
  * @returns {{isDraggingOver: Boolean}}
  */
-export default function useDragAndDrop({dropZoneElement, onDrop = () => {}, shouldAllowDrop = true, isDisabled = false, shouldAcceptDrop = () => true}) {
+export default function useDragAndDrop({dropZone, onDrop = () => {}, shouldAllowDrop = true, isDisabled = false, shouldAcceptDrop = () => true}) {
     const isFocused = useIsFocused();
     const [isDraggingOver, setIsDraggingOver] = useState(false);
 
@@ -102,27 +102,27 @@ export default function useDragAndDrop({dropZoneElement, onDrop = () => {}, shou
     );
 
     useEffect(() => {
-        if (!dropZoneElement) {
+        if (!dropZone.current) {
             return;
         }
 
         // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired:
         // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome
-        dropZoneElement.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
-        dropZoneElement.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
-        dropZoneElement.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
-        dropZoneElement.addEventListener(DROP_EVENT, dropZoneDragHandler);
+        dropZone.current.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
+        dropZone.current.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
+        dropZone.current.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
+        dropZone.current.addEventListener(DROP_EVENT, dropZoneDragHandler);
         return () => {
-            if (!dropZoneElement) {
+            if (!dropZone.current) {
                 return;
             }
 
-            dropZoneElement.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
-            dropZoneElement.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
-            dropZoneElement.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
-            dropZoneElement.removeEventListener(DROP_EVENT, dropZoneDragHandler);
+            dropZone.current.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
+            dropZone.current.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
+            dropZone.current.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
+            dropZone.current.removeEventListener(DROP_EVENT, dropZoneDragHandler);
         };
-    }, [dropZoneElement, dropZoneDragHandler]);
+    }, [dropZone, dropZoneDragHandler]);
 
     return {isDraggingOver};
 }

From be377dbdf33708160a6b67e0c5ab6a4196b5d555 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 15:34:15 -0700
Subject: [PATCH 26/37] Fix click events in the drop zone

---
 src/components/DragAndDrop/Provider/index.js | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js
index ee2f4a797f76..fa710000b4d6 100644
--- a/src/components/DragAndDrop/Provider/index.js
+++ b/src/components/DragAndDrop/Provider/index.js
@@ -40,12 +40,14 @@ function DragAndDropProvider({children, dropZoneID, isDisabled = false}) {
                 ref={(e) => (dropZone.current = e)}
                 style={[styles.flex1, styles.w100, styles.h100]}
             >
-                <View
-                    style={[styles.fullScreen, styles.invisibleOverlay]}
-                    nativeID={dropZoneID}
-                >
-                    <PortalHost name={dropZoneID} />
-                </View>
+                {isDraggingOver && (
+                    <View
+                        style={[styles.fullScreen, styles.invisibleOverlay]}
+                        nativeID={dropZoneID}
+                    >
+                        <PortalHost name={dropZoneID} />
+                    </View>
+                )}
                 {children}
             </View>
         </DragAndDropContext.Provider>

From f0685c3b594da20fa86c02cbac343cfb1aa55011 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 15:39:01 -0700
Subject: [PATCH 27/37] Fix lint

---
 src/hooks/useDragAndDrop.js | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

diff --git a/src/hooks/useDragAndDrop.js b/src/hooks/useDragAndDrop.js
index 323f230cc66f..a8744e89199c 100644
--- a/src/hooks/useDragAndDrop.js
+++ b/src/hooks/useDragAndDrop.js
@@ -106,21 +106,23 @@ export default function useDragAndDrop({dropZone, onDrop = () => {}, shouldAllow
             return;
         }
 
+        const dropZoneRef = dropZone.current;
+
         // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired:
         // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome
-        dropZone.current.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
-        dropZone.current.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
-        dropZone.current.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
-        dropZone.current.addEventListener(DROP_EVENT, dropZoneDragHandler);
+        dropZoneRef.addEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
+        dropZoneRef.addEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
+        dropZoneRef.addEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
+        dropZoneRef.addEventListener(DROP_EVENT, dropZoneDragHandler);
         return () => {
-            if (!dropZone.current) {
+            if (!dropZoneRef) {
                 return;
             }
 
-            dropZone.current.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
-            dropZone.current.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
-            dropZone.current.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
-            dropZone.current.removeEventListener(DROP_EVENT, dropZoneDragHandler);
+            dropZoneRef.removeEventListener(DRAG_OVER_EVENT, dropZoneDragHandler);
+            dropZoneRef.removeEventListener(DRAG_ENTER_EVENT, dropZoneDragHandler);
+            dropZoneRef.removeEventListener(DRAG_LEAVE_EVENT, dropZoneDragHandler);
+            dropZoneRef.removeEventListener(DROP_EVENT, dropZoneDragHandler);
         };
     }, [dropZone, dropZoneDragHandler]);
 

From 4373de1518ab9ded86596152f21399571ba58074 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 16:03:28 -0700
Subject: [PATCH 28/37] Fix tests

---
 __mocks__/@react-navigation/native/index.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/__mocks__/@react-navigation/native/index.js b/__mocks__/@react-navigation/native/index.js
index 7d26028217af..2f00886c30e5 100644
--- a/__mocks__/@react-navigation/native/index.js
+++ b/__mocks__/@react-navigation/native/index.js
@@ -1,6 +1,6 @@
-function useIsFocused() {
-    return true;
-}
+import {useIsFocused as realUseIsFocused} from '@react-navigation/native';
+
+const useIsFocused = process.env.NODE_ENV === 'test' ? realUseIsFocused : () => true;
 
 export * from '@react-navigation/core';
 export * from '@react-navigation/native';

From 8a4dcf5b7a383c364c28c5df1ecd2104279c0e3e Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 16:17:32 -0700
Subject: [PATCH 29/37] Add comment in mock

---
 __mocks__/@react-navigation/native/index.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/__mocks__/@react-navigation/native/index.js b/__mocks__/@react-navigation/native/index.js
index 2f00886c30e5..09abd0d02bf9 100644
--- a/__mocks__/@react-navigation/native/index.js
+++ b/__mocks__/@react-navigation/native/index.js
@@ -1,5 +1,6 @@
 import {useIsFocused as realUseIsFocused} from '@react-navigation/native';
 
+// We only want this mocked for storybook, not jest
 const useIsFocused = process.env.NODE_ENV === 'test' ? realUseIsFocused : () => true;
 
 export * from '@react-navigation/core';

From 834791ea06b1c82677bcedaa9555fbd8af0b56f4 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 16:19:25 -0700
Subject: [PATCH 30/37] Rename DNDUtils to DragAndDropUtils

---
 src/components/DragAndDrop/Consumer/index.js | 8 ++++----
 src/components/DragAndDrop/Provider/index.js | 8 ++++----
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js
index 886d10b37fc7..b1dee44e34e0 100644
--- a/src/components/DragAndDrop/Consumer/index.js
+++ b/src/components/DragAndDrop/Consumer/index.js
@@ -1,10 +1,10 @@
 import React, {useContext, useEffect, useRef} from 'react';
 import {Portal} from '@gorhom/portal';
 import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes';
-import DNDUtils from '../Utils';
+import DragAndDropUtils from '../Utils';
 
 function DragAndDropConsumer({children, dropZoneID, onDrop}) {
-    const DragAndDropContext = DNDUtils.getDragAndDropContext(dropZoneID);
+    const DragAndDropContext = DragAndDropUtils.getDragAndDropContext(dropZoneID);
     const {isDraggingOver} = useContext(DragAndDropContext);
 
     const onDropRef = useRef(onDrop);
@@ -18,8 +18,8 @@ function DragAndDropConsumer({children, dropZoneID, onDrop}) {
             }
             onDropRef.current(e);
         };
-        DNDUtils.registerOnDropCallback(dropZoneID, onDropCallback);
-        return () => DNDUtils.deregisterOnDropCallback(dropZoneID, onDropCallback);
+        DragAndDropUtils.registerOnDropCallback(dropZoneID, onDropCallback);
+        return () => DragAndDropUtils.deregisterOnDropCallback(dropZoneID, onDropCallback);
     }, [dropZoneID]);
 
     if (!isDraggingOver) {
diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js
index fa710000b4d6..d8ca80a17d3d 100644
--- a/src/components/DragAndDrop/Provider/index.js
+++ b/src/components/DragAndDrop/Provider/index.js
@@ -3,7 +3,7 @@ import React, {useEffect, useRef} from 'react';
 import {View} from 'react-native';
 import {PortalHost} from '@gorhom/portal';
 import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes';
-import DNDUtils from '../Utils';
+import DragAndDropUtils from '../Utils';
 import styles from '../../../styles/styles';
 import useDragAndDrop from '../../../hooks/useDragAndDrop';
 
@@ -16,10 +16,10 @@ function shouldAcceptDrop(event) {
 }
 
 function DragAndDropProvider({children, dropZoneID, isDisabled = false}) {
-    const DragAndDropContext = DNDUtils.getDragAndDropContext(dropZoneID, true);
+    const DragAndDropContext = DragAndDropUtils.getDragAndDropContext(dropZoneID, true);
     useEffect(
         () => () => {
-            DNDUtils.deleteDragAndDropContext(dropZoneID);
+            DragAndDropUtils.deleteDragAndDropContext(dropZoneID);
         },
         [dropZoneID],
     );
@@ -28,7 +28,7 @@ function DragAndDropProvider({children, dropZoneID, isDisabled = false}) {
     const {isDraggingOver} = useDragAndDrop({
         dropZone,
         onDrop: (event) => {
-            DNDUtils.executeOnDropCallbacks(event, dropZoneID);
+            DragAndDropUtils.executeOnDropCallbacks(event, dropZoneID);
         },
         shouldAcceptDrop,
         isDisabled,

From 578c22b43a8fbee1d1942f769ea22f71c6353941 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 17:05:51 -0700
Subject: [PATCH 31/37] Remove subscriber optimization

---
 src/components/DragAndDrop/Consumer/index.js | 16 +++-------------
 1 file changed, 3 insertions(+), 13 deletions(-)

diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js
index b1dee44e34e0..2cd9d11820d0 100644
--- a/src/components/DragAndDrop/Consumer/index.js
+++ b/src/components/DragAndDrop/Consumer/index.js
@@ -7,20 +7,10 @@ function DragAndDropConsumer({children, dropZoneID, onDrop}) {
     const DragAndDropContext = DragAndDropUtils.getDragAndDropContext(dropZoneID);
     const {isDraggingOver} = useContext(DragAndDropContext);
 
-    const onDropRef = useRef(onDrop);
-    onDropRef.current = onDrop;
     useEffect(() => {
-        // Internal function ensures that we only register the onDrop listener once for this consumer,
-        // even if the onDrop function passed in changes
-        const onDropCallback = (e) => {
-            if (!onDropRef.current) {
-                return;
-            }
-            onDropRef.current(e);
-        };
-        DragAndDropUtils.registerOnDropCallback(dropZoneID, onDropCallback);
-        return () => DragAndDropUtils.deregisterOnDropCallback(dropZoneID, onDropCallback);
-    }, [dropZoneID]);
+        DragAndDropUtils.registerOnDropCallback(dropZoneID, onDrop);
+        return () => DragAndDropUtils.deregisterOnDropCallback(dropZoneID, onDrop);
+    }, [dropZoneID, onDrop]);
 
     if (!isDraggingOver) {
         return null;

From ca842680093e056b09eb789de5c1cd7e71965de8 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 17:09:44 -0700
Subject: [PATCH 32/37] Fix lint

---
 src/components/DragAndDrop/Consumer/index.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js
index 2cd9d11820d0..2a4161b92630 100644
--- a/src/components/DragAndDrop/Consumer/index.js
+++ b/src/components/DragAndDrop/Consumer/index.js
@@ -1,4 +1,4 @@
-import React, {useContext, useEffect, useRef} from 'react';
+import React, {useContext, useEffect} from 'react';
 import {Portal} from '@gorhom/portal';
 import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes';
 import DragAndDropUtils from '../Utils';

From 8a028bb91aaff4b46e742e5e29e88542b694db90 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 19:59:45 -0700
Subject: [PATCH 33/37] Simplify context implementation

---
 .../Consumer/dragAndDropConsumerPropTypes.js  |  3 --
 src/components/DragAndDrop/Consumer/index.js  | 12 +++---
 .../Provider/dragAndDropProviderPropTypes.js  |  3 --
 src/components/DragAndDrop/Provider/index.js  | 37 +++++++++----------
 .../DragAndDrop/Provider/index.native.js      |  3 ++
 src/hooks/useDragAndDrop.js                   | 14 +------
 6 files changed, 27 insertions(+), 45 deletions(-)

diff --git a/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js b/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js
index d039c766b10a..66f8ed996e2e 100644
--- a/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js
+++ b/src/components/DragAndDrop/Consumer/dragAndDropConsumerPropTypes.js
@@ -4,9 +4,6 @@ export default {
     /** Children to render inside this component. */
     children: PropTypes.node.isRequired,
 
-    /** String ID to identify the dropZone. It should match the dropZoneID of the associated provider. */
-    dropZoneID: PropTypes.string.isRequired,
-
     /** Function to execute when an item is dropped in the drop zone. */
     onDrop: PropTypes.func.isRequired,
 };
diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js
index 2a4161b92630..ce3ac92c7885 100644
--- a/src/components/DragAndDrop/Consumer/index.js
+++ b/src/components/DragAndDrop/Consumer/index.js
@@ -1,16 +1,14 @@
 import React, {useContext, useEffect} from 'react';
 import {Portal} from '@gorhom/portal';
 import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes';
-import DragAndDropUtils from '../Utils';
+import {DragAndDropContext} from '../Provider';
 
-function DragAndDropConsumer({children, dropZoneID, onDrop}) {
-    const DragAndDropContext = DragAndDropUtils.getDragAndDropContext(dropZoneID);
-    const {isDraggingOver} = useContext(DragAndDropContext);
+function DragAndDropConsumer({children, onDrop}) {
+    const {isDraggingOver, setOnDropListener, dropZoneID} = useContext(DragAndDropContext);
 
     useEffect(() => {
-        DragAndDropUtils.registerOnDropCallback(dropZoneID, onDrop);
-        return () => DragAndDropUtils.deregisterOnDropCallback(dropZoneID, onDrop);
-    }, [dropZoneID, onDrop]);
+        setOnDropListener(onDrop);
+    }, [onDrop, setOnDropListener]);
 
     if (!isDraggingOver) {
         return null;
diff --git a/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js b/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js
index e33a47474517..d9cc806e9012 100644
--- a/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js
+++ b/src/components/DragAndDrop/Provider/dragAndDropProviderPropTypes.js
@@ -4,9 +4,6 @@ export default {
     /** Children to render inside this component. */
     children: PropTypes.node.isRequired,
 
-    /** ID for the drop zone. */
-    dropZoneID: PropTypes.string.isRequired,
-
     /** Should this dropZone be disabled? */
     isDisabled: PropTypes.bool,
 };
diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js
index d8ca80a17d3d..18374593a3c7 100644
--- a/src/components/DragAndDrop/Provider/index.js
+++ b/src/components/DragAndDrop/Provider/index.js
@@ -1,12 +1,14 @@
 import _ from 'underscore';
-import React, {useEffect, useRef} from 'react';
+import React, {useRef, useCallback} from 'react';
 import {View} from 'react-native';
 import {PortalHost} from '@gorhom/portal';
+import Str from 'expensify-common/lib/str';
 import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes';
-import DragAndDropUtils from '../Utils';
 import styles from '../../../styles/styles';
 import useDragAndDrop from '../../../hooks/useDragAndDrop';
 
+const DragAndDropContext = React.createContext({});
+
 /**
  * @param {Event} event – drag event
  * @returns {Boolean}
@@ -15,37 +17,31 @@ function shouldAcceptDrop(event) {
     return _.some(event.dataTransfer.types, (type) => type === 'Files');
 }
 
-function DragAndDropProvider({children, dropZoneID, isDisabled = false}) {
-    const DragAndDropContext = DragAndDropUtils.getDragAndDropContext(dropZoneID, true);
-    useEffect(
-        () => () => {
-            DragAndDropUtils.deleteDragAndDropContext(dropZoneID);
-        },
-        [dropZoneID],
-    );
-
+function DragAndDropProvider({children, isDisabled = false}) {
     const dropZone = useRef(null);
+    const dropZoneID = useRef(Str.guid('drag-n-drop'));
+
+    const onDropListener = useRef(() => {});
+    const setOnDropListener = useCallback((callback) => {
+        onDropListener.current = callback;
+    }, []);
+
     const {isDraggingOver} = useDragAndDrop({
         dropZone,
-        onDrop: (event) => {
-            DragAndDropUtils.executeOnDropCallbacks(event, dropZoneID);
-        },
+        onDrop: onDropListener.current,
         shouldAcceptDrop,
         isDisabled,
     });
 
     return (
-        <DragAndDropContext.Provider value={{isDraggingOver}}>
+        <DragAndDropContext.Provider value={{isDraggingOver, setOnDropListener, dropZoneID: dropZoneID.current}}>
             <View
                 ref={(e) => (dropZone.current = e)}
                 style={[styles.flex1, styles.w100, styles.h100]}
             >
                 {isDraggingOver && (
-                    <View
-                        style={[styles.fullScreen, styles.invisibleOverlay]}
-                        nativeID={dropZoneID}
-                    >
-                        <PortalHost name={dropZoneID} />
+                    <View style={[styles.fullScreen, styles.invisibleOverlay]}>
+                        <PortalHost name={dropZoneID.current} />
                     </View>
                 )}
                 {children}
@@ -58,3 +54,4 @@ DragAndDropProvider.propTypes = dragAndDropProviderPropTypes;
 DragAndDropProvider.displayName = 'DragAndDropProvider';
 
 export default DragAndDropProvider;
+export {DragAndDropContext};
diff --git a/src/components/DragAndDrop/Provider/index.native.js b/src/components/DragAndDrop/Provider/index.native.js
index f2539e37dc8d..4dac66912531 100644
--- a/src/components/DragAndDrop/Provider/index.native.js
+++ b/src/components/DragAndDrop/Provider/index.native.js
@@ -1,5 +1,7 @@
 import dragAndDropProviderPropTypes from './dragAndDropProviderPropTypes';
 
+const DragAndDropContext = {};
+
 function DragAndDropProvider({children}) {
     return children;
 }
@@ -8,3 +10,4 @@ DragAndDropProvider.propTypes = dragAndDropProviderPropTypes;
 DragAndDropProvider.displayName = 'DragAndDropProvider';
 
 export default DragAndDropProvider;
+export {DragAndDropContext};
diff --git a/src/hooks/useDragAndDrop.js b/src/hooks/useDragAndDrop.js
index a8744e89199c..98df70085a72 100644
--- a/src/hooks/useDragAndDrop.js
+++ b/src/hooks/useDragAndDrop.js
@@ -1,4 +1,3 @@
-import _ from 'underscore';
 import {useEffect, useRef, useState, useCallback} from 'react';
 import {useIsFocused} from '@react-navigation/native';
 
@@ -36,15 +35,6 @@ export default function useDragAndDrop({dropZone, onDrop = () => {}, shouldAllow
         setIsDraggingOver(false);
     }, [isFocused, isDisabled]);
 
-    const onDropRef = useRef(onDrop);
-    onDropRef.current = onDrop;
-    const onDropCallback = useCallback((event) => {
-        if (!_.isFunction(onDropRef.current)) {
-            return;
-        }
-        onDropRef.current(event);
-    }, []);
-
     const setDropEffect = useCallback(
         (event) => {
             const effect = shouldAllowDrop && shouldAcceptDrop(event) ? COPY_DROP_EFFECT : NONE_DROP_EFFECT;
@@ -92,13 +82,13 @@ export default function useDragAndDrop({dropZone, onDrop = () => {}, shouldAllow
                 case DROP_EVENT:
                     dragCounter.current = 0;
                     setIsDraggingOver(false);
-                    onDropCallback(event);
+                    onDrop(event);
                     break;
                 default:
                     break;
             }
         },
-        [isFocused, isDisabled, setDropEffect, isDraggingOver, onDropCallback],
+        [isFocused, isDisabled, setDropEffect, isDraggingOver, onDrop],
     );
 
     useEffect(() => {

From 0e90a133ca340739bab1019e77f331a27616d60a Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 20:05:19 -0700
Subject: [PATCH 34/37] Remove unused DragAndDropUtils

---
 src/CONST.js                                  |  1 -
 src/components/DragAndDrop/Utils/index.js     | 71 -------------------
 .../DragAndDrop/Utils/index.native.js         |  7 --
 src/pages/home/ReportScreen.js                |  5 +-
 src/pages/home/report/ReportDropUI.js         |  5 +-
 src/stories/DragAndDrop.stories.js            |  5 +-
 6 files changed, 3 insertions(+), 91 deletions(-)
 delete mode 100644 src/components/DragAndDrop/Utils/index.js
 delete mode 100644 src/components/DragAndDrop/Utils/index.native.js

diff --git a/src/CONST.js b/src/CONST.js
index 82a66c53e716..18637d582c31 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -480,7 +480,6 @@ const CONST = {
         PERSONAL_DETAIL: 'personalDetail',
     },
     REPORT: {
-        DROP_ZONE_ID: 'report-dropzone',
         ACTIVE_DROP_NATIVE_ID: 'report-dropzone',
         MAXIMUM_PARTICIPANTS: 8,
         SPLIT_REPORTID: '-2',
diff --git a/src/components/DragAndDrop/Utils/index.js b/src/components/DragAndDrop/Utils/index.js
deleted file mode 100644
index cc9c34c5abbc..000000000000
--- a/src/components/DragAndDrop/Utils/index.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import {createContext} from 'react';
-
-const dragAndDropContexts = {};
-const onDropCallbacks = {};
-
-/**
- * @param {String} dropZoneID
- * @param {Boolean} [shouldCreateNewContext] – should this create a new context for the dropZoneID if one does not already exist?
- * @returns {Object}
- */
-function getDragAndDropContext(dropZoneID, shouldCreateNewContext = false) {
-    if (dropZoneID in dragAndDropContexts) {
-        return dragAndDropContexts[dropZoneID];
-    }
-
-    if (!shouldCreateNewContext) {
-        throw new Error(`Could not find DragAndDrop context with dropZoneID: ${dropZoneID}`);
-    }
-
-    dragAndDropContexts[dropZoneID] = createContext({});
-    return dragAndDropContexts[dropZoneID];
-}
-
-function deleteDragAndDropContext(dropZoneID) {
-    delete dragAndDropContexts[dropZoneID];
-}
-
-/**
- * @param {String} dropZoneID
- * @param {Function} callback
- */
-function registerOnDropCallback(dropZoneID, callback) {
-    if (!(dropZoneID in onDropCallbacks)) {
-        onDropCallbacks[dropZoneID] = [];
-    }
-    onDropCallbacks[dropZoneID].push(callback);
-}
-
-/**
- * @param {String} dropZoneID
- * @param {Function} callback
- */
-function deregisterOnDropCallback(dropZoneID, callback) {
-    if (!(dropZoneID in onDropCallbacks)) {
-        return;
-    }
-    const index = onDropCallbacks[dropZoneID].indexOf(callback);
-    if (index < 0) {
-        return;
-    }
-    onDropCallbacks[dropZoneID] = onDropCallbacks[dropZoneID].splice(index, 1);
-}
-
-/**
- * @param {Event} event – onDrop event
- * @param {String} dropZoneID – dropZoneID that triggered the event
- */
-function executeOnDropCallbacks(event, dropZoneID) {
-    if (!(dropZoneID in onDropCallbacks)) {
-        return;
-    }
-    onDropCallbacks[dropZoneID].forEach((callback) => callback(event));
-}
-
-export default {
-    getDragAndDropContext,
-    deleteDragAndDropContext,
-    registerOnDropCallback,
-    deregisterOnDropCallback,
-    executeOnDropCallbacks,
-};
diff --git a/src/components/DragAndDrop/Utils/index.native.js b/src/components/DragAndDrop/Utils/index.native.js
deleted file mode 100644
index 24ca8384d5c4..000000000000
--- a/src/components/DragAndDrop/Utils/index.native.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export default {
-    getDragAndDropContext: () => {},
-    deleteDragAndDropContext: () => {},
-    registerOnDropCallback: () => {},
-    deregisterOnDropCallback: () => {},
-    executeOnDropCallbacks: () => {},
-};
diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js
index 55d8822e4274..66c46da11306 100644
--- a/src/pages/home/ReportScreen.js
+++ b/src/pages/home/ReportScreen.js
@@ -347,10 +347,7 @@ class ReportScreen extends React.Component {
                                 shouldShowCloseButton
                             />
                         )}
-                        <DragAndDropProvider
-                            dropZoneID={CONST.REPORT.DROP_ZONE_ID}
-                            isDisabled={!this.isReportReadyForDisplay()}
-                        >
+                        <DragAndDropProvider isDisabled={!this.isReportReadyForDisplay()}>
                             <View
                                 style={[styles.flex1, styles.justifyContentEnd, styles.overflowHidden]}
                                 onLayout={(event) => {
diff --git a/src/pages/home/report/ReportDropUI.js b/src/pages/home/report/ReportDropUI.js
index 201ecadbc6e2..4df6a5958d9a 100644
--- a/src/pages/home/report/ReportDropUI.js
+++ b/src/pages/home/report/ReportDropUI.js
@@ -17,10 +17,7 @@ const propTypes = {
 function ReportDropUI({onDrop}) {
     const {translate} = useLocalize();
     return (
-        <DragAndDropConsumer
-            dropZoneID={CONST.REPORT.DROP_ZONE_ID}
-            onDrop={onDrop}
-        >
+        <DragAndDropConsumer onDrop={onDrop}>
             <View style={[styles.reportDropOverlay, styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]}>
                 <View style={styles.mb3}>
                     <Icon
diff --git a/src/stories/DragAndDrop.stories.js b/src/stories/DragAndDrop.stories.js
index e57c349a6fe5..eb9b6bbed6f2 100644
--- a/src/stories/DragAndDrop.stories.js
+++ b/src/stories/DragAndDrop.stories.js
@@ -16,8 +16,6 @@ const story = {
     component: DragAndDropConsumer,
 };
 
-const DROP_ZONE_ID = 'dropID';
-
 function Default() {
     const [fileURL, setFileURL] = useState('');
     return (
@@ -32,7 +30,7 @@ function Default() {
                 styles.justifyContentCenter,
             ]}
         >
-            <DragAndDropProvider dropZoneID={DROP_ZONE_ID}>
+            <DragAndDropProvider>
                 <View style={[styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]}>
                     {fileURL ? (
                         <Image
@@ -47,7 +45,6 @@ function Default() {
                     )}
                 </View>
                 <DragAndDropConsumer
-                    dropZoneID={DROP_ZONE_ID}
                     onDrop={(e) => {
                         const file = lodashGet(e, ['dataTransfer', 'files', 0]);
                         if (file && file.type.includes('image')) {

From 8972845569f3ddd8ece9a3453eea8b6d0f149335 Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 20:06:15 -0700
Subject: [PATCH 35/37] Remove unused constant

---
 src/CONST.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/CONST.js b/src/CONST.js
index 18637d582c31..06bc1873431f 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -480,7 +480,6 @@ const CONST = {
         PERSONAL_DETAIL: 'personalDetail',
     },
     REPORT: {
-        ACTIVE_DROP_NATIVE_ID: 'report-dropzone',
         MAXIMUM_PARTICIPANTS: 8,
         SPLIT_REPORTID: '-2',
         ACTIONS: {

From 8711b494de73bf6921ad6cdfa2722b95d933579a Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 20:06:43 -0700
Subject: [PATCH 36/37] fix lint

---
 src/pages/home/report/ReportDropUI.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/pages/home/report/ReportDropUI.js b/src/pages/home/report/ReportDropUI.js
index 4df6a5958d9a..d742346cd769 100644
--- a/src/pages/home/report/ReportDropUI.js
+++ b/src/pages/home/report/ReportDropUI.js
@@ -3,7 +3,6 @@ import {View} from 'react-native';
 import PropTypes from 'prop-types';
 import styles from '../../../styles/styles';
 import Text from '../../../components/Text';
-import CONST from '../../../CONST';
 import DragAndDropConsumer from '../../../components/DragAndDrop/Consumer';
 import Icon from '../../../components/Icon';
 import * as Expensicons from '../../../components/Icon/Expensicons';

From cce276f4c40ab9f66716b8d8b731684ed5c27eec Mon Sep 17 00:00:00 2001
From: rory <rory@expensify.com>
Date: Thu, 27 Jul 2023 20:10:05 -0700
Subject: [PATCH 37/37] onDropHandler not onDropListener

---
 src/components/DragAndDrop/Consumer/index.js |  6 +++---
 src/components/DragAndDrop/Provider/index.js | 10 +++++-----
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/components/DragAndDrop/Consumer/index.js b/src/components/DragAndDrop/Consumer/index.js
index ce3ac92c7885..cf21afe627f4 100644
--- a/src/components/DragAndDrop/Consumer/index.js
+++ b/src/components/DragAndDrop/Consumer/index.js
@@ -4,11 +4,11 @@ import dragAndDropConsumerPropTypes from './dragAndDropConsumerPropTypes';
 import {DragAndDropContext} from '../Provider';
 
 function DragAndDropConsumer({children, onDrop}) {
-    const {isDraggingOver, setOnDropListener, dropZoneID} = useContext(DragAndDropContext);
+    const {isDraggingOver, setOnDropHandler, dropZoneID} = useContext(DragAndDropContext);
 
     useEffect(() => {
-        setOnDropListener(onDrop);
-    }, [onDrop, setOnDropListener]);
+        setOnDropHandler(onDrop);
+    }, [onDrop, setOnDropHandler]);
 
     if (!isDraggingOver) {
         return null;
diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js
index 18374593a3c7..89b0f47a830d 100644
--- a/src/components/DragAndDrop/Provider/index.js
+++ b/src/components/DragAndDrop/Provider/index.js
@@ -21,20 +21,20 @@ function DragAndDropProvider({children, isDisabled = false}) {
     const dropZone = useRef(null);
     const dropZoneID = useRef(Str.guid('drag-n-drop'));
 
-    const onDropListener = useRef(() => {});
-    const setOnDropListener = useCallback((callback) => {
-        onDropListener.current = callback;
+    const onDropHandler = useRef(() => {});
+    const setOnDropHandler = useCallback((callback) => {
+        onDropHandler.current = callback;
     }, []);
 
     const {isDraggingOver} = useDragAndDrop({
         dropZone,
-        onDrop: onDropListener.current,
+        onDrop: onDropHandler.current,
         shouldAcceptDrop,
         isDisabled,
     });
 
     return (
-        <DragAndDropContext.Provider value={{isDraggingOver, setOnDropListener, dropZoneID: dropZoneID.current}}>
+        <DragAndDropContext.Provider value={{isDraggingOver, setOnDropHandler, dropZoneID: dropZoneID.current}}>
             <View
                 ref={(e) => (dropZone.current = e)}
                 style={[styles.flex1, styles.w100, styles.h100]}
