perf(dashboard): reduce rerenders of DragDroppable (#16525)

* perf(dashboard): reduce rerenders of DragDroppable

* lint fix
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
index cc897b8..d16e761 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
@@ -18,7 +18,7 @@
  */
 /* eslint-env browser */
 import cx from 'classnames';
-import React, { FC } from 'react';
+import React, { FC, useCallback, useMemo } from 'react';
 import { JsonObject, styled, css } from '@superset-ui/core';
 import ErrorBoundary from 'src/components/ErrorBoundary';
 import BuilderComponentPane from 'src/dashboard/components/BuilderComponentPane';
@@ -157,15 +157,14 @@
     state => state.dashboardState.fullSizeChartId,
   );
 
-  const handleChangeTab = ({
-    pathToTabIndex,
-  }: {
-    pathToTabIndex: string[];
-  }) => {
-    dispatch(setDirectPathToChild(pathToTabIndex));
-  };
+  const handleChangeTab = useCallback(
+    ({ pathToTabIndex }: { pathToTabIndex: string[] }) => {
+      dispatch(setDirectPathToChild(pathToTabIndex));
+    },
+    [dispatch],
+  );
 
-  const handleDeleteTopLevelTabs = () => {
+  const handleDeleteTopLevelTabs = useCallback(() => {
     dispatch(deleteTopLevelTabs());
 
     const firstTab = getDirectPathToTabIndex(
@@ -173,7 +172,12 @@
       0,
     );
     dispatch(setDirectPathToChild(firstTab));
-  };
+  }, [dashboardLayout, dispatch]);
+
+  const handleDrop = useCallback(
+    dropResult => dispatch(handleComponentDrop(dropResult)),
+    [dispatch],
+  );
 
   const dashboardRoot = dashboardLayout[DASHBOARD_ROOT_ID];
   const rootChildId = dashboardRoot.children[0];
@@ -217,6 +221,54 @@
   const filterBarHeight = `calc(100vh - ${offset}px)`;
   const filterBarOffset = dashboardFiltersOpen ? 0 : barTopOffset + 20;
 
+  const draggableStyle = useMemo(
+    () => ({
+      marginLeft: dashboardFiltersOpen || editMode ? 0 : -32,
+    }),
+    [dashboardFiltersOpen, editMode],
+  );
+
+  const renderDraggableContent = useCallback(
+    ({ dropIndicatorProps }: { dropIndicatorProps: JsonObject }) => (
+      <div>
+        {!hideDashboardHeader && <DashboardHeader />}
+        {dropIndicatorProps && <div {...dropIndicatorProps} />}
+        {!isReport && topLevelTabs && (
+          <WithPopoverMenu
+            shouldFocus={shouldFocusTabs}
+            menuItems={[
+              <IconButton
+                icon={<Icons.FallOutlined iconSize="xl" />}
+                label="Collapse tab content"
+                onClick={handleDeleteTopLevelTabs}
+              />,
+            ]}
+            editMode={editMode}
+          >
+            {/* @ts-ignore */}
+            <DashboardComponent
+              id={topLevelTabs?.id}
+              parentId={DASHBOARD_ROOT_ID}
+              depth={DASHBOARD_ROOT_DEPTH + 1}
+              index={0}
+              renderTabContent={false}
+              renderHoverMenu={false}
+              onChangeTab={handleChangeTab}
+            />
+          </WithPopoverMenu>
+        )}
+      </div>
+    ),
+    [
+      editMode,
+      handleChangeTab,
+      handleDeleteTopLevelTabs,
+      hideDashboardHeader,
+      isReport,
+      topLevelTabs,
+    ],
+  );
+
   return (
     <StyledDiv>
       {nativeFiltersEnabled && !editMode && (
@@ -244,45 +296,13 @@
           depth={DASHBOARD_ROOT_DEPTH}
           index={0}
           orientation="column"
-          onDrop={dropResult => dispatch(handleComponentDrop(dropResult))}
+          onDrop={handleDrop}
           editMode={editMode}
           // you cannot drop on/displace tabs if they already exist
           disableDragDrop={!!topLevelTabs}
-          style={{
-            marginLeft: dashboardFiltersOpen || editMode ? 0 : -32,
-          }}
+          style={draggableStyle}
         >
-          {({ dropIndicatorProps }: { dropIndicatorProps: JsonObject }) => (
-            <div>
-              {!hideDashboardHeader && <DashboardHeader />}
-              {dropIndicatorProps && <div {...dropIndicatorProps} />}
-              {!isReport && topLevelTabs && (
-                <WithPopoverMenu
-                  shouldFocus={shouldFocusTabs}
-                  menuItems={[
-                    <IconButton
-                      icon={<Icons.FallOutlined iconSize="xl" />}
-                      label="Collapse tab content"
-                      onClick={handleDeleteTopLevelTabs}
-                    />,
-                  ]}
-                  editMode={editMode}
-                >
-                  {/*
-                      // @ts-ignore */}
-                  <DashboardComponent
-                    id={topLevelTabs?.id}
-                    parentId={DASHBOARD_ROOT_ID}
-                    depth={DASHBOARD_ROOT_DEPTH + 1}
-                    index={0}
-                    renderTabContent={false}
-                    renderHoverMenu={false}
-                    onChangeTab={handleChangeTab}
-                  />
-                </WithPopoverMenu>
-              )}
-            </div>
-          )}
+          {renderDraggableContent}
         </DragDroppable>
       </StyledHeader>
       <StyledContent fullSizeChartId={fullSizeChartId}>
diff --git a/superset-frontend/src/dashboard/components/DashboardGrid.jsx b/superset-frontend/src/dashboard/components/DashboardGrid.jsx
index 9fb0fb5..ad03859 100644
--- a/superset-frontend/src/dashboard/components/DashboardGrid.jsx
+++ b/superset-frontend/src/dashboard/components/DashboardGrid.jsx
@@ -38,6 +38,16 @@
 
 const defaultProps = {};
 
+const renderDraggableContentBottom = dropProps =>
+  dropProps.dropIndicatorProps && (
+    <div className="drop-indicator drop-indicator--bottom" />
+  );
+
+const renderDraggableContentTop = dropProps =>
+  dropProps.dropIndicatorProps && (
+    <div className="drop-indicator drop-indicator--top" />
+  );
+
 class DashboardGrid extends React.PureComponent {
   constructor(props) {
     super(props);
@@ -144,11 +154,7 @@
               className="empty-droptarget"
               editMode
             >
-              {({ dropIndicatorProps }) =>
-                dropIndicatorProps && (
-                  <div className="drop-indicator drop-indicator--bottom" />
-                )
-              }
+              {renderDraggableContentBottom}
             </DragDroppable>
           )}
 
@@ -181,11 +187,7 @@
               className="empty-droptarget"
               editMode
             >
-              {({ dropIndicatorProps }) =>
-                dropIndicatorProps && (
-                  <div className="drop-indicator drop-indicator--top" />
-                )
-              }
+              {renderDraggableContentTop}
             </DragDroppable>
           )}
 
diff --git a/superset-frontend/src/dashboard/components/dnd/DragDroppable.jsx b/superset-frontend/src/dashboard/components/dnd/DragDroppable.jsx
index a841be7..a29d047 100644
--- a/superset-frontend/src/dashboard/components/dnd/DragDroppable.jsx
+++ b/superset-frontend/src/dashboard/components/dnd/DragDroppable.jsx
@@ -66,7 +66,7 @@
 };
 
 // export unwrapped component for testing
