Browse Source

ref(js): Convert ScrollToTop to a hook (#30652)

Evan Purkhiser 3 years ago
parent
commit
9dea41d208

+ 31 - 0
static/app/utils/useScrollToTop.tsx

@@ -0,0 +1,31 @@
+import {useEffect, useRef} from 'react';
+import {Location} from 'history';
+
+type Options = {
+  location: Location;
+  /**
+   * Function to stop scrolling from happening if a certan condition is met
+   */
+  disable?: (location: Location, prevLocation: Location) => boolean;
+};
+
+/**
+ * Automatically scrolls to the top of the page any time the location changes.
+ */
+function useScrollToTop({location, disable}: Options) {
+  const lastLocation = useRef(location);
+
+  // Check if we should scroll to the top any time the location changes
+  useEffect(() => {
+    const shouldDisable = disable?.(location, lastLocation.current);
+    lastLocation.current = location;
+
+    if (shouldDisable) {
+      return;
+    }
+
+    window.scrollTo(0, 0);
+  }, [location]);
+}
+
+export default useScrollToTop;

+ 12 - 7
static/app/views/alerts/builder/projectProvider.tsx

@@ -1,4 +1,4 @@
-import * as React from 'react';
+import {cloneElement, Fragment, isValidElement} from 'react';
 import {RouteComponentProps} from 'react-router';
 
 import {fetchOrgMembers} from 'sentry/actionCreators/members';
@@ -8,7 +8,7 @@ import {t} from 'sentry/locale';
 import {Organization, Project} from 'sentry/types';
 import Projects from 'sentry/utils/projects';
 import useApi from 'sentry/utils/useApi';
-import ScrollToTop from 'sentry/views/settings/components/scrollToTop';
+import useScrollToTop from 'sentry/utils/useScrollToTop';
 
 type Props = RouteComponentProps<RouteParams, {}> & {
   organization: Organization;
@@ -22,6 +22,7 @@ type RouteParams = {
 
 function AlertBuilderProjectProvider(props: Props) {
   const api = useApi();
+  useScrollToTop({location: props.location});
 
   const {children, params, organization, ...other} = props;
   const {projectId} = params;
@@ -32,8 +33,11 @@ function AlertBuilderProjectProvider(props: Props) {
         if (!initiallyLoaded) {
           return <LoadingIndicator />;
         }
+
         const project = (projects as Project[]).find(({slug}) => slug === projectId);
-        // if loaded, but project fetching states incomplete or project can't be found, project doesn't exist
+
+        // if loaded, but project fetching states incomplete or project can't
+        // be found, project doesn't exist
         if (isIncomplete || !project) {
           return (
             <Alert type="warning">
@@ -41,20 +45,21 @@ function AlertBuilderProjectProvider(props: Props) {
             </Alert>
           );
         }
+
         // fetch members list for mail action fields
         fetchOrgMembers(api, organization.slug, [project.id]);
 
         return (
-          <ScrollToTop location={props.location} disable={() => false}>
-            {children && React.isValidElement(children)
-              ? React.cloneElement(children, {
+          <Fragment>
+            {children && isValidElement(children)
+              ? cloneElement(children, {
                   ...other,
                   ...children.props,
                   project,
                   organization,
                 })
               : children}
-          </ScrollToTop>
+          </Fragment>
         );
       }}
     </Projects>

+ 0 - 27
static/app/views/settings/components/scrollToTop.tsx

@@ -1,27 +0,0 @@
-import {Component} from 'react';
-import {Location} from 'history';
-
-import {callIfFunction} from 'sentry/utils/callIfFunction';
-
-type Props = {
-  location: Location;
-  disable: (location: Location, prevLocation: Location) => boolean;
-};
-
-class ScrollToTop extends Component<Props> {
-  componentDidUpdate(prevProps: Props) {
-    const {disable, location} = this.props;
-
-    const shouldDisable = callIfFunction(disable, location, prevProps.location);
-
-    if (!shouldDisable && this.props.location !== prevProps.location) {
-      window.scrollTo(0, 0);
-    }
-  }
-
-  render() {
-    return this.props.children;
-  }
-}
-
-export default ScrollToTop;

+ 9 - 14
static/app/views/settings/components/settingsWrapper.tsx

@@ -2,7 +2,7 @@ import styled from '@emotion/styled';
 import {Location} from 'history';
 
 import space from 'sentry/styles/space';
-import ScrollToTop from 'sentry/views/settings/components/scrollToTop';
+import useScrollToTop from 'sentry/utils/useScrollToTop';
 
 type Props = {
   location: Location;
@@ -10,19 +10,14 @@ type Props = {
 };
 
 function SettingsWrapper({location, children}: Props) {
-  return (
-    <StyledSettingsWrapper>
-      <ScrollToTop
-        location={location}
-        disable={(newLocation, prevLocation) =>
-          newLocation.pathname === prevLocation.pathname &&
-          newLocation.query?.query !== prevLocation.query?.query
-        }
-      >
-        {children}
-      </ScrollToTop>
-    </StyledSettingsWrapper>
-  );
+  useScrollToTop({
+    location,
+    disable: (newLocation, prevLocation) =>
+      newLocation.pathname === prevLocation.pathname &&
+      newLocation.query?.query !== prevLocation.query?.query,
+  });
+
+  return <StyledSettingsWrapper>{children}</StyledSettingsWrapper>;
 }
 
 export default SettingsWrapper;