Repository: microsoft/fluent-ui-react (ts)

Issue:
DOM elements retained in `toRefObject`
## Bug Report

There is a weak map used in `toRefObject`
https://github.com/microsoft/fluent-ui-react/blob/99ad7d479105246bb1d8581ebab0c25a0a3ef96f/packages/react-component-ref/src/toRefObject.ts#L6

The problem is that nodes are used not only as weak keys but also as values - this retains them in memory forever.

Pull request:
chore(Ref): remove `toRefObject()` function
Fixes #2283.

# BREAKING CHANGES

`toRefObject()` was removed, to support previous behavior we added `target` prop support to `EventListener` and `useEventListener()` hook.

#### Before

```tsx
<EventListener targetRef={toRefObject(document)} />
```

### After

```tsx
<EventListener target={document} />
```

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56106e294..652b339ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
 - Restricted prop set in the `Button`, `Avatar`, `Box` and `Image` styles; changed `avatarBorderWidth` and `statusBorderWidth` avatar variables types from number to string and updated styles in Teams theme @mnajdova ([#2238](https://github.com/microsoft/fluent-ui-react/pull/2238))
 - Restricted prop set in the `List` & `ListItem` @layershifter ([#2238](https://github.com/microsoft/fluent-ui-react/pull/2238))
 - Remove `mountDocument` prop in `Popup` & `MenuButton` components @layershifter ([#2286](https://github.com/microsoft/fluent-ui-react/pull/2286))
+- Remove `toRefObject` function @layershifter ([#2287](https://github.com/microsoft/fluent-ui-react/pull/2287))
 
 ### Fixes
 - Fix styleParam to always be required in the styles functions @layershifter, @mnajdova ([#2235](https://github.com/microsoft/fluent-ui-react/pull/2235))
@@ -42,6 +43,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
 - Add `useStyles()` hook to use theming capabilities in custom components @layershifter, @mnajdova ([#2217](https://github.com/microsoft/fluent-ui-react/pull/2217))
 - Add optional wrapper function to `List` which can be used to inject custom scrollbars to `Dropdown` @jurokapsiar ([#2092](https://github.com/microsoft/fluent-ui-react/pull/2092))
 - Add `useTelemetry()` hook for adding telemetry information for the Fluent components and improve return types for the `useStyles` and `useStateManager` hooks @mnajdova ([#2257](https://github.com/microsoft/fluent-ui-react/pull/2257))
+- Add `target` prop to `EventListener` component and `useEventListener()` hook @layershifter ([#2287](https://github.com/microsoft/fluent-ui-react/pull/2287))
 
 ### Documentation
 - Add per-component performance charts @miroslavstastny ([#2240](https://github.com/microsoft/fluent-ui-react/pull/2240))
diff --git a/docs/src/prototypes/EditorToolbar/EditorToolbar.tsx b/docs/src/prototypes/EditorToolbar/EditorToolbar.tsx
index fa3722e67..af78414f3 100644
--- a/docs/src/prototypes/EditorToolbar/EditorToolbar.tsx
+++ b/docs/src/prototypes/EditorToolbar/EditorToolbar.tsx
@@ -16,7 +16,6 @@ import {
   ToolbarMenuItemShorthandKinds,
 } from '@fluentui/react'
 import { useEventListener } from '@fluentui/react-component-event-listener'
-import { toRefObject } from '@fluentui/react-component-ref'
 import * as keyboardKey from 'keyboard-key'
 import * as _ from 'lodash'
 import * as React from 'react'
@@ -243,7 +242,7 @@ const EditorToolbar: React.FC<EditorToolbarProps> = props => {
       }
     },
     type: 'keydown',
-    targetRef: toRefObject(props.target),
+    target: props.target,
   })
   useEventListener({
     listener: () => {
@@ -258,7 +257,7 @@ const EditorToolbar: React.FC<EditorToolbarProps> = props => {
       }
     },
     type: 'resize',
-    targetRef: toRefObject(props.target.defaultView),
+    target: props.target.defaultView,
   })
 
   return (
diff --git a/packages/react-component-event-listener/src/EventListener.ts b/packages/react-component-event-listener/src/EventListener.ts
index 48b318b09..b7a3dcb06 100644
--- a/packages/react-component-event-listener/src/EventListener.ts
+++ b/packages/react-component-event-listener/src/EventListener.ts
@@ -1,7 +1,7 @@
 import * as PropTypes from 'prop-types'
 
 import useEventListener from './useEventListener'
-import { EventListenerOptions, EventTypes, TargetRef } from './types'
+import { EventListenerOptions, EventTypes, Target, TargetRef } from './types'
 
 function EventListener<T extends EventTypes>(props: EventListenerOptions<T>) {
   useEventListener(props)
@@ -16,9 +16,10 @@ EventListener.propTypes =
     ? {
         capture: PropTypes.bool,
         listener: PropTypes.func.isRequired,
+        target: PropTypes.object as PropTypes.Validator<Target>,
         targetRef: PropTypes.shape({
           current: PropTypes.object,
-        }).isRequired as PropTypes.Validator<TargetRef>,
+        }) as PropTypes.Validator<TargetRef>,
         type: PropTypes.string.isRequired as PropTypes.Validator<EventTypes>,
       }
     : {}
diff --git a/packages/react-component-event-listener/src/types.ts b/packages/react-component-event-listener/src/types.ts
index 4155976c1..57c9a0d43 100644
--- a/packages/react-component-event-listener/src/types.ts
+++ b/packages/react-component-event-listener/src/types.ts
@@ -7,8 +7,11 @@ export interface EventListenerOptions<T extends EventTypes = 'click'> {
   /** A function which receives a notification when an event of the specified type occurs. */
   listener: EventHandler<T>
 
+  /** A target node. Use `target` or `targetRef` prop. */
+  target?: Target
+
   /** A ref object with a target node. */
-  targetRef: TargetRef
+  targetRef?: TargetRef
 
   /** A case-sensitive string representing the event type to listen for. */
   type: T
@@ -17,4 +20,5 @@ export interface EventListenerOptions<T extends EventTypes = 'click'> {
 export type EventHandler<T extends EventTypes> = (e: DocumentEventMap[T]) => void
 export type EventTypes = keyof DocumentEventMap
 
-export type TargetRef = React.RefObject<Node | Window>
+export type Target = Node | Window
+export type TargetRef = React.RefObject<Target>
diff --git a/packages/react-component-event-listener/src/useEventListener.ts b/packages/react-component-event-listener/src/useEventListener.ts
index 91f7561c5..125633ee3 100644
--- a/packages/react-component-event-listener/src/useEventListener.ts
+++ b/packages/react-component-event-listener/src/useEventListener.ts
@@ -1,14 +1,14 @@
 import * as React from 'react'
 
-import { EventHandler, EventListenerOptions, EventTypes, TargetRef } from './types'
+import { EventHandler, EventListenerOptions, EventTypes, Target } from './types'
 
 const isActionSupported = (
-  targetRef: TargetRef,
+  element: Target | null | undefined,
   method: 'addEventListener' | 'removeEventListener',
-) => targetRef && !!targetRef.current && !!targetRef.current[method]
+): element is Target => (element ? !!element[method] : false)
 
 const useEventListener = <T extends EventTypes>(options: EventListenerOptions<T>): void => {
-  const { capture, listener, type, targetRef } = options
+  const { capture, listener, type, target, targetRef } = options
 
   const latestListener = React.useRef<EventHandler<T>>(listener)
   latestListener.current = listener
@@ -17,25 +17,44 @@ const useEventListener = <T extends EventTypes>(options: EventListenerOptions<T>
     return latestListener.current(event)
   }, [])
 
+  if (process.env.NODE_ENV !== 'production') {
+    React.useEffect(() => {
+      if (typeof target !== 'undefined' && typeof targetRef !== 'undefined') {
+        throw new Error(
+          '`target` and `targetRef` props are mutually exclusive, please use one of them.',
+        )
+      }
+
+      if (typeof target === 'undefined' && typeof targetRef === 'undefined') {
+        throw new Error(
+          "`target` and `targetRef` props are `undefined`, it' required to use one of them.",
+        )
+      }
+    }, [target, targetRef])
+  }
+
   React.useEffect(() => {
-    if (isActionSupported(targetRef, 'addEventListener')) {
-      ;(targetRef.current as NonNullable<Node>).addEventListener(type, eventHandler, capture)
+    const element: Target | null | undefined =
+      typeof targetRef === 'undefined' ? target : targetRef.current
+
+    if (isActionSupported(element, 'addEventListener')) {
+      element.addEventListener(type, eventHandler, capture)
     } else if (process.env.NODE_ENV !== 'production') {
       throw new Error(
-        '@fluentui/react-component-event-listener: Passed `targetRef` is not valid or does not support `addEventListener()` method.',
+        '@fluentui/react-component-event-listener: Passed `element` is not valid or does not support `addEventListener()` method.',
       )
     }
 
     return () => {
-      if (isActionSupported(targetRef, 'removeEventListener')) {
-        ;(targetRef.current as NonNullable<Node>).removeEventListener(type, eventHandler, capture)
+      if (isActionSupported(element, 'removeEventListener')) {
+        element.removeEventListener(type, eventHandler, capture)
       } else if (process.env.NODE_ENV !== 'production') {
         throw new Error(
-          '@fluentui/react-component-event-listener: Passed `targetRef` is not valid or does not support `removeEventListener()` method.',
+          '@fluentui/react-component-event-listener: Passed `element` is not valid or does not support `removeEventListener()` method.',
         )
       }
     }
-  }, [capture, targetRef, type])
+  }, [capture, target, targetRef, type])
 }
 
 export default useEventListener
diff --git a/packages/react-component-ref/src/index.ts b/packages/react-component-ref/src/index.ts
index d5724dd37..5242fee94 100644
--- a/packages/react-component-ref/src/index.ts
+++ b/packages/react-component-ref/src/index.ts
@@ -1,6 +1,5 @@
 export { default as handleRef } from './handleRef'
 export { default as isRefObject } from './isRefObject'
-export { default as toRefObject } from './toRefObject'
 
 export { default as Ref } from './Ref'
 export { default as RefFindNode } from './RefFindNode'
diff --git a/packages/react-component-ref/src/toRefObject.ts b/packages/react-component-ref/src/toRefObject.ts
deleted file mode 100644
index 872620d46..000000000
--- a/packages/react-component-ref/src/toRefObject.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import * as React from 'react'
-
-const nullRefObject: React.RefObject<null> = { current: null }
-
-// A map of created ref objects to provide memoization.
-const refObjects = new WeakMap<Node | Window, React.RefObject<Node | Window>>()
-
-/** Creates a React ref object from existing DOM node. */
-const toRefObject = <T extends Node | Window>(node: T): React.RefObject<T> => {
-  // A "null" is not valid key for a WeakMap
-  if (node === null) {
-    return nullRefObject as any
-  }
-
-  if (refObjects.has(node)) {
-    return refObjects.get(node) as React.RefObject<T>
-  }
-
-  const refObject: React.RefObject<T> = { current: node }
-  refObjects.set(node, refObject)
-
-  return refObject
-}
-
-export default toRefObject
diff --git a/packages/react/src/components/Debug/Debug.tsx b/packages/react/src/components/Debug/Debug.tsx
index 6c0c3bd3e..931268e9c 100644
--- a/packages/react/src/components/Debug/Debug.tsx
+++ b/packages/react/src/components/Debug/Debug.tsx
@@ -1,7 +1,6 @@
 import keyboardKey from 'keyboard-key'
 import * as PropTypes from 'prop-types'
 import * as React from 'react'
-import { toRefObject } from '@fluentui/react-component-ref'
 import { EventListener } from '@fluentui/react-component-event-listener'
 
 import { isBrowser } from '../../utils'
@@ -146,22 +145,18 @@ class Debug extends React.Component<DebugProps, DebugState> {
     if (process.env.NODE_ENV !== 'production' && isDebugEnabled) {
       return (
         <>
-          <EventListener
-            targetRef={toRefObject(mountDocument.body)}
-            listener={this.handleKeyDown}
-            type="keydown"
-          />
+          <EventListener listener={this.handleKeyDown} target={mountDocument.body} type="keydown" />
           {isSelecting && (
             <EventListener
-              targetRef={toRefObject(mountDocument.body)}
               listener={this.handleMouseMove}
+              target={mountDocument.body}
               type="mousemove"
             />
           )}
           {isSelecting && fiberNav && fiberNav.domNode && (
             <EventListener
-              targetRef={toRefObject(fiberNav.domNode)}
               listener={this.handleDOMNodeClick}
+              target={fiberNav.domNode}
               type="click"
             />
           )}
diff --git a/packages/react/src/components/Dialog/Dialog.tsx b/packages/react/src/components/Dialog/Dialog.tsx
index 0c20b1cee..6485a452a 100644
--- a/packages/react/src/components/Dialog/Dialog.tsx
+++ b/packages/react/src/components/Dialog/Dialog.tsx
@@ -2,7 +2,7 @@ import { Accessibility, dialogBehavior } from '@fluentui/accessibility'
 import { FocusTrapZoneProps } from '@fluentui/react-bindings'
 import { Unstable_NestingAuto } from '@fluentui/react-component-nesting-registry'
 import { EventListener } from '@fluentui/react-component-event-listener'
-import { Ref, toRefObject } from '@fluentui/react-component-ref'
+import { Ref } from '@fluentui/react-component-ref'
 import * as customPropTypes from '@fluentui/react-proptypes'
 import * as _ from 'lodash'
 import * as PropTypes from 'prop-types'
@@ -330,7 +330,6 @@ class Dialog extends AutoControlledComponent<WithAsProp<DialogProps>, DialogStat
       </Ref>
     )
 
-    const targetRef = toRefObject(this.context.target)
     const triggerAccessibility: TriggerAccessibility = {
       attributes: accessibility.attributes.trigger,
       keyHandlers: accessibility.keyHandlers.trigger,
@@ -366,14 +365,14 @@ class Dialog extends AutoControlledComponent<WithAsProp<DialogProps>, DialogStat
               {closeOnOutsideClick && (
                 <EventListener
                   listener={this.handleOverlayClick}
-                  targetRef={targetRef}
+                  target={this.context.target}
                   type="click"
                   capture
                 />
               )}
               <EventListener
                 listener={this.handleDocumentKeydown(getRefs)}
-                targetRef={targetRef}
+                target={this.context.target}
                 type="keydown"
                 capture
               />
diff --git a/packages/react/src/components/Menu/MenuItem.tsx b/packages/react/src/components/Menu/MenuItem.tsx
index 0ae3ee368..de7fc0516 100644
--- a/packages/react/src/components/Menu/MenuItem.tsx
+++ b/packages/react/src/components/Menu/MenuItem.tsx
@@ -1,7 +1,7 @@
 import { Accessibility, menuItemBehavior, submenuBehavior } from '@fluentui/accessibility'
 import { focusAsync } from '@fluentui/react-bindings'
 import { EventListener } from '@fluentui/react-component-event-listener'
-import { Ref, toRefObject } from '@fluentui/react-component-ref'
+import { Ref } from '@fluentui/react-component-ref'
 import * as customPropTypes from '@fluentui/react-proptypes'
 import * as _ from 'lodash'
 import cx from 'classnames'
@@ -215,7 +215,6 @@ class MenuItem extends AutoControlledComponent<WithAsProp<MenuItemProps>, MenuIt
 
     const defaultIndicator = { name: vertical ? 'icon-arrow-end' : 'icon-arrow-down' }
     const indicatorWithDefaults = indicator === undefined ? defaultIndicator : indicator
-    const targetRef = toRefObject(this.context.target)
 
     const menuItemInner = childrenExist(children) ? (
       children
@@ -274,7 +273,11 @@ class MenuItem extends AutoControlledComponent<WithAsProp<MenuItemProps>, MenuIt
               })}
             </Popper>
           </Ref>
-          <EventListener listener={this.outsideClickHandler} targetRef={targetRef} type="click" />
+          <EventListener
+            listener={this.outsideClickHandler}
+            target={this.context.target}
+            type="click"
+          />
         </>
       ) : null
 
diff --git a/packages/react/src/components/Popup/Popup.tsx b/packages/react/src/components/Popup/Popup.tsx
index 1a82c47c9..4fca47f4b 100644
--- a/packages/react/src/components/Popup/Popup.tsx
+++ b/packages/react/src/components/Popup/Popup.tsx
@@ -6,7 +6,7 @@ import {
 } from '@fluentui/react-bindings'
 import { EventListener } from '@fluentui/react-component-event-listener'
 import { NodeRef, Unstable_NestingAuto } from '@fluentui/react-component-nesting-registry'
-import { handleRef, toRefObject, Ref } from '@fluentui/react-component-ref'
+import { handleRef, Ref } from '@fluentui/react-component-ref'
 import * as customPropTypes from '@fluentui/react-proptypes'
 import * as React from 'react'
 import * as PropTypes from 'prop-types'
@@ -477,9 +477,7 @@ export default class Popup extends AutoControlledComponent<PopupProps, PopupStat
         offset={offset}
         rtl={rtl}
         unstable_pinned={unstable_pinned}
-        targetRef={
-          this.rightClickReferenceObject || (target ? toRefObject(target) : this.triggerRef)
-        }
+        targetRef={this.rightClickReferenceObject || target || this.triggerRef}
         children={this.renderPopperChildren.bind(this, popupPositionClasses, rtl, accessibility)}
       />
     )
@@ -501,8 +499,6 @@ export default class Popup extends AutoControlledComponent<PopupProps, PopupStat
     } = this.props
 
     const content = renderContent ? renderContent(scheduleUpdate) : propsContent
-    const targetRef = toRefObject(this.context.target)
-
     const popupContent = Popup.Content.create(content || {}, {
       defaultProps: () => ({
         ...(rtl && { dir: 'rtl' }),
@@ -535,19 +531,19 @@ export default class Popup extends AutoControlledComponent<PopupProps, PopupStat
 
             <EventListener
               listener={this.handleDocumentClick(getRefs)}
-              targetRef={targetRef}
+              target={this.context.target}
               type="click"
               capture
             />
             <EventListener
               listener={this.handleDocumentClick(getRefs)}
-              targetRef={targetRef}
+              target={this.context.target}
               type="contextmenu"
               capture
             />
             <EventListener
               listener={this.handleDocumentKeyDown(getRefs)}
-              targetRef={targetRef}
+              target={this.context.target}
               type="keydown"
               capture
             />
@@ -556,13 +552,13 @@ export default class Popup extends AutoControlledComponent<PopupProps, PopupStat
               <>
                 <EventListener
                   listener={this.dismissOnScroll}
-                  targetRef={targetRef}
+                  target={this.context.target}
                   type="wheel"
                   capture
                 />
                 <EventListener
                   listener={this.dismissOnScroll}
-                  targetRef={targetRef}
+                  target={this.context.target}
                   type="touchmove"
                   capture
                 />
diff --git a/packages/react/src/components/Portal/Portal.tsx b/packages/react/src/components/Portal/Portal.tsx
index ef4998fca..da1ac9bd7 100644
--- a/packages/react/src/components/Portal/Portal.tsx
+++ b/packages/react/src/components/Portal/Portal.tsx
@@ -5,7 +5,7 @@ import {
   FocusTrapZoneProps,
 } from '@fluentui/react-bindings'
 import { EventListener } from '@fluentui/react-component-event-listener'
-import { handleRef, Ref, toRefObject } from '@fluentui/react-component-ref'
+import { handleRef, Ref } from '@fluentui/react-component-ref'
 import * as customPropTypes from '@fluentui/react-proptypes'
 import * as PropTypes from 'prop-types'
 import * as React from 'react'
@@ -126,7 +126,6 @@ class Portal extends AutoControlledComponent<PortalProps, PortalState> {
 
     const contentToRender = childrenExist(children) ? children : content
     const focusTrapZoneProps = (_.keys(trapFocus).length && trapFocus) || {}
-    const targetRef = toRefObject(this.context.target)
 
     return (
       open && (
@@ -141,7 +140,11 @@ class Portal extends AutoControlledComponent<PortalProps, PortalState> {
             ) : (
               contentToRender
             )}
-            <EventListener listener={this.handleDocumentClick} targetRef={targetRef} type="click" />
+            <EventListener
+              listener={this.handleDocumentClick}
+              target={this.context.target}
+              type="click"
+            />
           </PortalInner>
         </Ref>
       )
diff --git a/packages/react/src/components/Toolbar/Toolbar.tsx b/packages/react/src/components/Toolbar/Toolbar.tsx
index 0fc284db4..8a2a849fa 100644
--- a/packages/react/src/components/Toolbar/Toolbar.tsx
+++ b/packages/react/src/components/Toolbar/Toolbar.tsx
@@ -8,7 +8,7 @@ import * as React from 'react'
 import * as _ from 'lodash'
 import * as customPropTypes from '@fluentui/react-proptypes'
 import * as PropTypes from 'prop-types'
-import { Ref, toRefObject } from '@fluentui/react-component-ref'
+import { Ref } from '@fluentui/react-component-ref'
 import { EventListener } from '@fluentui/react-component-event-listener'
 
 import {
@@ -498,8 +498,6 @@ class Toolbar extends UIComponent<WithAsProp<ToolbarProps>> {
     unhandledProps,
     rtl,
   }): React.ReactNode {
-    const windowRef = toRefObject(this.context.target.defaultView)
-
     this.rtl = rtl
     const { children, items, overflow, overflowItem } = this.props
 
@@ -530,7 +528,11 @@ class Toolbar extends UIComponent<WithAsProp<ToolbarProps>> {
           </div>
           <div className={classes.offsetMeasure} ref={this.offsetMeasureRef} />
         </ElementType>
-        <EventListener listener={this.handleWindowResize} targetRef={windowRef} type="resize" />
+        <EventListener
+          listener={this.handleWindowResize}
+          target={this.context.target.defaultView}
+          type="resize"
+        />
       </>
     )
   }
diff --git a/packages/react/src/components/Toolbar/ToolbarItem.tsx b/packages/react/src/components/Toolbar/ToolbarItem.tsx
index 508852977..f6e350de5 100644
--- a/packages/react/src/components/Toolbar/ToolbarItem.tsx
+++ b/packages/react/src/components/Toolbar/ToolbarItem.tsx
@@ -4,7 +4,7 @@ import * as PropTypes from 'prop-types'
 import * as customPropTypes from '@fluentui/react-proptypes'
 import { Accessibility, toolbarItemBehavior } from '@fluentui/accessibility'
 import cx from 'classnames'
-import { Ref, toRefObject } from '@fluentui/react-component-ref'
+import { Ref } from '@fluentui/react-component-ref'
 import { EventListener } from '@fluentui/react-component-event-listener'
 
 import {
@@ -199,7 +199,6 @@ class ToolbarItem extends UIComponent<WithAsProp<ToolbarItemProps>> {
 
   renderComponent({ ElementType, classes, unhandledProps, accessibility, variables }) {
     const { icon, children, disabled, popup, menu, menuOpen, wrapper } = this.props
-    const targetRef = toRefObject(this.context.target)
 
     const itemElement = (
       <ElementType
@@ -242,7 +241,7 @@ class ToolbarItem extends UIComponent<WithAsProp<ToolbarItemProps>> {
             </Ref>
             <EventListener
               listener={this.handleOutsideClick(getRefs)}
-              targetRef={targetRef}
+              target={this.context.target}
               type="click"
               capture
             />
diff --git a/packages/react/src/components/Toolbar/ToolbarMenuItem.tsx b/packages/react/src/components/Toolbar/ToolbarMenuItem.tsx
index 55b5a709f..70c19c25e 100644
--- a/packages/react/src/components/Toolbar/ToolbarMenuItem.tsx
+++ b/packages/react/src/components/Toolbar/ToolbarMenuItem.tsx
@@ -5,7 +5,7 @@ import cx from 'classnames'
 import * as PropTypes from 'prop-types'
 
 import { EventListener } from '@fluentui/react-component-event-listener'
-import { Ref, toRefObject } from '@fluentui/react-component-ref'
+import { Ref } from '@fluentui/react-component-ref'
 import * as customPropTypes from '@fluentui/react-proptypes'
 import { focusAsync } from '@fluentui/react-bindings'
 import { GetRefs, NodeRef, Unstable_NestingAuto } from '@fluentui/react-component-nesting-registry'
@@ -281,8 +281,6 @@ class ToolbarMenuItem extends AutoControlledComponent<
     } = this.props
     const { menuOpen } = this.state
 
-    const targetRef = toRefObject(this.context.target)
-
     const elementType = (
       <ElementType
         {...accessibility.attributes.root}
@@ -363,7 +361,7 @@ class ToolbarMenuItem extends AutoControlledComponent<
               </Ref>
               <EventListener
                 listener={this.outsideClickHandler(getRefs)}
-                targetRef={targetRef}
+                target={this.context.target}
                 type="click"
               />
             </>
diff --git a/packages/react/src/components/Tooltip/Tooltip.tsx b/packages/react/src/components/Tooltip/Tooltip.tsx
index bdbc0adae..2f35194a6 100644
--- a/packages/react/src/components/Tooltip/Tooltip.tsx
+++ b/packages/react/src/components/Tooltip/Tooltip.tsx
@@ -1,6 +1,6 @@
 import { Accessibility, tooltipAsLabelBehavior } from '@fluentui/accessibility'
 import { ReactAccessibilityBehavior } from '@fluentui/react-bindings'
-import { toRefObject, Ref } from '@fluentui/react-component-ref'
+import { Ref } from '@fluentui/react-component-ref'
 import * as customPropTypes from '@fluentui/react-proptypes'
 import * as React from 'react'
 import * as PropTypes from 'prop-types'
@@ -141,9 +141,9 @@ export default class Tooltip extends AutoControlledComponent<TooltipProps, Toolt
 
   static create: ShorthandFactory<TooltipProps>
 
+  contentRef = React.createRef<HTMLElement>()
   pointerTargetRef = React.createRef<HTMLDivElement>()
   triggerRef = React.createRef<HTMLElement>()
-  contentRef = React.createRef<HTMLElement>()
   closeTimeoutId
 
   actionHandlers = {
@@ -259,7 +259,7 @@ export default class Tooltip extends AutoControlledComponent<TooltipProps, Toolt
         position={position}
         enabled={open}
         rtl={rtl}
-        targetRef={target ? toRefObject(target) : this.triggerRef}
+        targetRef={target || this.triggerRef}
         children={this.renderPopperChildren.bind(this, tooltipPositionClasses, rtl, accessibility)}
       />
     )


Test patch:
diff --git a/packages/react-component-event-listener/test/EventListener-test.tsx b/packages/react-component-event-listener/test/EventListener-test.tsx
index 57280e2a0..56e46fd88 100644
--- a/packages/react-component-event-listener/test/EventListener-test.tsx
+++ b/packages/react-component-event-listener/test/EventListener-test.tsx
@@ -4,6 +4,23 @@ import * as React from 'react'
 // @ts-ignore
 import * as simulant from 'simulant'
 
+class TestBoundary extends React.Component<{ onError: (e: Error) => void }, { hasError: boolean }> {
+  state = { hasError: false }
+
+  componentDidCatch(error: Error) {
+    this.props.onError(error)
+    this.setState({ hasError: true })
+  }
+
+  render() {
+    if (this.state.hasError) {
+      return null
+    }
+
+    return this.props.children
+  }
+}
+
 describe('EventListener', () => {
   describe('listener', () => {
     it('handles events on `target`', () => {
@@ -83,4 +100,70 @@ describe('EventListener', () => {
       expect(listener).toHaveBeenCalledTimes(2)
     })
   })
+
+  describe('target', () => {
+    it('handles events', () => {
+      const listener = jest.fn()
+      mount(<EventListener listener={listener} target={document} type="click" />)
+
+      simulant.fire(document, 'click')
+      expect(listener).toHaveBeenCalledTimes(1)
+    })
+
+    it('throws an error when is used with `targetRef`', () => {
+      jest.spyOn(console, 'error').mockImplementation(() => {})
+      const onError = jest.fn()
+
+      mount(
+        <TestBoundary onError={onError}>
+          <EventListener
+            listener={jest.fn()}
+            target={document}
+            targetRef={documentRef}
+            type="click"
+          />
+        </TestBoundary>,
+      )
+
+      expect(onError).toBeCalledWith(
+        expect.objectContaining({
+          message: '`target` and `targetRef` props are mutually exclusive, please use one of them.',
+        }),
+      )
+
+      // We need to clean up mocks to avoid errors reported by React
+      ;(console.error as any).mockClear()
+    })
+
+    it('throws an error when not defined', () => {
+      jest.spyOn(console, 'error').mockImplementation(() => {})
+      const onError = jest.fn()
+
+      mount(
+        <TestBoundary onError={onError}>
+          <EventListener listener={jest.fn()} type="click" />
+        </TestBoundary>,
+      )
+
+      expect(onError).toBeCalledWith(
+        expect.objectContaining({
+          message:
+            "`target` and `targetRef` props are `undefined`, it' required to use one of them.",
+        }),
+      )
+
+      // We need to clean up mocks to avoid errors reported by React
+      ;(console.error as any).mockClear()
+    })
+  })
+
+  describe('targetRef', () => {
+    it('handles events', () => {
+      const listener = jest.fn()
+      mount(<EventListener listener={listener} targetRef={documentRef} type="click" />)
+
+      simulant.fire(document, 'click')
+      expect(listener).toHaveBeenCalledTimes(1)
+    })
+  })
 })
diff --git a/packages/react-component-ref/test/toRefObject-test.ts b/packages/react-component-ref/test/toRefObject-test.ts
deleted file mode 100644
index d103efe87..000000000
--- a/packages/react-component-ref/test/toRefObject-test.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { toRefObject } from '@fluentui/react-component-ref'
-
-describe('toRefObject', () => {
-  it('creates an ref object from an input', () => {
-    const node = document.createElement('div')
-    expect(toRefObject(node)).toHaveProperty('current', node)
-  })
-
-  it('handles "null" as input', () => {
-    expect(toRefObject(null as any)).toHaveProperty('current', null)
-  })
-
-  it('returned object is memoized', () => {
-    const node = document.createElement('div')
-    const refObject = toRefObject(node)
-
-    expect(toRefObject(node)).toBe(refObject)
-  })
-})