-export class UnwrappedDragDroppable extends React.Component {
+export class UnwrappedDragDroppable extends React.PureComponent {
   constructor(props) {
     super(props);
     this.state = {
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx
index faa1e04..17691b8 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx
@@ -74,6 +74,16 @@
   `}
 `;
 
+const renderDraggableContentBottom = dropProps =>
+  dropProps.dropIndicatorProps && (
+    <div className="drop-indicator drop-indicator--bottom" />
+  );
+
+const renderDraggableContentTop = dropProps =>
+  dropProps.dropIndicatorProps && (
+    <div className="drop-indicator drop-indicator--top" />
+  );
+
 export default class Tab extends React.PureComponent {
   constructor(props) {
     super(props);
@@ -148,11 +158,7 @@
             editMode
             className="empty-droptarget"
           >
-            {({ dropIndicatorProps }) =>
-              dropIndicatorProps && (
-                <div className="drop-indicator drop-indicator--top" />
-              )
-            }
+            {renderDraggableContentTop}
           </DragDroppable>
         )}
         {tabComponent.children.map((componentId, componentIndex) => (
@@ -184,11 +190,7 @@
             editMode
             className="empty-droptarget"
           >
-            {({ dropIndicatorProps }) =>
-              dropIndicatorProps && (
-                <div className="drop-indicator drop-indicator--bottom" />
-              )
-            }
+            {renderDraggableContentBottom}
           </DragDroppable>
         )}
       </div>
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
index 9916520..1966dd5 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
@@ -129,6 +129,7 @@
     this.handleDeleteComponent = this.handleDeleteComponent.bind(this);
     this.handleDeleteTab = this.handleDeleteTab.bind(this);
     this.handleDropOnTab = this.handleDropOnTab.bind(this);
+    this.handleDrop = this.handleDrop.bind(this);
   }
 
   componentDidMount() {
@@ -281,6 +282,12 @@
     }
   }
 
+  handleDrop(dropResult) {
+    if (dropResult.dragging.type !== TABS_TYPE) {
+      this.props.handleComponentDrop(dropResult);
+    }
+  }
+
   render() {
     const {
       depth,
@@ -292,7 +299,6 @@
       onResizeStart,
       onResize,
       onResizeStop,
-      handleComponentDrop,
       renderTabContent,
       renderHoverMenu,
       isComponentVisible: isCurrentTabVisible,
@@ -315,11 +321,7 @@
         orientation="row"
         index={index}
         depth={depth}
-        onDrop={dropResult => {
-          if (dropResult.dragging.type !== TABS_TYPE) {
-            handleComponentDrop(dropResult);
-          }
-        }}
+        onDrop={this.handleDrop}
         editMode={editMode}
       >
         {({