lyndsiWilliams commented on code in PR #21320:
URL: https://github.com/apache/superset/pull/21320#discussion_r963910408


##########
superset-frontend/src/SqlLab/components/SqlEditor/index.jsx:
##########
@@ -18,16 +18,16 @@
  */
 /* eslint-disable jsx-a11y/anchor-is-valid */
 /* eslint-disable jsx-a11y/no-static-element-interactions */
-import React from 'react';
+/* eslint no-use-before-define: 0 */

Review Comment:
   Instead of using this, rearrange the functions so that they're defined 
before used.



##########
superset-frontend/src/SqlLab/components/SqlEditor/index.jsx:
##########
@@ -140,172 +139,158 @@ const StyledSidebar = styled.div`
 
 const propTypes = {
   actions: PropTypes.object.isRequired,
-  database: PropTypes.object,
-  latestQuery: PropTypes.object,
   tables: PropTypes.array.isRequired,
   editorQueries: PropTypes.array.isRequired,
   dataPreviewQueries: PropTypes.array.isRequired,
   queryEditor: PropTypes.object.isRequired,
-  hideLeftBar: PropTypes.bool,
   defaultQueryLimit: PropTypes.number.isRequired,
   maxRow: PropTypes.number.isRequired,
   displayLimit: PropTypes.number.isRequired,
   saveQueryWarning: PropTypes.string,
   scheduleQueryWarning: PropTypes.string,
 };
 
-const defaultProps = {
-  database: null,
-  latestQuery: null,
-  hideLeftBar: false,
-  scheduleQueryWarning: null,
-};
-
-class SqlEditor extends React.PureComponent {
-  constructor(props) {
-    super(props);
-    this.state = {
-      autorun: props.queryEditor.autorun,
-      ctas: '',
-      northPercent: props.queryEditor.northPercent || INITIAL_NORTH_PERCENT,
-      southPercent: props.queryEditor.southPercent || INITIAL_SOUTH_PERCENT,
-      autocompleteEnabled: getItem(
-        LocalStorageKeys.sqllab__is_autocomplete_enabled,
-        true,
-      ),
-      showCreateAsModal: false,
-      createAs: '',
-      showEmptyState: false,
-    };
-    this.sqlEditorRef = React.createRef();
-    this.northPaneRef = React.createRef();
-
-    this.elementStyle = this.elementStyle.bind(this);
-    this.onResizeStart = this.onResizeStart.bind(this);
-    this.onResizeEnd = this.onResizeEnd.bind(this);
-    this.canValidateQuery = this.canValidateQuery.bind(this);
-    this.runQuery = this.runQuery.bind(this);
-    this.setEmptyState = this.setEmptyState.bind(this);
-    this.stopQuery = this.stopQuery.bind(this);
-    this.saveQuery = this.saveQuery.bind(this);
-    this.onSqlChanged = this.onSqlChanged.bind(this);
-    this.setQueryEditorAndSaveSql = this.setQueryEditorAndSaveSql.bind(this);
-    this.setQueryEditorAndSaveSqlWithDebounce = debounce(
-      this.setQueryEditorAndSaveSql.bind(this),
-      SET_QUERY_EDITOR_SQL_DEBOUNCE_MS,
-    );
-    this.queryPane = this.queryPane.bind(this);
-    this.getHotkeyConfig = this.getHotkeyConfig.bind(this);
-    this.getAceEditorAndSouthPaneHeights =
-      this.getAceEditorAndSouthPaneHeights.bind(this);
-    this.getSqlEditorHeight = this.getSqlEditorHeight.bind(this);
-    this.requestValidation = debounce(
-      this.requestValidation.bind(this),
-      VALIDATION_DEBOUNCE_MS,
-    );
-    this.getQueryCostEstimate = this.getQueryCostEstimate.bind(this);
-    this.handleWindowResize = throttle(
-      this.handleWindowResize.bind(this),
-      WINDOW_RESIZE_THROTTLE_MS,
-    );
+const SqlEditor = ({
+  actions,
+  tables,
+  editorQueries,
+  dataPreviewQueries,
+  queryEditor,
+  defaultQueryLimit,
+  maxRow,
+  displayLimit,
+  saveQueryWarning,
+  scheduleQueryWarning = null,
+}) => {
+  const theme = useTheme();
+  const dispatch = useDispatch();
+
+  const { database, latestQuery, hideLeftBar } = useSelector(
+    ({ sqlLab: { unsavedQueryEditor, databases, queries } }) => {
+      let { dbId, latestQueryId, hideLeftBar } = queryEditor;
+      if (unsavedQueryEditor.id === queryEditor.id) {
+        dbId = unsavedQueryEditor.dbId || dbId;
+        latestQueryId = unsavedQueryEditor.latestQueryId || latestQueryId;
+        hideLeftBar = unsavedQueryEditor.hideLeftBar || hideLeftBar;
+      }
+      return {
+        database: databases[dbId],
+        latestQuery: queries[latestQueryId],
+        hideLeftBar,
+      };
+    },
+  );
 
-    this.onBeforeUnload = this.onBeforeUnload.bind(this);
-    this.renderDropdown = this.renderDropdown.bind(this);
-  }
+  const queryEditors = useSelector(({ sqlLab }) => sqlLab.queryEditors);
 
-  UNSAFE_componentWillMount() {
-    if (this.state.autorun) {
-      this.setState({ autorun: false });
-      this.props.queryEditorSetAutorun(this.props.queryEditor, false);
-      this.startQuery();
+  const [height, setHeight] = useState(0);
+  const [autorun, setAutorun] = useState(queryEditor.autorun);
+  const [ctas, setCtas] = useState('');
+  const [northPercent, setNorthPercent] = useState(
+    queryEditor.northPercent || INITIAL_NORTH_PERCENT,
+  );
+  const [southPercent, setSouthPercent] = useState(
+    queryEditor.southPercent || INITIAL_SOUTH_PERCENT,
+  );
+  const [autocompleteEnabled, setAutocompleteEnabled] = useState(
+    getItem(LocalStorageKeys.sqllab__is_autocomplete_enabled, true),
+  );
+  const [showCreateAsModal, setShowCreateAsModal] = useState(false);
+  const [createAs, setCreateAs] = useState('');
+  const [showEmptyState, setShowEmptyState] = useState(false);
+
+  const sqlEditorRef = useRef(null);
+  const northPaneRef = useRef(null);
+
+  useState(() => {
+    if (autorun) {
+      setAutorun(false);
+      dispatch(queryEditorSetAutorun(queryEditor, false));
+      startQuery();
     }
-  }
+  });
 
-  componentDidMount() {
+  useEffect(() => {
     // We need to measure the height of the sql editor post render to figure 
the height of
     // the south pane so it gets rendered properly
-    // eslint-disable-next-line react/no-did-mount-set-state
-    const db = this.props.database;
-    this.setState({ height: this.getSqlEditorHeight() });
-    if (!db || isEmpty(db)) {
-      this.setEmptyState(true);
+    setHeight(getSqlEditorHeight());
+    if (!database || isEmpty(database)) {
+      setShowEmptyState(true);
     }
 
-    window.addEventListener('resize', this.handleWindowResize);
-    window.addEventListener('beforeunload', this.onBeforeUnload);
+    window.addEventListener('resize', handleWindowResizeWithThrottle);
+    window.addEventListener('beforeunload', onBeforeUnload);
 
     // setup hotkeys
-    const hotkeys = this.getHotkeyConfig();
+    const hotkeys = getHotkeyConfig();
     hotkeys.forEach(keyConfig => {
       Mousetrap.bind([keyConfig.key], keyConfig.func);
     });
-  }
 
-  componentWillUnmount() {
-    window.removeEventListener('resize', this.handleWindowResize);
-    window.removeEventListener('beforeunload', this.onBeforeUnload);
-  }
+    return () => {
+      window.removeEventListener('resize', handleWindowResizeWithThrottle);
+      window.removeEventListener('beforeunload', onBeforeUnload);
+    };
+  }, []);
 
-  onResizeStart() {
+  const onResizeStart = () => {
     // Set the heights on the ace editor and the ace content area after drag 
starts
     // to smooth out the visual transition to the new heights when drag ends
     document.getElementsByClassName('ace_content')[0].style.height = '100%';
-  }
+  };
 
-  onResizeEnd([northPercent, southPercent]) {
-    this.setState({ northPercent, southPercent });
+  const onResizeEnd = ([northPercent, southPercent]) => {
+    setNorthPercent(northPercent);
+    setSouthPercent(southPercent);
 
-    if (this.northPaneRef.current && this.northPaneRef.current.clientHeight) {
-      this.props.persistEditorHeight(
-        this.props.queryEditor,
-        northPercent,
-        southPercent,
-      );
+    if (northPaneRef.current && northPaneRef.current.clientHeight) {

Review Comment:
   ```suggestion
       if (northPaneRef?.current?.clientHeight) {
   ```
   This can be shortened with optional chaining



##########
superset-frontend/src/SqlLab/components/SqlEditor/index.jsx:
##########
@@ -614,214 +579,145 @@ class SqlEditor extends React.PureComponent {
         <div className="leftItems">
           <span>
             <RunQueryActionButton
-              allowAsync={
-                this.props.database
-                  ? this.props.database.allow_run_async
-                  : false
-              }
-              queryEditor={qe}
-              queryState={this.props.latestQuery?.state}
-              runQuery={this.runQuery}
-              stopQuery={this.stopQuery}
+              allowAsync={database ? database.allow_run_async : false}
+              queryEditor={queryEditor}
+              queryState={latestQuery?.state}
+              runQuery={startQuery}
+              stopQuery={stopQuery}
               overlayCreateAsMenu={showMenu ? runMenuBtn : null}
             />
           </span>
           {isFeatureEnabled(FeatureFlag.ESTIMATE_QUERY_COST) &&
-            this.props.database &&
-            this.props.database.allows_cost_estimate && (
+            database?.allows_cost_estimate && (
               <span>
                 <EstimateQueryCostButton
-                  getEstimate={this.getQueryCostEstimate}
-                  queryEditor={qe}
+                  getEstimate={getQueryCostEstimate}
+                  queryEditor={queryEditor}
                   tooltip={t('Estimate the cost before running a query')}
                 />
               </span>
             )}
           <span>
             <QueryLimitSelect
-              queryEditor={this.props.queryEditor}
-              maxRow={this.props.maxRow}
-              defaultQueryLimit={this.props.defaultQueryLimit}
+              queryEditor={queryEditor}
+              maxRow={maxRow}
+              defaultQueryLimit={defaultQueryLimit}
             />
           </span>
-          {this.props.latestQuery && (
+          {latestQuery && (
             <Timer
-              startTime={this.props.latestQuery.startDttm}
-              endTime={this.props.latestQuery.endDttm}
-              state={STATE_TYPE_MAP[this.props.latestQuery.state]}
-              isRunning={this.props.latestQuery.state === 'running'}
+              startTime={latestQuery.startDttm}
+              endTime={latestQuery.endDttm}
+              state={STATE_TYPE_MAP[latestQuery.state]}
+              isRunning={latestQuery.state === 'running'}
             />
           )}
         </div>
         <div className="rightItems">
           <span>
             <SaveQuery
-              queryEditor={qe}
-              columns={this.props.latestQuery?.results?.columns || []}
-              onSave={this.saveQuery}
-              onUpdate={this.props.actions.updateSavedQuery}
-              saveQueryWarning={this.props.saveQueryWarning}
-              database={this.props.database}
+              queryEditor={queryEditor}
+              columns={latestQuery?.results?.columns || []}
+              onSave={onSaveQuery}
+              onUpdate={dispatch(updateSavedQuery)}
+              saveQueryWarning={saveQueryWarning}
+              database={database}
             />
           </span>
           <span>
-            <ShareSqlLabQuery queryEditor={qe} />
+            <ShareSqlLabQuery queryEditor={queryEditor} />
           </span>
-          <AntdDropdown overlay={this.renderDropdown()} trigger="click">
+          <AntdDropdown overlay={renderDropdown()} trigger="click">
             <Icons.MoreHoriz iconColor={theme.colors.grayscale.base} />
           </AntdDropdown>
         </div>
       </StyledToolbar>
     );
-  }
+  };
 
-  render() {
-    const createViewModalTitle =
-      this.state.createAs === CtasEnum.VIEW
-        ? 'CREATE VIEW AS'
-        : 'CREATE TABLE AS';
-
-    const createModalPlaceHolder =
-      this.state.createAs === CtasEnum.VIEW
-        ? 'Specify name to CREATE VIEW AS schema in: public'
-        : 'Specify name to CREATE TABLE AS schema in: public';
-    const leftBarStateClass = this.props.hideLeftBar
-      ? 'schemaPane-exit-done'
-      : 'schemaPane-enter-done';
-    return (
-      <div ref={this.sqlEditorRef} className="SqlEditor">
-        <CSSTransition
-          classNames="schemaPane"
-          in={!this.props.hideLeftBar}
-          timeout={300}
+  const createViewModalTitle =
+    createAs === CtasEnum.VIEW ? 'CREATE VIEW AS' : 'CREATE TABLE AS';
+
+  const createModalPlaceHolder =
+    createAs === CtasEnum.VIEW
+      ? t('Specify name to CREATE VIEW AS schema in: public')
+      : t('Specify name to CREATE TABLE AS schema in: public');
+
+  const leftBarStateClass = hideLeftBar
+    ? 'schemaPane-exit-done'
+    : 'schemaPane-enter-done';
+  return (
+    <div ref={sqlEditorRef} className="SqlEditor">
+      <CSSTransition classNames="schemaPane" in={!hideLeftBar} timeout={300}>
+        <ResizableSidebar
+          id={`sqllab:${queryEditor.id}`}
+          minWidth={SQL_EDITOR_LEFTBAR_WIDTH}
+          initialWidth={SQL_EDITOR_LEFTBAR_WIDTH}
+          enable={!hideLeftBar}
         >
-          <ResizableSidebar
-            id={`sqllab:${this.props.queryEditor.id}`}
-            minWidth={SQL_EDITOR_LEFTBAR_WIDTH}
-            initialWidth={SQL_EDITOR_LEFTBAR_WIDTH}
-            enable={!this.props.hideLeftBar}
-          >
-            {adjustedWidth => (
-              <StyledSidebar
-                className={`schemaPane ${leftBarStateClass}`}
-                width={adjustedWidth}
-                hide={this.props.hideLeftBar}
+          {adjustedWidth => (
+            <StyledSidebar
+              className={`schemaPane ${leftBarStateClass}`}
+              width={adjustedWidth}
+              hide={hideLeftBar}
+            >
+              <SqlEditorLeftBar
+                database={database}
+                queryEditor={queryEditor}
+                tables={tables}
+                actions={actions}
+                setEmptyState={bool => setShowEmptyState(bool)}
+              />
+            </StyledSidebar>
+          )}
+        </ResizableSidebar>
+      </CSSTransition>
+      {showEmptyState ? (
+        <EmptyStateBig
+          image="vector.svg"
+          title={t('Select a database to write a query')}
+          description={t(
+            'Choose one of the available databases from the panel on the 
left.',
+          )}
+        />
+      ) : (
+        queryPane()
+      )}
+      <Modal
+        visible={showCreateAsModal}
+        title={t(createViewModalTitle)}
+        onHide={() => setShowCreateAsModal(false)}
+        footer={
+          <>
+            <Button onClick={() => setShowCreateAsModal(false)}>Cancel</Button>
+            {createAs === CtasEnum.TABLE && (
+              <Button
+                buttonStyle="primary"
+                disabled={ctas.length === 0}
+                onClick={createTableAs}
               >
-                <SqlEditorLeftBar
-                  database={this.props.database}
-                  queryEditor={this.props.queryEditor}
-                  tables={this.props.tables}
-                  actions={this.props.actions}
-                  setEmptyState={this.setEmptyState}
-                />
-              </StyledSidebar>
-            )}
-          </ResizableSidebar>
-        </CSSTransition>
-        {this.state.showEmptyState ? (
-          <EmptyStateBig
-            image="vector.svg"
-            title={t('Select a database to write a query')}
-            description={t(
-              'Choose one of the available databases from the panel on the 
left.',
+                Create
+              </Button>
             )}
-          />
-        ) : (
-          this.queryPane()
-        )}
-        <StyledModal
-          visible={this.state.showCreateAsModal}
-          title={t(createViewModalTitle)}
-          onHide={() => {
-            this.setState({ showCreateAsModal: false });
-          }}
-          footer={
-            <>
+            {createAs === CtasEnum.VIEW && (
               <Button
-                onClick={() => this.setState({ showCreateAsModal: false })}
+                buttonStyle="primary"
+                disabled={ctas.length === 0}
+                onClick={createViewAs}
               >
-                Cancel
+                Create

Review Comment:
   ```suggestion
                   {t('Create')}
   ```
   This string needs to be translated



##########
superset-frontend/src/SqlLab/components/SqlEditor/index.jsx:
##########
@@ -614,214 +579,145 @@ class SqlEditor extends React.PureComponent {
         <div className="leftItems">
           <span>
             <RunQueryActionButton
-              allowAsync={
-                this.props.database
-                  ? this.props.database.allow_run_async
-                  : false
-              }
-              queryEditor={qe}
-              queryState={this.props.latestQuery?.state}
-              runQuery={this.runQuery}
-              stopQuery={this.stopQuery}
+              allowAsync={database ? database.allow_run_async : false}
+              queryEditor={queryEditor}
+              queryState={latestQuery?.state}
+              runQuery={startQuery}
+              stopQuery={stopQuery}
               overlayCreateAsMenu={showMenu ? runMenuBtn : null}
             />
           </span>
           {isFeatureEnabled(FeatureFlag.ESTIMATE_QUERY_COST) &&
-            this.props.database &&
-            this.props.database.allows_cost_estimate && (
+            database?.allows_cost_estimate && (
               <span>
                 <EstimateQueryCostButton
-                  getEstimate={this.getQueryCostEstimate}
-                  queryEditor={qe}
+                  getEstimate={getQueryCostEstimate}
+                  queryEditor={queryEditor}
                   tooltip={t('Estimate the cost before running a query')}
                 />
               </span>
             )}
           <span>
             <QueryLimitSelect
-              queryEditor={this.props.queryEditor}
-              maxRow={this.props.maxRow}
-              defaultQueryLimit={this.props.defaultQueryLimit}
+              queryEditor={queryEditor}
+              maxRow={maxRow}
+              defaultQueryLimit={defaultQueryLimit}
             />
           </span>
-          {this.props.latestQuery && (
+          {latestQuery && (
             <Timer
-              startTime={this.props.latestQuery.startDttm}
-              endTime={this.props.latestQuery.endDttm}
-              state={STATE_TYPE_MAP[this.props.latestQuery.state]}
-              isRunning={this.props.latestQuery.state === 'running'}
+              startTime={latestQuery.startDttm}
+              endTime={latestQuery.endDttm}
+              state={STATE_TYPE_MAP[latestQuery.state]}
+              isRunning={latestQuery.state === 'running'}
             />
           )}
         </div>
         <div className="rightItems">
           <span>
             <SaveQuery
-              queryEditor={qe}
-              columns={this.props.latestQuery?.results?.columns || []}
-              onSave={this.saveQuery}
-              onUpdate={this.props.actions.updateSavedQuery}
-              saveQueryWarning={this.props.saveQueryWarning}
-              database={this.props.database}
+              queryEditor={queryEditor}
+              columns={latestQuery?.results?.columns || []}
+              onSave={onSaveQuery}
+              onUpdate={dispatch(updateSavedQuery)}
+              saveQueryWarning={saveQueryWarning}
+              database={database}
             />
           </span>
           <span>
-            <ShareSqlLabQuery queryEditor={qe} />
+            <ShareSqlLabQuery queryEditor={queryEditor} />
           </span>
-          <AntdDropdown overlay={this.renderDropdown()} trigger="click">
+          <AntdDropdown overlay={renderDropdown()} trigger="click">
             <Icons.MoreHoriz iconColor={theme.colors.grayscale.base} />
           </AntdDropdown>
         </div>
       </StyledToolbar>
     );
-  }
+  };
 
-  render() {
-    const createViewModalTitle =
-      this.state.createAs === CtasEnum.VIEW
-        ? 'CREATE VIEW AS'
-        : 'CREATE TABLE AS';
-
-    const createModalPlaceHolder =
-      this.state.createAs === CtasEnum.VIEW
-        ? 'Specify name to CREATE VIEW AS schema in: public'
-        : 'Specify name to CREATE TABLE AS schema in: public';
-    const leftBarStateClass = this.props.hideLeftBar
-      ? 'schemaPane-exit-done'
-      : 'schemaPane-enter-done';
-    return (
-      <div ref={this.sqlEditorRef} className="SqlEditor">
-        <CSSTransition
-          classNames="schemaPane"
-          in={!this.props.hideLeftBar}
-          timeout={300}
+  const createViewModalTitle =
+    createAs === CtasEnum.VIEW ? 'CREATE VIEW AS' : 'CREATE TABLE AS';
+
+  const createModalPlaceHolder =
+    createAs === CtasEnum.VIEW
+      ? t('Specify name to CREATE VIEW AS schema in: public')
+      : t('Specify name to CREATE TABLE AS schema in: public');
+
+  const leftBarStateClass = hideLeftBar
+    ? 'schemaPane-exit-done'
+    : 'schemaPane-enter-done';
+  return (
+    <div ref={sqlEditorRef} className="SqlEditor">
+      <CSSTransition classNames="schemaPane" in={!hideLeftBar} timeout={300}>
+        <ResizableSidebar
+          id={`sqllab:${queryEditor.id}`}
+          minWidth={SQL_EDITOR_LEFTBAR_WIDTH}
+          initialWidth={SQL_EDITOR_LEFTBAR_WIDTH}
+          enable={!hideLeftBar}
         >
-          <ResizableSidebar
-            id={`sqllab:${this.props.queryEditor.id}`}
-            minWidth={SQL_EDITOR_LEFTBAR_WIDTH}
-            initialWidth={SQL_EDITOR_LEFTBAR_WIDTH}
-            enable={!this.props.hideLeftBar}
-          >
-            {adjustedWidth => (
-              <StyledSidebar
-                className={`schemaPane ${leftBarStateClass}`}
-                width={adjustedWidth}
-                hide={this.props.hideLeftBar}
+          {adjustedWidth => (
+            <StyledSidebar
+              className={`schemaPane ${leftBarStateClass}`}
+              width={adjustedWidth}
+              hide={hideLeftBar}
+            >
+              <SqlEditorLeftBar
+                database={database}
+                queryEditor={queryEditor}
+                tables={tables}
+                actions={actions}
+                setEmptyState={bool => setShowEmptyState(bool)}
+              />
+            </StyledSidebar>
+          )}
+        </ResizableSidebar>
+      </CSSTransition>
+      {showEmptyState ? (
+        <EmptyStateBig
+          image="vector.svg"
+          title={t('Select a database to write a query')}
+          description={t(
+            'Choose one of the available databases from the panel on the 
left.',
+          )}
+        />
+      ) : (
+        queryPane()
+      )}
+      <Modal
+        visible={showCreateAsModal}
+        title={t(createViewModalTitle)}
+        onHide={() => setShowCreateAsModal(false)}
+        footer={
+          <>
+            <Button onClick={() => setShowCreateAsModal(false)}>Cancel</Button>
+            {createAs === CtasEnum.TABLE && (
+              <Button
+                buttonStyle="primary"
+                disabled={ctas.length === 0}
+                onClick={createTableAs}
               >
-                <SqlEditorLeftBar
-                  database={this.props.database}
-                  queryEditor={this.props.queryEditor}
-                  tables={this.props.tables}
-                  actions={this.props.actions}
-                  setEmptyState={this.setEmptyState}
-                />
-              </StyledSidebar>
-            )}
-          </ResizableSidebar>
-        </CSSTransition>
-        {this.state.showEmptyState ? (
-          <EmptyStateBig
-            image="vector.svg"
-            title={t('Select a database to write a query')}
-            description={t(
-              'Choose one of the available databases from the panel on the 
left.',
+                Create

Review Comment:
   ```suggestion
                   {t('Create')}
   ```
   This string needs to be translated



##########
superset-frontend/src/SqlLab/components/SqlEditor/index.jsx:
##########
@@ -614,214 +579,145 @@ class SqlEditor extends React.PureComponent {
         <div className="leftItems">
           <span>
             <RunQueryActionButton
-              allowAsync={
-                this.props.database
-                  ? this.props.database.allow_run_async
-                  : false
-              }
-              queryEditor={qe}
-              queryState={this.props.latestQuery?.state}
-              runQuery={this.runQuery}
-              stopQuery={this.stopQuery}
+              allowAsync={database ? database.allow_run_async : false}
+              queryEditor={queryEditor}
+              queryState={latestQuery?.state}
+              runQuery={startQuery}
+              stopQuery={stopQuery}
               overlayCreateAsMenu={showMenu ? runMenuBtn : null}
             />
           </span>
           {isFeatureEnabled(FeatureFlag.ESTIMATE_QUERY_COST) &&
-            this.props.database &&
-            this.props.database.allows_cost_estimate && (
+            database?.allows_cost_estimate && (
               <span>
                 <EstimateQueryCostButton
-                  getEstimate={this.getQueryCostEstimate}
-                  queryEditor={qe}
+                  getEstimate={getQueryCostEstimate}
+                  queryEditor={queryEditor}
                   tooltip={t('Estimate the cost before running a query')}
                 />
               </span>
             )}
           <span>
             <QueryLimitSelect
-              queryEditor={this.props.queryEditor}
-              maxRow={this.props.maxRow}
-              defaultQueryLimit={this.props.defaultQueryLimit}
+              queryEditor={queryEditor}
+              maxRow={maxRow}
+              defaultQueryLimit={defaultQueryLimit}
             />
           </span>
-          {this.props.latestQuery && (
+          {latestQuery && (
             <Timer
-              startTime={this.props.latestQuery.startDttm}
-              endTime={this.props.latestQuery.endDttm}
-              state={STATE_TYPE_MAP[this.props.latestQuery.state]}
-              isRunning={this.props.latestQuery.state === 'running'}
+              startTime={latestQuery.startDttm}
+              endTime={latestQuery.endDttm}
+              state={STATE_TYPE_MAP[latestQuery.state]}
+              isRunning={latestQuery.state === 'running'}
             />
           )}
         </div>
         <div className="rightItems">
           <span>
             <SaveQuery
-              queryEditor={qe}
-              columns={this.props.latestQuery?.results?.columns || []}
-              onSave={this.saveQuery}
-              onUpdate={this.props.actions.updateSavedQuery}
-              saveQueryWarning={this.props.saveQueryWarning}
-              database={this.props.database}
+              queryEditor={queryEditor}
+              columns={latestQuery?.results?.columns || []}
+              onSave={onSaveQuery}
+              onUpdate={dispatch(updateSavedQuery)}
+              saveQueryWarning={saveQueryWarning}
+              database={database}
             />
           </span>
           <span>
-            <ShareSqlLabQuery queryEditor={qe} />
+            <ShareSqlLabQuery queryEditor={queryEditor} />
           </span>
-          <AntdDropdown overlay={this.renderDropdown()} trigger="click">
+          <AntdDropdown overlay={renderDropdown()} trigger="click">
             <Icons.MoreHoriz iconColor={theme.colors.grayscale.base} />
           </AntdDropdown>
         </div>
       </StyledToolbar>
     );
-  }
+  };
 
-  render() {
-    const createViewModalTitle =
-      this.state.createAs === CtasEnum.VIEW
-        ? 'CREATE VIEW AS'
-        : 'CREATE TABLE AS';
-
-    const createModalPlaceHolder =
-      this.state.createAs === CtasEnum.VIEW
-        ? 'Specify name to CREATE VIEW AS schema in: public'
-        : 'Specify name to CREATE TABLE AS schema in: public';
-    const leftBarStateClass = this.props.hideLeftBar
-      ? 'schemaPane-exit-done'
-      : 'schemaPane-enter-done';
-    return (
-      <div ref={this.sqlEditorRef} className="SqlEditor">
-        <CSSTransition
-          classNames="schemaPane"
-          in={!this.props.hideLeftBar}
-          timeout={300}
+  const createViewModalTitle =
+    createAs === CtasEnum.VIEW ? 'CREATE VIEW AS' : 'CREATE TABLE AS';
+
+  const createModalPlaceHolder =
+    createAs === CtasEnum.VIEW
+      ? t('Specify name to CREATE VIEW AS schema in: public')
+      : t('Specify name to CREATE TABLE AS schema in: public');
+
+  const leftBarStateClass = hideLeftBar
+    ? 'schemaPane-exit-done'
+    : 'schemaPane-enter-done';
+  return (
+    <div ref={sqlEditorRef} className="SqlEditor">
+      <CSSTransition classNames="schemaPane" in={!hideLeftBar} timeout={300}>
+        <ResizableSidebar
+          id={`sqllab:${queryEditor.id}`}
+          minWidth={SQL_EDITOR_LEFTBAR_WIDTH}
+          initialWidth={SQL_EDITOR_LEFTBAR_WIDTH}
+          enable={!hideLeftBar}
         >
-          <ResizableSidebar
-            id={`sqllab:${this.props.queryEditor.id}`}
-            minWidth={SQL_EDITOR_LEFTBAR_WIDTH}
-            initialWidth={SQL_EDITOR_LEFTBAR_WIDTH}
-            enable={!this.props.hideLeftBar}
-          >
-            {adjustedWidth => (
-              <StyledSidebar
-                className={`schemaPane ${leftBarStateClass}`}
-                width={adjustedWidth}
-                hide={this.props.hideLeftBar}
+          {adjustedWidth => (
+            <StyledSidebar
+              className={`schemaPane ${leftBarStateClass}`}
+              width={adjustedWidth}
+              hide={hideLeftBar}
+            >
+              <SqlEditorLeftBar
+                database={database}
+                queryEditor={queryEditor}
+                tables={tables}
+                actions={actions}
+                setEmptyState={bool => setShowEmptyState(bool)}
+              />
+            </StyledSidebar>
+          )}
+        </ResizableSidebar>
+      </CSSTransition>
+      {showEmptyState ? (
+        <EmptyStateBig
+          image="vector.svg"
+          title={t('Select a database to write a query')}
+          description={t(
+            'Choose one of the available databases from the panel on the 
left.',
+          )}
+        />
+      ) : (
+        queryPane()
+      )}
+      <Modal
+        visible={showCreateAsModal}
+        title={t(createViewModalTitle)}
+        onHide={() => setShowCreateAsModal(false)}
+        footer={
+          <>
+            <Button onClick={() => setShowCreateAsModal(false)}>Cancel</Button>
+            {createAs === CtasEnum.TABLE && (
+              <Button
+                buttonStyle="primary"
+                disabled={ctas.length === 0}
+                onClick={createTableAs}
               >
-                <SqlEditorLeftBar
-                  database={this.props.database}
-                  queryEditor={this.props.queryEditor}
-                  tables={this.props.tables}
-                  actions={this.props.actions}
-                  setEmptyState={this.setEmptyState}
-                />
-              </StyledSidebar>
-            )}
-          </ResizableSidebar>
-        </CSSTransition>
-        {this.state.showEmptyState ? (
-          <EmptyStateBig
-            image="vector.svg"
-            title={t('Select a database to write a query')}
-            description={t(
-              'Choose one of the available databases from the panel on the 
left.',
+                Create
+              </Button>
             )}
-          />
-        ) : (
-          this.queryPane()
-        )}
-        <StyledModal
-          visible={this.state.showCreateAsModal}
-          title={t(createViewModalTitle)}
-          onHide={() => {
-            this.setState({ showCreateAsModal: false });
-          }}
-          footer={
-            <>
+            {createAs === CtasEnum.VIEW && (
               <Button
-                onClick={() => this.setState({ showCreateAsModal: false })}
+                buttonStyle="primary"
+                disabled={ctas.length === 0}
+                onClick={createViewAs}
               >
-                Cancel
+                Create
               </Button>
-              {this.state.createAs === CtasEnum.TABLE && (
-                <Button
-                  buttonStyle="primary"
-                  disabled={this.state.ctas.length === 0}
-                  onClick={this.createTableAs.bind(this)}
-                >
-                  Create
-                </Button>
-              )}
-              {this.state.createAs === CtasEnum.VIEW && (
-                <Button
-                  buttonStyle="primary"
-                  disabled={this.state.ctas.length === 0}
-                  onClick={this.createViewAs.bind(this)}
-                >
-                  Create
-                </Button>
-              )}
-            </>
-          }
-        >
-          <span>Name</span>
-          <Input
-            placeholder={createModalPlaceHolder}
-            onChange={this.ctasChanged.bind(this)}
-          />
-        </StyledModal>
-      </div>
-    );
-  }
-}
-SqlEditor.defaultProps = defaultProps;
-SqlEditor.propTypes = propTypes;
-
-function mapStateToProps({ sqlLab }, { queryEditor }) {
-  let { latestQueryId, dbId, hideLeftBar } = queryEditor;
-  if (sqlLab.unsavedQueryEditor.id === queryEditor.id) {
-    const {
-      latestQueryId: unsavedQID,
-      dbId: unsavedDBID,
-      hideLeftBar: unsavedHideLeftBar,
-    } = sqlLab.unsavedQueryEditor;
-    latestQueryId = unsavedQID || latestQueryId;
-    dbId = unsavedDBID || dbId;
-    hideLeftBar = unsavedHideLeftBar || hideLeftBar;
-  }
-  const database = sqlLab.databases[dbId];
-  const latestQuery = sqlLab.queries[latestQueryId];
-
-  return {
-    hideLeftBar,
-    queryEditors: sqlLab.queryEditors,
-    latestQuery,
-    database,
-  };
-}
-
-function mapDispatchToProps(dispatch) {
-  return bindActionCreators(
-    {
-      addQueryEditor,
-      estimateQueryCost,
-      persistEditorHeight,
-      postStopQuery,
-      queryEditorSetAutorun,
-      queryEditorSetSql,
-      queryEditorSetAndSaveSql,
-      queryEditorSetTemplateParams,
-      runQueryFromSqlEditor,
-      runQuery,
-      saveQuery,
-      addSavedQueryToTabState,
-      scheduleQuery,
-      setActiveSouthPaneTab,
-      updateSavedQuery,
-      validateQuery,
-    },
-    dispatch,
+            )}
+          </>
+        }
+      >
+        <span>Name</span>

Review Comment:
   ```suggestion
           <span>{t('Name')}</span>
   ```
   This string needs to be translated



##########
superset-frontend/src/SqlLab/components/SqlEditor/index.jsx:
##########
@@ -362,176 +349,163 @@ class SqlEditor extends React.PureComponent {
     }
 
     return base;
-  }
+  };
 
-  setEmptyState(bool) {
-    this.setState({ showEmptyState: bool });
-  }
+  const setQueryEditorAndSaveSql = sql => {
+    dispatch(queryEditorSetAndSaveSql(queryEditor, sql));
+  };
 
-  setQueryEditorAndSaveSql(sql) {
-    this.props.queryEditorSetAndSaveSql(this.props.queryEditor, sql);
-  }
+  const setQueryEditorAndSaveSqlWithDebounce = useMemo(
+    () => debounce(setQueryEditorAndSaveSql, SET_QUERY_EDITOR_SQL_DEBOUNCE_MS),
+    [],
+  );
 
-  getQueryCostEstimate() {
-    if (this.props.database) {
-      const qe = this.props.queryEditor;
-      this.props.estimateQueryCost(qe);
+  const getQueryCostEstimate = () => {
+    if (database) {
+      dispatch(estimateQueryCost(queryEditor));
     }
-  }
+  };
 
-  handleToggleAutocompleteEnabled = () => {
-    this.setState(prevState => {
-      setItem(
-        LocalStorageKeys.sqllab__is_autocomplete_enabled,
-        !prevState.autocompleteEnabled,
-      );
-      return {
-        autocompleteEnabled: !prevState.autocompleteEnabled,
-      };
-    });
+  const handleToggleAutocompleteEnabled = () => {
+    setItem(
+      LocalStorageKeys.sqllab__is_autocomplete_enabled,
+      !autocompleteEnabled,
+    );
+    setAutocompleteEnabled(!autocompleteEnabled);
   };
 
-  handleWindowResize() {
-    this.setState({ height: this.getSqlEditorHeight() });
-  }
+  const handleWindowResize = () => {
+    setHeight(getSqlEditorHeight());
+  };
 
-  elementStyle(dimension, elementSize, gutterSize) {
-    return {
-      [dimension]: `calc(${elementSize}% - ${
-        gutterSize + SQL_EDITOR_GUTTER_MARGIN
-      }px)`,
-    };
-  }
+  const handleWindowResizeWithThrottle = useMemo(
+    () => throttle(handleWindowResize, WINDOW_RESIZE_THROTTLE_MS),
+    [],
+  );
+
+  const elementStyle = (dimension, elementSize, gutterSize) => ({
+    [dimension]: `calc(${elementSize}% - ${
+      gutterSize + SQL_EDITOR_GUTTER_MARGIN
+    }px)`,
+  });
 
-  requestValidation(sql) {
-    const { database, queryEditor, validateQuery } = this.props;
+  const requestValidation = sql => {
     if (database) {
-      validateQuery(queryEditor, sql);
+      dispatch(validateQuery(queryEditor, sql));
     }
-  }
+  };
 
-  canValidateQuery() {
+  const requestValidationWithDebounce = useMemo(
+    () => debounce(requestValidation, VALIDATION_DEBOUNCE_MS),
+    [],
+  );
+
+  const canValidateQuery = () => {
     // Check whether or not we can validate the current query based on whether
     // or not the backend has a validator configured for it.
-    if (this.props.database) {
-      return validatorMap.hasOwnProperty(this.props.database.backend);
+    if (database) {
+      return validatorMap.hasOwnProperty(database.backend);
     }
     return false;
-  }
+  };
 
-  runQuery() {
-    if (this.props.database) {
-      this.startQuery();
+  const startQuery = (ctasArg = false, ctas_method = CtasEnum.TABLE) => {
+    if (!database) {
+      return;
     }
-  }
 
-  convertToNumWithSpaces(num) {
-    return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1 ');
-  }
-
-  startQuery(ctas = false, ctas_method = CtasEnum.TABLE) {
-    const {
-      database,
-      runQueryFromSqlEditor,
-      setActiveSouthPaneTab,
-      queryEditor,
-      defaultQueryLimit,
-    } = this.props;
-    runQueryFromSqlEditor(
-      database,
-      queryEditor,
-      defaultQueryLimit,
-      ctas ? this.state.ctas : '',
-      ctas,
-      ctas_method,
+    dispatch(
+      runQueryFromSqlEditor(
+        database,
+        queryEditor,
+        defaultQueryLimit,
+        ctasArg ? ctas : '',
+        ctasArg,
+        ctas_method,
+      ),
     );
-    setActiveSouthPaneTab('Results');
-  }
+    dispatch(setActiveSouthPaneTab('Results'));
+  };
 
-  stopQuery() {
-    if (
-      this.props.latestQuery &&
-      ['running', 'pending'].indexOf(this.props.latestQuery.state) >= 0
-    ) {
-      this.props.postStopQuery(this.props.latestQuery);
+  const stopQuery = () => {
+    if (latestQuery && ['running', 'pending'].indexOf(latestQuery.state) >= 0) 
{
+      dispatch(postStopQuery(latestQuery));
     }
-  }
+  };
 
-  createTableAs() {
-    this.startQuery(true, CtasEnum.TABLE);
-    this.setState({ showCreateAsModal: false, ctas: '' });
-  }
+  const createTableAs = () => {
+    startQuery(true, CtasEnum.TABLE);
+    setShowCreateAsModal(false);
+    setCtas('');
+  };
 
-  createViewAs() {
-    this.startQuery(true, CtasEnum.VIEW);
-    this.setState({ showCreateAsModal: false, ctas: '' });
-  }
+  const createViewAs = () => {
+    startQuery(true, CtasEnum.VIEW);
+    setShowCreateAsModal(false);
+    setCtas('');
+  };
 
-  ctasChanged(event) {
-    this.setState({ ctas: event.target.value });
-  }
+  const ctasChanged = event => {
+    setCtas(event.target.value);
+  };
 
-  queryPane() {
-    const hotkeys = this.getHotkeyConfig();
+  const queryPane = () => {
+    const hotkeys = getHotkeyConfig();
     const { aceEditorHeight, southPaneHeight } =
-      this.getAceEditorAndSouthPaneHeights(
-        this.state.height,
-        this.state.northPercent,
-        this.state.southPercent,
-      );
+      getAceEditorAndSouthPaneHeights(height, northPercent, southPercent);
     return (
       <Split
         expandToMin
         className="queryPane"
-        sizes={[this.state.northPercent, this.state.southPercent]}
-        elementStyle={this.elementStyle}
+        sizes={[northPercent, southPercent]}
+        elementStyle={elementStyle}
         minSize={200}
         direction="vertical"
         gutterSize={SQL_EDITOR_GUTTER_HEIGHT}
-        onDragStart={this.onResizeStart}
-        onDragEnd={this.onResizeEnd}
+        onDragStart={onResizeStart}
+        onDragEnd={onResizeEnd}
       >
-        <div ref={this.northPaneRef} className="north-pane">
+        <div ref={northPaneRef} className="north-pane">
           <AceEditorWrapper
-            actions={this.props.actions}
-            autocomplete={this.state.autocompleteEnabled}
-            onBlur={this.setQueryEditorSql}
-            onChange={this.onSqlChanged}
-            queryEditor={this.props.queryEditor}
-            database={this.props.database}
-            extendedTables={this.props.tables}
+            actions={actions}
+            autocomplete={autocompleteEnabled}
+            onBlur={setQueryEditorAndSaveSql}
+            onChange={onSqlChanged}
+            queryEditor={queryEditor}
+            database={database}
+            extendedTables={tables}
             height={`${aceEditorHeight}px`}
             hotkeys={hotkeys}
           />
-          {this.renderEditorBottomBar(hotkeys)}
+          {renderEditorBottomBar(hotkeys)}
         </div>
         <ConnectedSouthPane
-          editorQueries={this.props.editorQueries}
-          latestQueryId={this.props.latestQuery && this.props.latestQuery.id}
-          dataPreviewQueries={this.props.dataPreviewQueries}
-          actions={this.props.actions}
+          editorQueries={editorQueries}
+          latestQueryId={latestQuery && latestQuery.id}
+          dataPreviewQueries={dataPreviewQueries}
+          actions={actions}
           height={southPaneHeight}
-          displayLimit={this.props.displayLimit}
-          defaultQueryLimit={this.props.defaultQueryLimit}
+          displayLimit={displayLimit}
+          defaultQueryLimit={defaultQueryLimit}
         />
       </Split>
     );
-  }
+  };
 
-  renderDropdown() {
-    const qe = this.props.queryEditor;
-    const successful = this.props.latestQuery?.state === 'success';
+  const renderDropdown = () => {
+    const qe = queryEditor;
+    const successful = latestQuery?.state === 'success';
     const scheduleToolTip = successful
       ? t('Schedule the query periodically')
       : t('You must run the query successfully first');
     return (
-      <Menu onClick={this.handleMenuClick} style={{ width: 176 }}>
+      <Menu style={{ width: 176 }}>
         <Menu.Item style={{ display: 'flex', justifyContent: 'space-between' 
}}>

Review Comment:
   ```suggestion
           <Menu.Item css={{ display: 'flex', justifyContent: 'space-between' 
}}>
   ```
   Use `css` instead of `style`



##########
superset-frontend/src/SqlLab/components/SqlEditor/index.jsx:
##########
@@ -614,214 +579,145 @@ class SqlEditor extends React.PureComponent {
         <div className="leftItems">
           <span>
             <RunQueryActionButton
-              allowAsync={
-                this.props.database
-                  ? this.props.database.allow_run_async
-                  : false
-              }
-              queryEditor={qe}
-              queryState={this.props.latestQuery?.state}
-              runQuery={this.runQuery}
-              stopQuery={this.stopQuery}
+              allowAsync={database ? database.allow_run_async : false}
+              queryEditor={queryEditor}
+              queryState={latestQuery?.state}
+              runQuery={startQuery}
+              stopQuery={stopQuery}
               overlayCreateAsMenu={showMenu ? runMenuBtn : null}
             />
           </span>
           {isFeatureEnabled(FeatureFlag.ESTIMATE_QUERY_COST) &&
-            this.props.database &&
-            this.props.database.allows_cost_estimate && (
+            database?.allows_cost_estimate && (
               <span>
                 <EstimateQueryCostButton
-                  getEstimate={this.getQueryCostEstimate}
-                  queryEditor={qe}
+                  getEstimate={getQueryCostEstimate}
+                  queryEditor={queryEditor}
                   tooltip={t('Estimate the cost before running a query')}
                 />
               </span>
             )}
           <span>
             <QueryLimitSelect
-              queryEditor={this.props.queryEditor}
-              maxRow={this.props.maxRow}
-              defaultQueryLimit={this.props.defaultQueryLimit}
+              queryEditor={queryEditor}
+              maxRow={maxRow}
+              defaultQueryLimit={defaultQueryLimit}
             />
           </span>
-          {this.props.latestQuery && (
+          {latestQuery && (
             <Timer
-              startTime={this.props.latestQuery.startDttm}
-              endTime={this.props.latestQuery.endDttm}
-              state={STATE_TYPE_MAP[this.props.latestQuery.state]}
-              isRunning={this.props.latestQuery.state === 'running'}
+              startTime={latestQuery.startDttm}
+              endTime={latestQuery.endDttm}
+              state={STATE_TYPE_MAP[latestQuery.state]}
+              isRunning={latestQuery.state === 'running'}
             />
           )}
         </div>
         <div className="rightItems">
           <span>
             <SaveQuery
-              queryEditor={qe}
-              columns={this.props.latestQuery?.results?.columns || []}
-              onSave={this.saveQuery}
-              onUpdate={this.props.actions.updateSavedQuery}
-              saveQueryWarning={this.props.saveQueryWarning}
-              database={this.props.database}
+              queryEditor={queryEditor}
+              columns={latestQuery?.results?.columns || []}
+              onSave={onSaveQuery}
+              onUpdate={dispatch(updateSavedQuery)}
+              saveQueryWarning={saveQueryWarning}
+              database={database}
             />
           </span>
           <span>
-            <ShareSqlLabQuery queryEditor={qe} />
+            <ShareSqlLabQuery queryEditor={queryEditor} />
           </span>
-          <AntdDropdown overlay={this.renderDropdown()} trigger="click">
+          <AntdDropdown overlay={renderDropdown()} trigger="click">
             <Icons.MoreHoriz iconColor={theme.colors.grayscale.base} />
           </AntdDropdown>
         </div>
       </StyledToolbar>
     );
-  }
+  };
 
-  render() {
-    const createViewModalTitle =
-      this.state.createAs === CtasEnum.VIEW
-        ? 'CREATE VIEW AS'
-        : 'CREATE TABLE AS';
-
-    const createModalPlaceHolder =
-      this.state.createAs === CtasEnum.VIEW
-        ? 'Specify name to CREATE VIEW AS schema in: public'
-        : 'Specify name to CREATE TABLE AS schema in: public';
-    const leftBarStateClass = this.props.hideLeftBar
-      ? 'schemaPane-exit-done'
-      : 'schemaPane-enter-done';
-    return (
-      <div ref={this.sqlEditorRef} className="SqlEditor">
-        <CSSTransition
-          classNames="schemaPane"
-          in={!this.props.hideLeftBar}
-          timeout={300}
+  const createViewModalTitle =
+    createAs === CtasEnum.VIEW ? 'CREATE VIEW AS' : 'CREATE TABLE AS';
+
+  const createModalPlaceHolder =
+    createAs === CtasEnum.VIEW
+      ? t('Specify name to CREATE VIEW AS schema in: public')
+      : t('Specify name to CREATE TABLE AS schema in: public');
+
+  const leftBarStateClass = hideLeftBar
+    ? 'schemaPane-exit-done'
+    : 'schemaPane-enter-done';
+  return (
+    <div ref={sqlEditorRef} className="SqlEditor">
+      <CSSTransition classNames="schemaPane" in={!hideLeftBar} timeout={300}>
+        <ResizableSidebar
+          id={`sqllab:${queryEditor.id}`}
+          minWidth={SQL_EDITOR_LEFTBAR_WIDTH}
+          initialWidth={SQL_EDITOR_LEFTBAR_WIDTH}
+          enable={!hideLeftBar}
         >
-          <ResizableSidebar
-            id={`sqllab:${this.props.queryEditor.id}`}
-            minWidth={SQL_EDITOR_LEFTBAR_WIDTH}
-            initialWidth={SQL_EDITOR_LEFTBAR_WIDTH}
-            enable={!this.props.hideLeftBar}
-          >
-            {adjustedWidth => (
-              <StyledSidebar
-                className={`schemaPane ${leftBarStateClass}`}
-                width={adjustedWidth}
-                hide={this.props.hideLeftBar}
+          {adjustedWidth => (
+            <StyledSidebar
+              className={`schemaPane ${leftBarStateClass}`}
+              width={adjustedWidth}
+              hide={hideLeftBar}
+            >
+              <SqlEditorLeftBar
+                database={database}
+                queryEditor={queryEditor}
+                tables={tables}
+                actions={actions}
+                setEmptyState={bool => setShowEmptyState(bool)}
+              />
+            </StyledSidebar>
+          )}
+        </ResizableSidebar>
+      </CSSTransition>
+      {showEmptyState ? (
+        <EmptyStateBig
+          image="vector.svg"
+          title={t('Select a database to write a query')}
+          description={t(
+            'Choose one of the available databases from the panel on the 
left.',
+          )}
+        />
+      ) : (
+        queryPane()
+      )}
+      <Modal
+        visible={showCreateAsModal}
+        title={t(createViewModalTitle)}
+        onHide={() => setShowCreateAsModal(false)}
+        footer={
+          <>
+            <Button onClick={() => setShowCreateAsModal(false)}>Cancel</Button>

Review Comment:
   ```suggestion
               <Button onClick={() => 
setShowCreateAsModal(false)}>{t('Cancel')}</Button>
   ```
   This string needs to be translated



##########
superset-frontend/src/SqlLab/components/SqlEditor/index.jsx:
##########
@@ -362,176 +349,163 @@ class SqlEditor extends React.PureComponent {
     }
 
     return base;
-  }
+  };
 
-  setEmptyState(bool) {
-    this.setState({ showEmptyState: bool });
-  }
+  const setQueryEditorAndSaveSql = sql => {
+    dispatch(queryEditorSetAndSaveSql(queryEditor, sql));
+  };
 
-  setQueryEditorAndSaveSql(sql) {
-    this.props.queryEditorSetAndSaveSql(this.props.queryEditor, sql);
-  }
+  const setQueryEditorAndSaveSqlWithDebounce = useMemo(
+    () => debounce(setQueryEditorAndSaveSql, SET_QUERY_EDITOR_SQL_DEBOUNCE_MS),
+    [],
+  );
 
-  getQueryCostEstimate() {
-    if (this.props.database) {
-      const qe = this.props.queryEditor;
-      this.props.estimateQueryCost(qe);
+  const getQueryCostEstimate = () => {
+    if (database) {
+      dispatch(estimateQueryCost(queryEditor));
     }
-  }
+  };
 
-  handleToggleAutocompleteEnabled = () => {
-    this.setState(prevState => {
-      setItem(
-        LocalStorageKeys.sqllab__is_autocomplete_enabled,
-        !prevState.autocompleteEnabled,
-      );
-      return {
-        autocompleteEnabled: !prevState.autocompleteEnabled,
-      };
-    });
+  const handleToggleAutocompleteEnabled = () => {
+    setItem(
+      LocalStorageKeys.sqllab__is_autocomplete_enabled,
+      !autocompleteEnabled,
+    );
+    setAutocompleteEnabled(!autocompleteEnabled);
   };
 
-  handleWindowResize() {
-    this.setState({ height: this.getSqlEditorHeight() });
-  }
+  const handleWindowResize = () => {
+    setHeight(getSqlEditorHeight());
+  };
 
-  elementStyle(dimension, elementSize, gutterSize) {
-    return {
-      [dimension]: `calc(${elementSize}% - ${
-        gutterSize + SQL_EDITOR_GUTTER_MARGIN
-      }px)`,
-    };
-  }
+  const handleWindowResizeWithThrottle = useMemo(
+    () => throttle(handleWindowResize, WINDOW_RESIZE_THROTTLE_MS),
+    [],
+  );
+
+  const elementStyle = (dimension, elementSize, gutterSize) => ({
+    [dimension]: `calc(${elementSize}% - ${
+      gutterSize + SQL_EDITOR_GUTTER_MARGIN
+    }px)`,
+  });
 
-  requestValidation(sql) {
-    const { database, queryEditor, validateQuery } = this.props;
+  const requestValidation = sql => {
     if (database) {
-      validateQuery(queryEditor, sql);
+      dispatch(validateQuery(queryEditor, sql));
     }
-  }
+  };
 
-  canValidateQuery() {
+  const requestValidationWithDebounce = useMemo(
+    () => debounce(requestValidation, VALIDATION_DEBOUNCE_MS),
+    [],
+  );
+
+  const canValidateQuery = () => {
     // Check whether or not we can validate the current query based on whether
     // or not the backend has a validator configured for it.
-    if (this.props.database) {
-      return validatorMap.hasOwnProperty(this.props.database.backend);
+    if (database) {
+      return validatorMap.hasOwnProperty(database.backend);
     }
     return false;
-  }
+  };
 
-  runQuery() {
-    if (this.props.database) {
-      this.startQuery();
+  const startQuery = (ctasArg = false, ctas_method = CtasEnum.TABLE) => {
+    if (!database) {
+      return;
     }
-  }
 
-  convertToNumWithSpaces(num) {
-    return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1 ');
-  }
-
-  startQuery(ctas = false, ctas_method = CtasEnum.TABLE) {
-    const {
-      database,
-      runQueryFromSqlEditor,
-      setActiveSouthPaneTab,
-      queryEditor,
-      defaultQueryLimit,
-    } = this.props;
-    runQueryFromSqlEditor(
-      database,
-      queryEditor,
-      defaultQueryLimit,
-      ctas ? this.state.ctas : '',
-      ctas,
-      ctas_method,
+    dispatch(
+      runQueryFromSqlEditor(
+        database,
+        queryEditor,
+        defaultQueryLimit,
+        ctasArg ? ctas : '',
+        ctasArg,
+        ctas_method,
+      ),
     );
-    setActiveSouthPaneTab('Results');
-  }
+    dispatch(setActiveSouthPaneTab('Results'));
+  };
 
-  stopQuery() {
-    if (
-      this.props.latestQuery &&
-      ['running', 'pending'].indexOf(this.props.latestQuery.state) >= 0
-    ) {
-      this.props.postStopQuery(this.props.latestQuery);
+  const stopQuery = () => {
+    if (latestQuery && ['running', 'pending'].indexOf(latestQuery.state) >= 0) 
{
+      dispatch(postStopQuery(latestQuery));
     }
-  }
+  };
 
-  createTableAs() {
-    this.startQuery(true, CtasEnum.TABLE);
-    this.setState({ showCreateAsModal: false, ctas: '' });
-  }
+  const createTableAs = () => {
+    startQuery(true, CtasEnum.TABLE);
+    setShowCreateAsModal(false);
+    setCtas('');
+  };
 
-  createViewAs() {
-    this.startQuery(true, CtasEnum.VIEW);
-    this.setState({ showCreateAsModal: false, ctas: '' });
-  }
+  const createViewAs = () => {
+    startQuery(true, CtasEnum.VIEW);
+    setShowCreateAsModal(false);
+    setCtas('');
+  };
 
-  ctasChanged(event) {
-    this.setState({ ctas: event.target.value });
-  }
+  const ctasChanged = event => {
+    setCtas(event.target.value);
+  };
 
-  queryPane() {
-    const hotkeys = this.getHotkeyConfig();
+  const queryPane = () => {
+    const hotkeys = getHotkeyConfig();
     const { aceEditorHeight, southPaneHeight } =
-      this.getAceEditorAndSouthPaneHeights(
-        this.state.height,
-        this.state.northPercent,
-        this.state.southPercent,
-      );
+      getAceEditorAndSouthPaneHeights(height, northPercent, southPercent);
     return (
       <Split
         expandToMin
         className="queryPane"
-        sizes={[this.state.northPercent, this.state.southPercent]}
-        elementStyle={this.elementStyle}
+        sizes={[northPercent, southPercent]}
+        elementStyle={elementStyle}
         minSize={200}
         direction="vertical"
         gutterSize={SQL_EDITOR_GUTTER_HEIGHT}
-        onDragStart={this.onResizeStart}
-        onDragEnd={this.onResizeEnd}
+        onDragStart={onResizeStart}
+        onDragEnd={onResizeEnd}
       >
-        <div ref={this.northPaneRef} className="north-pane">
+        <div ref={northPaneRef} className="north-pane">
           <AceEditorWrapper
-            actions={this.props.actions}
-            autocomplete={this.state.autocompleteEnabled}
-            onBlur={this.setQueryEditorSql}
-            onChange={this.onSqlChanged}
-            queryEditor={this.props.queryEditor}
-            database={this.props.database}
-            extendedTables={this.props.tables}
+            actions={actions}
+            autocomplete={autocompleteEnabled}
+            onBlur={setQueryEditorAndSaveSql}
+            onChange={onSqlChanged}
+            queryEditor={queryEditor}
+            database={database}
+            extendedTables={tables}
             height={`${aceEditorHeight}px`}
             hotkeys={hotkeys}
           />
-          {this.renderEditorBottomBar(hotkeys)}
+          {renderEditorBottomBar(hotkeys)}
         </div>
         <ConnectedSouthPane
-          editorQueries={this.props.editorQueries}
-          latestQueryId={this.props.latestQuery && this.props.latestQuery.id}
-          dataPreviewQueries={this.props.dataPreviewQueries}
-          actions={this.props.actions}
+          editorQueries={editorQueries}
+          latestQueryId={latestQuery && latestQuery.id}

Review Comment:
   ```suggestion
             latestQueryId={latestQuery?.id}
   ```
   This can be shortened with optional chaining



##########
superset-frontend/src/SqlLab/components/SqlEditor/index.jsx:
##########
@@ -362,176 +349,163 @@ class SqlEditor extends React.PureComponent {
     }
 
     return base;
-  }
+  };
 
-  setEmptyState(bool) {
-    this.setState({ showEmptyState: bool });
-  }
+  const setQueryEditorAndSaveSql = sql => {
+    dispatch(queryEditorSetAndSaveSql(queryEditor, sql));
+  };
 
-  setQueryEditorAndSaveSql(sql) {
-    this.props.queryEditorSetAndSaveSql(this.props.queryEditor, sql);
-  }
+  const setQueryEditorAndSaveSqlWithDebounce = useMemo(
+    () => debounce(setQueryEditorAndSaveSql, SET_QUERY_EDITOR_SQL_DEBOUNCE_MS),
+    [],
+  );
 
-  getQueryCostEstimate() {
-    if (this.props.database) {
-      const qe = this.props.queryEditor;
-      this.props.estimateQueryCost(qe);
+  const getQueryCostEstimate = () => {
+    if (database) {
+      dispatch(estimateQueryCost(queryEditor));
     }
-  }
+  };
 
-  handleToggleAutocompleteEnabled = () => {
-    this.setState(prevState => {
-      setItem(
-        LocalStorageKeys.sqllab__is_autocomplete_enabled,
-        !prevState.autocompleteEnabled,
-      );
-      return {
-        autocompleteEnabled: !prevState.autocompleteEnabled,
-      };
-    });
+  const handleToggleAutocompleteEnabled = () => {
+    setItem(
+      LocalStorageKeys.sqllab__is_autocomplete_enabled,
+      !autocompleteEnabled,
+    );
+    setAutocompleteEnabled(!autocompleteEnabled);
   };
 
-  handleWindowResize() {
-    this.setState({ height: this.getSqlEditorHeight() });
-  }
+  const handleWindowResize = () => {
+    setHeight(getSqlEditorHeight());
+  };
 
-  elementStyle(dimension, elementSize, gutterSize) {
-    return {
-      [dimension]: `calc(${elementSize}% - ${
-        gutterSize + SQL_EDITOR_GUTTER_MARGIN
-      }px)`,
-    };
-  }
+  const handleWindowResizeWithThrottle = useMemo(
+    () => throttle(handleWindowResize, WINDOW_RESIZE_THROTTLE_MS),
+    [],
+  );
+
+  const elementStyle = (dimension, elementSize, gutterSize) => ({
+    [dimension]: `calc(${elementSize}% - ${
+      gutterSize + SQL_EDITOR_GUTTER_MARGIN
+    }px)`,
+  });
 
-  requestValidation(sql) {
-    const { database, queryEditor, validateQuery } = this.props;
+  const requestValidation = sql => {
     if (database) {
-      validateQuery(queryEditor, sql);
+      dispatch(validateQuery(queryEditor, sql));
     }
-  }
+  };
 
-  canValidateQuery() {
+  const requestValidationWithDebounce = useMemo(
+    () => debounce(requestValidation, VALIDATION_DEBOUNCE_MS),
+    [],
+  );
+
+  const canValidateQuery = () => {
     // Check whether or not we can validate the current query based on whether
     // or not the backend has a validator configured for it.
-    if (this.props.database) {
-      return validatorMap.hasOwnProperty(this.props.database.backend);
+    if (database) {
+      return validatorMap.hasOwnProperty(database.backend);
     }
     return false;
-  }
+  };
 
-  runQuery() {
-    if (this.props.database) {
-      this.startQuery();
+  const startQuery = (ctasArg = false, ctas_method = CtasEnum.TABLE) => {
+    if (!database) {
+      return;
     }
-  }
 
-  convertToNumWithSpaces(num) {
-    return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1 ');
-  }
-
-  startQuery(ctas = false, ctas_method = CtasEnum.TABLE) {
-    const {
-      database,
-      runQueryFromSqlEditor,
-      setActiveSouthPaneTab,
-      queryEditor,
-      defaultQueryLimit,
-    } = this.props;
-    runQueryFromSqlEditor(
-      database,
-      queryEditor,
-      defaultQueryLimit,
-      ctas ? this.state.ctas : '',
-      ctas,
-      ctas_method,
+    dispatch(
+      runQueryFromSqlEditor(
+        database,
+        queryEditor,
+        defaultQueryLimit,
+        ctasArg ? ctas : '',
+        ctasArg,
+        ctas_method,
+      ),
     );
-    setActiveSouthPaneTab('Results');
-  }
+    dispatch(setActiveSouthPaneTab('Results'));
+  };
 
-  stopQuery() {
-    if (
-      this.props.latestQuery &&
-      ['running', 'pending'].indexOf(this.props.latestQuery.state) >= 0
-    ) {
-      this.props.postStopQuery(this.props.latestQuery);
+  const stopQuery = () => {
+    if (latestQuery && ['running', 'pending'].indexOf(latestQuery.state) >= 0) 
{
+      dispatch(postStopQuery(latestQuery));
     }
-  }
+  };
 
-  createTableAs() {
-    this.startQuery(true, CtasEnum.TABLE);
-    this.setState({ showCreateAsModal: false, ctas: '' });
-  }
+  const createTableAs = () => {
+    startQuery(true, CtasEnum.TABLE);
+    setShowCreateAsModal(false);
+    setCtas('');
+  };
 
-  createViewAs() {
-    this.startQuery(true, CtasEnum.VIEW);
-    this.setState({ showCreateAsModal: false, ctas: '' });
-  }
+  const createViewAs = () => {
+    startQuery(true, CtasEnum.VIEW);
+    setShowCreateAsModal(false);
+    setCtas('');
+  };
 
-  ctasChanged(event) {
-    this.setState({ ctas: event.target.value });
-  }
+  const ctasChanged = event => {
+    setCtas(event.target.value);
+  };
 
-  queryPane() {
-    const hotkeys = this.getHotkeyConfig();
+  const queryPane = () => {
+    const hotkeys = getHotkeyConfig();
     const { aceEditorHeight, southPaneHeight } =
-      this.getAceEditorAndSouthPaneHeights(
-        this.state.height,
-        this.state.northPercent,
-        this.state.southPercent,
-      );
+      getAceEditorAndSouthPaneHeights(height, northPercent, southPercent);
     return (
       <Split
         expandToMin
         className="queryPane"
-        sizes={[this.state.northPercent, this.state.southPercent]}
-        elementStyle={this.elementStyle}
+        sizes={[northPercent, southPercent]}
+        elementStyle={elementStyle}
         minSize={200}
         direction="vertical"
         gutterSize={SQL_EDITOR_GUTTER_HEIGHT}
-        onDragStart={this.onResizeStart}
-        onDragEnd={this.onResizeEnd}
+        onDragStart={onResizeStart}
+        onDragEnd={onResizeEnd}
       >
-        <div ref={this.northPaneRef} className="north-pane">
+        <div ref={northPaneRef} className="north-pane">
           <AceEditorWrapper
-            actions={this.props.actions}
-            autocomplete={this.state.autocompleteEnabled}
-            onBlur={this.setQueryEditorSql}
-            onChange={this.onSqlChanged}
-            queryEditor={this.props.queryEditor}
-            database={this.props.database}
-            extendedTables={this.props.tables}
+            actions={actions}
+            autocomplete={autocompleteEnabled}
+            onBlur={setQueryEditorAndSaveSql}
+            onChange={onSqlChanged}
+            queryEditor={queryEditor}
+            database={database}
+            extendedTables={tables}
             height={`${aceEditorHeight}px`}
             hotkeys={hotkeys}
           />
-          {this.renderEditorBottomBar(hotkeys)}
+          {renderEditorBottomBar(hotkeys)}
         </div>
         <ConnectedSouthPane
-          editorQueries={this.props.editorQueries}
-          latestQueryId={this.props.latestQuery && this.props.latestQuery.id}
-          dataPreviewQueries={this.props.dataPreviewQueries}
-          actions={this.props.actions}
+          editorQueries={editorQueries}
+          latestQueryId={latestQuery && latestQuery.id}
+          dataPreviewQueries={dataPreviewQueries}
+          actions={actions}
           height={southPaneHeight}
-          displayLimit={this.props.displayLimit}
-          defaultQueryLimit={this.props.defaultQueryLimit}
+          displayLimit={displayLimit}
+          defaultQueryLimit={defaultQueryLimit}
         />
       </Split>
     );
-  }
+  };
 
-  renderDropdown() {
-    const qe = this.props.queryEditor;
-    const successful = this.props.latestQuery?.state === 'success';
+  const renderDropdown = () => {
+    const qe = queryEditor;
+    const successful = latestQuery?.state === 'success';
     const scheduleToolTip = successful
       ? t('Schedule the query periodically')
       : t('You must run the query successfully first');
     return (
-      <Menu onClick={this.handleMenuClick} style={{ width: 176 }}>
+      <Menu style={{ width: 176 }}>

Review Comment:
   ```suggestion
         <Menu css={{ width: theme.gridUnit * 44 }}>
   ```
   Unit values should stay within the Superset theme, also use `css` instead of 
`style`



##########
superset-frontend/src/SqlLab/components/SqlEditor/index.jsx:
##########
@@ -140,172 +139,158 @@ const StyledSidebar = styled.div`
 
 const propTypes = {
   actions: PropTypes.object.isRequired,
-  database: PropTypes.object,
-  latestQuery: PropTypes.object,
   tables: PropTypes.array.isRequired,
   editorQueries: PropTypes.array.isRequired,
   dataPreviewQueries: PropTypes.array.isRequired,
   queryEditor: PropTypes.object.isRequired,
-  hideLeftBar: PropTypes.bool,
   defaultQueryLimit: PropTypes.number.isRequired,
   maxRow: PropTypes.number.isRequired,
   displayLimit: PropTypes.number.isRequired,
   saveQueryWarning: PropTypes.string,
   scheduleQueryWarning: PropTypes.string,
 };
 
-const defaultProps = {
-  database: null,
-  latestQuery: null,
-  hideLeftBar: false,
-  scheduleQueryWarning: null,
-};
-
-class SqlEditor extends React.PureComponent {
-  constructor(props) {
-    super(props);
-    this.state = {
-      autorun: props.queryEditor.autorun,
-      ctas: '',
-      northPercent: props.queryEditor.northPercent || INITIAL_NORTH_PERCENT,
-      southPercent: props.queryEditor.southPercent || INITIAL_SOUTH_PERCENT,
-      autocompleteEnabled: getItem(
-        LocalStorageKeys.sqllab__is_autocomplete_enabled,
-        true,
-      ),
-      showCreateAsModal: false,
-      createAs: '',
-      showEmptyState: false,
-    };
-    this.sqlEditorRef = React.createRef();
-    this.northPaneRef = React.createRef();
-
-    this.elementStyle = this.elementStyle.bind(this);
-    this.onResizeStart = this.onResizeStart.bind(this);
-    this.onResizeEnd = this.onResizeEnd.bind(this);
-    this.canValidateQuery = this.canValidateQuery.bind(this);
-    this.runQuery = this.runQuery.bind(this);
-    this.setEmptyState = this.setEmptyState.bind(this);
-    this.stopQuery = this.stopQuery.bind(this);
-    this.saveQuery = this.saveQuery.bind(this);
-    this.onSqlChanged = this.onSqlChanged.bind(this);
-    this.setQueryEditorAndSaveSql = this.setQueryEditorAndSaveSql.bind(this);
-    this.setQueryEditorAndSaveSqlWithDebounce = debounce(
-      this.setQueryEditorAndSaveSql.bind(this),
-      SET_QUERY_EDITOR_SQL_DEBOUNCE_MS,
-    );
-    this.queryPane = this.queryPane.bind(this);
-    this.getHotkeyConfig = this.getHotkeyConfig.bind(this);
-    this.getAceEditorAndSouthPaneHeights =
-      this.getAceEditorAndSouthPaneHeights.bind(this);
-    this.getSqlEditorHeight = this.getSqlEditorHeight.bind(this);
-    this.requestValidation = debounce(
-      this.requestValidation.bind(this),
-      VALIDATION_DEBOUNCE_MS,
-    );
-    this.getQueryCostEstimate = this.getQueryCostEstimate.bind(this);
-    this.handleWindowResize = throttle(
-      this.handleWindowResize.bind(this),
-      WINDOW_RESIZE_THROTTLE_MS,
-    );
+const SqlEditor = ({
+  actions,
+  tables,
+  editorQueries,
+  dataPreviewQueries,
+  queryEditor,
+  defaultQueryLimit,
+  maxRow,
+  displayLimit,
+  saveQueryWarning,
+  scheduleQueryWarning = null,
+}) => {
+  const theme = useTheme();
+  const dispatch = useDispatch();
+
+  const { database, latestQuery, hideLeftBar } = useSelector(
+    ({ sqlLab: { unsavedQueryEditor, databases, queries } }) => {
+      let { dbId, latestQueryId, hideLeftBar } = queryEditor;
+      if (unsavedQueryEditor.id === queryEditor.id) {
+        dbId = unsavedQueryEditor.dbId || dbId;
+        latestQueryId = unsavedQueryEditor.latestQueryId || latestQueryId;
+        hideLeftBar = unsavedQueryEditor.hideLeftBar || hideLeftBar;
+      }
+      return {
+        database: databases[dbId],
+        latestQuery: queries[latestQueryId],
+        hideLeftBar,
+      };
+    },
+  );
 
-    this.onBeforeUnload = this.onBeforeUnload.bind(this);
-    this.renderDropdown = this.renderDropdown.bind(this);
-  }
+  const queryEditors = useSelector(({ sqlLab }) => sqlLab.queryEditors);
 
-  UNSAFE_componentWillMount() {
-    if (this.state.autorun) {
-      this.setState({ autorun: false });
-      this.props.queryEditorSetAutorun(this.props.queryEditor, false);
-      this.startQuery();
+  const [height, setHeight] = useState(0);
+  const [autorun, setAutorun] = useState(queryEditor.autorun);
+  const [ctas, setCtas] = useState('');
+  const [northPercent, setNorthPercent] = useState(
+    queryEditor.northPercent || INITIAL_NORTH_PERCENT,
+  );
+  const [southPercent, setSouthPercent] = useState(
+    queryEditor.southPercent || INITIAL_SOUTH_PERCENT,
+  );
+  const [autocompleteEnabled, setAutocompleteEnabled] = useState(
+    getItem(LocalStorageKeys.sqllab__is_autocomplete_enabled, true),
+  );
+  const [showCreateAsModal, setShowCreateAsModal] = useState(false);
+  const [createAs, setCreateAs] = useState('');
+  const [showEmptyState, setShowEmptyState] = useState(false);
+
+  const sqlEditorRef = useRef(null);
+  const northPaneRef = useRef(null);
+
+  useState(() => {
+    if (autorun) {
+      setAutorun(false);
+      dispatch(queryEditorSetAutorun(queryEditor, false));
+      startQuery();
     }
-  }
+  });
 
-  componentDidMount() {
+  useEffect(() => {
     // We need to measure the height of the sql editor post render to figure 
the height of
     // the south pane so it gets rendered properly
-    // eslint-disable-next-line react/no-did-mount-set-state
-    const db = this.props.database;
-    this.setState({ height: this.getSqlEditorHeight() });
-    if (!db || isEmpty(db)) {
-      this.setEmptyState(true);
+    setHeight(getSqlEditorHeight());
+    if (!database || isEmpty(database)) {
+      setShowEmptyState(true);
     }
 
-    window.addEventListener('resize', this.handleWindowResize);
-    window.addEventListener('beforeunload', this.onBeforeUnload);
+    window.addEventListener('resize', handleWindowResizeWithThrottle);
+    window.addEventListener('beforeunload', onBeforeUnload);
 
     // setup hotkeys
-    const hotkeys = this.getHotkeyConfig();
+    const hotkeys = getHotkeyConfig();
     hotkeys.forEach(keyConfig => {
       Mousetrap.bind([keyConfig.key], keyConfig.func);
     });
-  }
 
-  componentWillUnmount() {
-    window.removeEventListener('resize', this.handleWindowResize);
-    window.removeEventListener('beforeunload', this.onBeforeUnload);
-  }
+    return () => {
+      window.removeEventListener('resize', handleWindowResizeWithThrottle);
+      window.removeEventListener('beforeunload', onBeforeUnload);
+    };
+  }, []);
 
-  onResizeStart() {
+  const onResizeStart = () => {
     // Set the heights on the ace editor and the ace content area after drag 
starts
     // to smooth out the visual transition to the new heights when drag ends
     document.getElementsByClassName('ace_content')[0].style.height = '100%';
-  }
+  };
 
-  onResizeEnd([northPercent, southPercent]) {
-    this.setState({ northPercent, southPercent });
+  const onResizeEnd = ([northPercent, southPercent]) => {
+    setNorthPercent(northPercent);
+    setSouthPercent(southPercent);
 
-    if (this.northPaneRef.current && this.northPaneRef.current.clientHeight) {
-      this.props.persistEditorHeight(
-        this.props.queryEditor,
-        northPercent,
-        southPercent,
-      );
+    if (northPaneRef.current && northPaneRef.current.clientHeight) {
+      dispatch(persistEditorHeight(queryEditor, northPercent, southPercent));
     }
-  }
+  };
 
-  onBeforeUnload(event) {
+  const onBeforeUnload = event => {
     if (
-      this.props.database?.extra_json?.cancel_query_on_windows_unload &&
-      this.props.latestQuery?.state === 'running'
+      database?.extra_json?.cancel_query_on_windows_unload &&
+      latestQuery?.state === 'running'
     ) {
       event.preventDefault();
-      this.stopQuery();
+      stopQuery();
     }
-  }
+  };
 
-  onSqlChanged(sql) {
-    this.props.queryEditorSetSql(this.props.queryEditor, sql);
-    this.setQueryEditorAndSaveSqlWithDebounce(sql);
+  const onSqlChanged = sql => {
+    dispatch(queryEditorSetSql(queryEditor, sql));
+    setQueryEditorAndSaveSqlWithDebounce(sql);
     // Request server-side validation of the query text
-    if (this.canValidateQuery()) {
+    if (canValidateQuery()) {
       // NB. requestValidation is debounced
-      this.requestValidation(sql);
+      requestValidationWithDebounce(sql);
     }
-  }
+  };
 
   // One layer of abstraction for easy spying in unit tests
-  getSqlEditorHeight() {
-    return this.sqlEditorRef.current
-      ? this.sqlEditorRef.current.clientHeight - SQL_EDITOR_PADDING * 2
+  const getSqlEditorHeight = () =>
+    sqlEditorRef.current
+      ? sqlEditorRef.current.clientHeight - SQL_EDITOR_PADDING * 2
       : 0;
-  }
 
   // Return the heights for the ace editor and the south pane as an object
   // given the height of the sql editor, north pane percent and south pane 
percent.
-  getAceEditorAndSouthPaneHeights(height, northPercent, southPercent) {
-    return {
-      aceEditorHeight:
-        (height * northPercent) / 100 -
-        (SQL_EDITOR_GUTTER_HEIGHT / 2 + SQL_EDITOR_GUTTER_MARGIN) -
-        SQL_TOOLBAR_HEIGHT,
-      southPaneHeight:
-        (height * southPercent) / 100 -
-        (SQL_EDITOR_GUTTER_HEIGHT / 2 + SQL_EDITOR_GUTTER_MARGIN),
-    };
-  }
-
-  getHotkeyConfig() {
+  const getAceEditorAndSouthPaneHeights = (
+    height,
+    northPercent,
+    southPercent,
+  ) => ({
+    aceEditorHeight:
+      (height * northPercent) / 100 -
+      (SQL_EDITOR_GUTTER_HEIGHT / 2 + SQL_EDITOR_GUTTER_MARGIN) -
+      SQL_TOOLBAR_HEIGHT,
+    southPaneHeight:
+      (height * southPercent) / 100 -

Review Comment:
   ```suggestion
         (height * northPercent) / (theme.gridUnit * 25) -
         (SQL_EDITOR_GUTTER_HEIGHT / 2 + SQL_EDITOR_GUTTER_MARGIN) -
         SQL_TOOLBAR_HEIGHT,
       southPaneHeight:
         (height * southPercent) / (theme.gridUnit * 25) -
   ```
   Unit values should stay within the Superset theme



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to