Browse Source

ref(replays): Refactor ScrubberMouseTracking into a hook (#41824)

Turning this into a hook means we can remove one naked `<div>` in the
dom, but also hooks are so cool. Hooks for all the things!
Ryan Albrecht 2 years ago
parent
commit
6c3ab3ed29

+ 30 - 28
static/app/components/replays/breadcrumbs/replayTimeline.tsx

@@ -1,3 +1,4 @@
+import {useRef} from 'react';
 import styled from '@emotion/styled';
 
 import {Panel} from 'sentry/components/panels';
@@ -10,7 +11,7 @@ import ReplayTimelineEvents from 'sentry/components/replays/breadcrumbs/replayTi
 import ReplayTimelineSpans from 'sentry/components/replays/breadcrumbs/replayTimelineSpans';
 import Stacked from 'sentry/components/replays/breadcrumbs/stacked';
 import {TimelineScrubber} from 'sentry/components/replays/player/scrubber';
-import ScrubberMouseTracking from 'sentry/components/replays/player/scrubberMouseTracking';
+import useScrubberMouseTracking from 'sentry/components/replays/player/useScrubberMouseTracking';
 import {useReplayContext} from 'sentry/components/replays/replayContext';
 import {Resizeable} from 'sentry/components/replays/resizeable';
 import {BreadcrumbType} from 'sentry/types/breadcrumbs';
@@ -27,6 +28,9 @@ const USER_ACTIONS = [
 function ReplayTimeline({}: Props) {
   const {replay} = useReplayContext();
 
+  const elem = useRef<HTMLDivElement>(null);
+  const mouseTrackingProps = useScrubberMouseTracking({elem});
+
   if (!replay) {
     return <Placeholder height="54px" bottomGutter={2} />;
   }
@@ -38,33 +42,31 @@ function ReplayTimeline({}: Props) {
   const networkSpans = replay.getNetworkSpans();
 
   return (
-    <Panel>
-      <ScrubberMouseTracking>
-        <Resizeable>
-          {({width}) => (
-            <Stacked>
-              <MinorGridlines durationMs={durationMs} width={width} />
-              <MajorGridlines durationMs={durationMs} width={width} />
-              <TimelineScrubber />
-              <UnderTimestamp paddingTop="36px">
-                <ReplayTimelineSpans
-                  durationMs={durationMs}
-                  spans={networkSpans}
-                  startTimestampMs={startTimestampMs}
-                />
-              </UnderTimestamp>
-              <UnderTimestamp paddingTop="26px">
-                <ReplayTimelineEvents
-                  crumbs={userCrumbs}
-                  durationMs={durationMs}
-                  startTimestampMs={startTimestampMs}
-                  width={width}
-                />
-              </UnderTimestamp>
-            </Stacked>
-          )}
-        </Resizeable>
-      </ScrubberMouseTracking>
+    <Panel ref={elem} {...mouseTrackingProps}>
+      <Resizeable>
+        {({width}) => (
+          <Stacked>
+            <MinorGridlines durationMs={durationMs} width={width} />
+            <MajorGridlines durationMs={durationMs} width={width} />
+            <TimelineScrubber />
+            <UnderTimestamp paddingTop="36px">
+              <ReplayTimelineSpans
+                durationMs={durationMs}
+                spans={networkSpans}
+                startTimestampMs={startTimestampMs}
+              />
+            </UnderTimestamp>
+            <UnderTimestamp paddingTop="26px">
+              <ReplayTimelineEvents
+                crumbs={userCrumbs}
+                durationMs={durationMs}
+                startTimestampMs={startTimestampMs}
+                width={width}
+              />
+            </UnderTimestamp>
+          </Stacked>
+        )}
+      </Resizeable>
     </Panel>
   );
 }

+ 8 - 13
static/app/components/replays/player/scrubberMouseTracking.tsx → static/app/components/replays/player/useScrubberMouseTracking.tsx

@@ -1,14 +1,13 @@
-import {useCallback} from 'react';
+import {RefObject, useCallback} from 'react';
 
 import {useReplayContext} from 'sentry/components/replays/replayContext';
 import useMouseTracking from 'sentry/utils/replays/hooks/useMouseTracking';
 
-type Props = {
-  children: React.ReactNode;
-  className?: string;
+type Opts<T extends Element> = {
+  elem: RefObject<T>;
 };
 
-function ScrubberMouseTracking({className, children}: Props) {
+export function useScrubberMouseTracking<T extends Element>({elem}: Opts<T>) {
   const {replay, setCurrentHoverTime} = useReplayContext();
   const durationMs = replay?.getDurationMs();
 
@@ -31,15 +30,11 @@ function ScrubberMouseTracking({className, children}: Props) {
     [durationMs, setCurrentHoverTime]
   );
 
-  const mouseTrackingProps = useMouseTracking<HTMLDivElement>({
+  const mouseTrackingProps = useMouseTracking({
+    elem,
     onPositionChange: handlePositionChange,
   });
-
-  return (
-    <div className={className} {...mouseTrackingProps}>
-      {children}
-    </div>
-  );
+  return mouseTrackingProps;
 }
 
-export default ScrubberMouseTracking;
+export default useScrubberMouseTracking;

+ 6 - 3
static/app/components/replays/replayController.tsx

@@ -6,7 +6,7 @@ import Button from 'sentry/components/button';
 import ButtonBar from 'sentry/components/buttonBar';
 import CompositeSelect from 'sentry/components/compositeSelect';
 import {PlayerScrubber} from 'sentry/components/replays/player/scrubber';
-import ScrubberMouseTracking from 'sentry/components/replays/player/scrubberMouseTracking';
+import useScrubberMouseTracking from 'sentry/components/replays/player/useScrubberMouseTracking';
 import {useReplayContext} from 'sentry/components/replays/replayContext';
 import {formatTime, relativeTimeInMs} from 'sentry/components/replays/utils';
 import {
@@ -192,12 +192,15 @@ const ReplayControls = ({
   });
   useLayoutEffect(() => updateIsCompact, [updateIsCompact]);
 
+  const elem = useRef<HTMLDivElement>(null);
+  const mouseTrackingProps = useScrubberMouseTracking({elem});
+
   return (
     <ButtonGrid ref={barRef} isCompact={isCompact}>
       <ReplayPlayPauseBar />
       <TimeAndScrubber isCompact={isCompact}>
         <Time>{formatTime(currentTime)}</Time>
-        <StyledScrubber>
+        <StyledScrubber ref={elem} {...mouseTrackingProps}>
           <PlayerScrubber />
         </StyledScrubber>
         <Time>{durationMs ? formatTime(durationMs) : '--:--'}</Time>
@@ -244,7 +247,7 @@ const Time = styled('span')`
   font-variant-numeric: tabular-nums;
 `;
 
-const StyledScrubber = styled(ScrubberMouseTracking)`
+const StyledScrubber = styled('div')`
   height: 32px;
   display: flex;
   align-items: center;

+ 4 - 4
static/app/utils/replays/hooks/useMouseTracking.tsx

@@ -1,9 +1,10 @@
-import {DOMAttributes, MouseEvent, useCallback, useRef} from 'react';
+import {DOMAttributes, MouseEvent, RefObject, useCallback, useRef} from 'react';
 import * as Sentry from '@sentry/react';
 
 type CallbackArgs = {height: number; left: number; top: number; width: number};
 
 type Opts<T extends Element> = {
+  elem: RefObject<T>;
   onPositionChange: (args: undefined | CallbackArgs) => void;
 } & DOMAttributes<T>;
 
@@ -39,13 +40,13 @@ function getBoundingRect(
 }
 
 function useMouseTracking<T extends Element>({
+  elem,
   onPositionChange,
   onMouseEnter,
   onMouseMove,
   onMouseLeave,
   ...rest
 }: Opts<T>) {
-  const elem = useRef<T>(null);
   const controller = useRef<AbortController>(new AbortController());
 
   const handlePositionChange = useCallback(
@@ -74,7 +75,7 @@ function useMouseTracking<T extends Element>({
         Sentry.captureException(err);
       }
     },
-    [onPositionChange, controller]
+    [onPositionChange, controller, elem]
   );
 
   const handleOnMouseLeave = useCallback(() => {
@@ -87,7 +88,6 @@ function useMouseTracking<T extends Element>({
   }, [onPositionChange, controller]);
 
   return {
-    ref: elem,
     ...rest,
     onMouseEnter: (e: MouseEvent<T>) => {
       handlePositionChange(e);

+ 14 - 2
static/app/views/replays/detail/layout/splitPanel.tsx

@@ -192,7 +192,9 @@ function SplitPanel(props: Props) {
     [props, startMouseIdleTimer]
   );
 
+  const elem = useRef<HTMLDivElement>(null);
   const mouseTrackingProps = useMouseTracking<HTMLDivElement>({
+    elem,
     onPositionChange: handlePositionChange,
   });
 
@@ -202,7 +204,12 @@ function SplitPanel(props: Props) {
     const {left: a, right: b} = props;
 
     return (
-      <SplitPanelContainer orientation="columns" size={sizeCSS} {...activeTrackingProps}>
+      <SplitPanelContainer
+        orientation="columns"
+        size={sizeCSS}
+        ref={elem}
+        {...activeTrackingProps}
+      >
         <Panel>{getValFromSide(a, 'content') || a}</Panel>
         <Divider
           slideDirection="leftright"
@@ -215,7 +222,12 @@ function SplitPanel(props: Props) {
   }
   const {top: a, bottom: b} = props;
   return (
-    <SplitPanelContainer orientation="rows" size={sizeCSS} {...activeTrackingProps}>
+    <SplitPanelContainer
+      orientation="rows"
+      size={sizeCSS}
+      ref={elem}
+      {...activeTrackingProps}
+    >
       <Panel>{getValFromSide(a, 'content') || a}</Panel>
       <Divider
         slideDirection="updown"