You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2022/08/09 12:00:20 UTC

[superset] branch master updated: fix: forwardRef warnings in selects (#20970)

This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 6650076228 fix: forwardRef warnings in selects (#20970)
6650076228 is described below

commit 665007622845cf4303b50e57d11549a162dbbb1b
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Tue Aug 9 09:00:12 2022 -0300

    fix: forwardRef warnings in selects (#20970)
---
 .../src/components/Select/AsyncSelect.tsx          | 853 +++++++++++----------
 superset-frontend/src/components/Select/Select.tsx | 527 ++++++-------
 .../components/Select/WindowedSelect/windowed.tsx  |  22 +-
 3 files changed, 708 insertions(+), 694 deletions(-)

diff --git a/superset-frontend/src/components/Select/AsyncSelect.tsx b/superset-frontend/src/components/Select/AsyncSelect.tsx
index b95f2d8f0d..643981ac19 100644
--- a/superset-frontend/src/components/Select/AsyncSelect.tsx
+++ b/superset-frontend/src/components/Select/AsyncSelect.tsx
@@ -288,450 +288,455 @@ const getQueryCacheKey = (value: string, page: number, pageSize: number) =>
  * Each of the categories come with different abilities. For a comprehensive guide please refer to
  * the storybook in src/components/Select/Select.stories.tsx.
  */
-const AsyncSelect = (
-  {
-    allowClear,
-    allowNewOptions = false,
-    ariaLabel,
-    fetchOnlyOnSearch,
-    filterOption = true,
-    header = null,
-    invertSelection = false,
-    lazyLoading = true,
-    loading,
-    mode = 'single',
-    name,
-    notFoundContent,
-    onError,
-    onChange,
-    onClear,
-    onDropdownVisibleChange,
-    optionFilterProps = ['label', 'value'],
-    options,
-    pageSize = DEFAULT_PAGE_SIZE,
-    placeholder = t('Select ...'),
-    showSearch = true,
-    sortComparator = DEFAULT_SORT_COMPARATOR,
-    tokenSeparators,
-    value,
-    getPopupContainer,
-    ...props
-  }: AsyncSelectProps,
-  ref: RefObject<AsyncSelectRef>,
-) => {
-  const isSingleMode = mode === 'single';
-  const [selectValue, setSelectValue] = useState(value);
-  const [inputValue, setInputValue] = useState('');
-  const [isLoading, setIsLoading] = useState(loading);
-  const [error, setError] = useState('');
-  const [isDropdownVisible, setIsDropdownVisible] = useState(false);
-  const [page, setPage] = useState(0);
-  const [totalCount, setTotalCount] = useState(0);
-  const [loadingEnabled, setLoadingEnabled] = useState(!lazyLoading);
-  const [allValuesLoaded, setAllValuesLoaded] = useState(false);
-  const fetchedQueries = useRef(new Map<string, number>());
-  const mappedMode = isSingleMode
-    ? undefined
-    : allowNewOptions
-    ? 'tags'
-    : 'multiple';
-  const allowFetch = !fetchOnlyOnSearch || inputValue;
-
-  const sortSelectedFirst = useCallback(
-    (a: AntdLabeledValue, b: AntdLabeledValue) =>
-      selectValue && a.value !== undefined && b.value !== undefined
-        ? Number(hasOption(b.value, selectValue)) -
-          Number(hasOption(a.value, selectValue))
-        : 0,
-    [selectValue],
-  );
-  const sortComparatorWithSearch = useCallback(
-    (a: AntdLabeledValue, b: AntdLabeledValue) =>
-      sortSelectedFirst(a, b) || sortComparator(a, b, inputValue),
-    [inputValue, sortComparator, sortSelectedFirst],
-  );
-  const sortComparatorForNoSearch = useCallback(
-    (a: AntdLabeledValue, b: AntdLabeledValue) =>
-      sortSelectedFirst(a, b) ||
-      // Only apply the custom sorter in async mode because we should
-      // preserve the options order as much as possible.
-      sortComparator(a, b, ''),
-    [sortComparator, sortSelectedFirst],
-  );
-
-  const initialOptions = useMemo(
-    () => (options && Array.isArray(options) ? options.slice() : EMPTY_OPTIONS),
-    [options],
-  );
-  const initialOptionsSorted = useMemo(
-    () => initialOptions.slice().sort(sortComparatorForNoSearch),
-    [initialOptions, sortComparatorForNoSearch],
-  );
-
-  const [selectOptions, setSelectOptions] =
-    useState<OptionsType>(initialOptionsSorted);
-
-  // add selected values to options list if they are not in it
-  const fullSelectOptions = useMemo(() => {
-    const missingValues: OptionsType = ensureIsArray(selectValue)
-      .filter(opt => !hasOption(getValue(opt), selectOptions))
-      .map(opt =>
-        isLabeledValue(opt) ? opt : { value: opt, label: String(opt) },
-      );
-    return missingValues.length > 0
-      ? missingValues.concat(selectOptions)
-      : selectOptions;
-  }, [selectOptions, selectValue]);
-
-  const hasCustomLabels = fullSelectOptions.some(opt => !!opt?.customLabel);
-
-  const handleOnSelect = (
-    selectedItem: string | number | AntdLabeledValue | undefined,
-  ) => {
-    if (isSingleMode) {
-      setSelectValue(selectedItem);
-    } else {
-      setSelectValue(previousState => {
-        const array = ensureIsArray(previousState);
-        const value = getValue(selectedItem);
-        // Tokenized values can contain duplicated values
-        if (!hasOption(value, array)) {
-          const result = [...array, selectedItem];
-          return isLabeledValue(selectedItem)
-            ? (result as AntdLabeledValue[])
-            : (result as (string | number)[]);
-        }
-        return previousState;
-      });
-    }
-    setInputValue('');
-  };
-
-  const handleOnDeselect = (
-    value: string | number | AntdLabeledValue | undefined,
+const AsyncSelect = forwardRef(
+  (
+    {
+      allowClear,
+      allowNewOptions = false,
+      ariaLabel,
+      fetchOnlyOnSearch,
+      filterOption = true,
+      header = null,
+      invertSelection = false,
+      lazyLoading = true,
+      loading,
+      mode = 'single',
+      name,
+      notFoundContent,
+      onError,
+      onChange,
+      onClear,
+      onDropdownVisibleChange,
+      optionFilterProps = ['label', 'value'],
+      options,
+      pageSize = DEFAULT_PAGE_SIZE,
+      placeholder = t('Select ...'),
+      showSearch = true,
+      sortComparator = DEFAULT_SORT_COMPARATOR,
+      tokenSeparators,
+      value,
+      getPopupContainer,
+      ...props
+    }: AsyncSelectProps,
+    ref: RefObject<AsyncSelectRef>,
   ) => {
-    if (Array.isArray(selectValue)) {
-      if (isLabeledValue(value)) {
-        const array = selectValue as AntdLabeledValue[];
-        setSelectValue(array.filter(element => element.value !== value.value));
+    const isSingleMode = mode === 'single';
+    const [selectValue, setSelectValue] = useState(value);
+    const [inputValue, setInputValue] = useState('');
+    const [isLoading, setIsLoading] = useState(loading);
+    const [error, setError] = useState('');
+    const [isDropdownVisible, setIsDropdownVisible] = useState(false);
+    const [page, setPage] = useState(0);
+    const [totalCount, setTotalCount] = useState(0);
+    const [loadingEnabled, setLoadingEnabled] = useState(!lazyLoading);
+    const [allValuesLoaded, setAllValuesLoaded] = useState(false);
+    const fetchedQueries = useRef(new Map<string, number>());
+    const mappedMode = isSingleMode
+      ? undefined
+      : allowNewOptions
+      ? 'tags'
+      : 'multiple';
+    const allowFetch = !fetchOnlyOnSearch || inputValue;
+
+    const sortSelectedFirst = useCallback(
+      (a: AntdLabeledValue, b: AntdLabeledValue) =>
+        selectValue && a.value !== undefined && b.value !== undefined
+          ? Number(hasOption(b.value, selectValue)) -
+            Number(hasOption(a.value, selectValue))
+          : 0,
+      [selectValue],
+    );
+    const sortComparatorWithSearch = useCallback(
+      (a: AntdLabeledValue, b: AntdLabeledValue) =>
+        sortSelectedFirst(a, b) || sortComparator(a, b, inputValue),
+      [inputValue, sortComparator, sortSelectedFirst],
+    );
+    const sortComparatorForNoSearch = useCallback(
+      (a: AntdLabeledValue, b: AntdLabeledValue) =>
+        sortSelectedFirst(a, b) ||
+        // Only apply the custom sorter in async mode because we should
+        // preserve the options order as much as possible.
+        sortComparator(a, b, ''),
+      [sortComparator, sortSelectedFirst],
+    );
+
+    const initialOptions = useMemo(
+      () =>
+        options && Array.isArray(options) ? options.slice() : EMPTY_OPTIONS,
+      [options],
+    );
+    const initialOptionsSorted = useMemo(
+      () => initialOptions.slice().sort(sortComparatorForNoSearch),
+      [initialOptions, sortComparatorForNoSearch],
+    );
+
+    const [selectOptions, setSelectOptions] =
+      useState<OptionsType>(initialOptionsSorted);
+
+    // add selected values to options list if they are not in it
+    const fullSelectOptions = useMemo(() => {
+      const missingValues: OptionsType = ensureIsArray(selectValue)
+        .filter(opt => !hasOption(getValue(opt), selectOptions))
+        .map(opt =>
+          isLabeledValue(opt) ? opt : { value: opt, label: String(opt) },
+        );
+      return missingValues.length > 0
+        ? missingValues.concat(selectOptions)
+        : selectOptions;
+    }, [selectOptions, selectValue]);
+
+    const hasCustomLabels = fullSelectOptions.some(opt => !!opt?.customLabel);
+
+    const handleOnSelect = (
+      selectedItem: string | number | AntdLabeledValue | undefined,
+    ) => {
+      if (isSingleMode) {
+        setSelectValue(selectedItem);
       } else {
-        const array = selectValue as (string | number)[];
-        setSelectValue(array.filter(element => element !== value));
+        setSelectValue(previousState => {
+          const array = ensureIsArray(previousState);
+          const value = getValue(selectedItem);
+          // Tokenized values can contain duplicated values
+          if (!hasOption(value, array)) {
+            const result = [...array, selectedItem];
+            return isLabeledValue(selectedItem)
+              ? (result as AntdLabeledValue[])
+              : (result as (string | number)[]);
+          }
+          return previousState;
+        });
       }
-    }
-    setInputValue('');
-  };
+      setInputValue('');
+    };
+
+    const handleOnDeselect = (
+      value: string | number | AntdLabeledValue | undefined,
+    ) => {
+      if (Array.isArray(selectValue)) {
+        if (isLabeledValue(value)) {
+          const array = selectValue as AntdLabeledValue[];
+          setSelectValue(
+            array.filter(element => element.value !== value.value),
+          );
+        } else {
+          const array = selectValue as (string | number)[];
+          setSelectValue(array.filter(element => element !== value));
+        }
+      }
+      setInputValue('');
+    };
 
-  const internalOnError = useCallback(
-    (response: Response) =>
-      getClientErrorObject(response).then(e => {
-        const { error } = e;
-        setError(error);
+    const internalOnError = useCallback(
+      (response: Response) =>
+        getClientErrorObject(response).then(e => {
+          const { error } = e;
+          setError(error);
 
-        if (onError) {
-          onError(error);
+          if (onError) {
+            onError(error);
+          }
+        }),
+      [onError],
+    );
+
+    const mergeData = useCallback(
+      (data: OptionsType) => {
+        let mergedData: OptionsType = [];
+        if (data && Array.isArray(data) && data.length) {
+          // unique option values should always be case sensitive so don't lowercase
+          const dataValues = new Set(data.map(opt => opt.value));
+          // merges with existing and creates unique options
+          setSelectOptions(prevOptions => {
+            mergedData = prevOptions
+              .filter(previousOption => !dataValues.has(previousOption.value))
+              .concat(data)
+              .sort(sortComparatorForNoSearch);
+            return mergedData;
+          });
         }
-      }),
-    [onError],
-  );
-
-  const mergeData = useCallback(
-    (data: OptionsType) => {
-      let mergedData: OptionsType = [];
-      if (data && Array.isArray(data) && data.length) {
-        // unique option values should always be case sensitive so don't lowercase
-        const dataValues = new Set(data.map(opt => opt.value));
-        // merges with existing and creates unique options
-        setSelectOptions(prevOptions => {
-          mergedData = prevOptions
-            .filter(previousOption => !dataValues.has(previousOption.value))
-            .concat(data)
-            .sort(sortComparatorForNoSearch);
-          return mergedData;
-        });
+        return mergedData;
+      },
+      [sortComparatorForNoSearch],
+    );
+
+    const fetchPage = useMemo(
+      () => (search: string, page: number) => {
+        setPage(page);
+        if (allValuesLoaded) {
+          setIsLoading(false);
+          return;
+        }
+        const key = getQueryCacheKey(search, page, pageSize);
+        const cachedCount = fetchedQueries.current.get(key);
+        if (cachedCount !== undefined) {
+          setTotalCount(cachedCount);
+          setIsLoading(false);
+          return;
+        }
+        setIsLoading(true);
+        const fetchOptions = options as OptionsPagePromise;
+        fetchOptions(search, page, pageSize)
+          .then(({ data, totalCount }: OptionsTypePage) => {
+            const mergedData = mergeData(data);
+            fetchedQueries.current.set(key, totalCount);
+            setTotalCount(totalCount);
+            if (
+              !fetchOnlyOnSearch &&
+              value === '' &&
+              mergedData.length >= totalCount
+            ) {
+              setAllValuesLoaded(true);
+            }
+          })
+          .catch(internalOnError)
+          .finally(() => {
+            setIsLoading(false);
+          });
+      },
+      [
+        allValuesLoaded,
+        fetchOnlyOnSearch,
+        mergeData,
+        internalOnError,
+        options,
+        pageSize,
+        value,
+      ],
+    );
+
+    const debouncedFetchPage = useMemo(
+      () => debounce(fetchPage, SLOW_DEBOUNCE),
+      [fetchPage],
+    );
+
+    const handleOnSearch = (search: string) => {
+      const searchValue = search.trim();
+      if (allowNewOptions && isSingleMode) {
+        const newOption = searchValue &&
+          !hasOption(searchValue, fullSelectOptions, true) && {
+            label: searchValue,
+            value: searchValue,
+            isNewOption: true,
+          };
+        const cleanSelectOptions = fullSelectOptions.filter(
+          opt => !opt.isNewOption || hasOption(opt.value, selectValue),
+        );
+        const newOptions = newOption
+          ? [newOption, ...cleanSelectOptions]
+          : cleanSelectOptions;
+        setSelectOptions(newOptions);
       }
-      return mergedData;
-    },
-    [sortComparatorForNoSearch],
-  );
-
-  const fetchPage = useMemo(
-    () => (search: string, page: number) => {
-      setPage(page);
-      if (allValuesLoaded) {
-        setIsLoading(false);
-        return;
+      if (
+        !allValuesLoaded &&
+        loadingEnabled &&
+        !fetchedQueries.current.has(getQueryCacheKey(searchValue, 0, pageSize))
+      ) {
+        // if fetch only on search but search value is empty, then should not be
+        // in loading state
+        setIsLoading(!(fetchOnlyOnSearch && !searchValue));
       }
-      const key = getQueryCacheKey(search, page, pageSize);
-      const cachedCount = fetchedQueries.current.get(key);
-      if (cachedCount !== undefined) {
-        setTotalCount(cachedCount);
-        setIsLoading(false);
-        return;
+      setInputValue(search);
+    };
+
+    const handlePagination = (e: UIEvent<HTMLElement>) => {
+      const vScroll = e.currentTarget;
+      const thresholdReached =
+        vScroll.scrollTop > (vScroll.scrollHeight - vScroll.offsetHeight) * 0.7;
+      const hasMoreData = page * pageSize + pageSize < totalCount;
+
+      if (!isLoading && hasMoreData && thresholdReached) {
+        const newPage = page + 1;
+        fetchPage(inputValue, newPage);
       }
-      setIsLoading(true);
-      const fetchOptions = options as OptionsPagePromise;
-      fetchOptions(search, page, pageSize)
-        .then(({ data, totalCount }: OptionsTypePage) => {
-          const mergedData = mergeData(data);
-          fetchedQueries.current.set(key, totalCount);
-          setTotalCount(totalCount);
-          if (
-            !fetchOnlyOnSearch &&
-            value === '' &&
-            mergedData.length >= totalCount
-          ) {
-            setAllValuesLoaded(true);
-          }
-        })
-        .catch(internalOnError)
-        .finally(() => {
-          setIsLoading(false);
-        });
-    },
-    [
-      allValuesLoaded,
-      fetchOnlyOnSearch,
-      mergeData,
-      internalOnError,
-      options,
-      pageSize,
-      value,
-    ],
-  );
-
-  const debouncedFetchPage = useMemo(
-    () => debounce(fetchPage, SLOW_DEBOUNCE),
-    [fetchPage],
-  );
-
-  const handleOnSearch = (search: string) => {
-    const searchValue = search.trim();
-    if (allowNewOptions && isSingleMode) {
-      const newOption = searchValue &&
-        !hasOption(searchValue, fullSelectOptions, true) && {
-          label: searchValue,
-          value: searchValue,
-          isNewOption: true,
-        };
-      const cleanSelectOptions = fullSelectOptions.filter(
-        opt => !opt.isNewOption || hasOption(opt.value, selectValue),
-      );
-      const newOptions = newOption
-        ? [newOption, ...cleanSelectOptions]
-        : cleanSelectOptions;
-      setSelectOptions(newOptions);
-    }
-    if (
-      !allValuesLoaded &&
-      loadingEnabled &&
-      !fetchedQueries.current.has(getQueryCacheKey(searchValue, 0, pageSize))
-    ) {
-      // if fetch only on search but search value is empty, then should not be
-      // in loading state
-      setIsLoading(!(fetchOnlyOnSearch && !searchValue));
-    }
-    setInputValue(search);
-  };
-
-  const handlePagination = (e: UIEvent<HTMLElement>) => {
-    const vScroll = e.currentTarget;
-    const thresholdReached =
-      vScroll.scrollTop > (vScroll.scrollHeight - vScroll.offsetHeight) * 0.7;
-    const hasMoreData = page * pageSize + pageSize < totalCount;
-
-    if (!isLoading && hasMoreData && thresholdReached) {
-      const newPage = page + 1;
-      fetchPage(inputValue, newPage);
-    }
-  };
+    };
 
-  const handleFilterOption = (search: string, option: AntdLabeledValue) => {
-    if (typeof filterOption === 'function') {
-      return filterOption(search, option);
-    }
+    const handleFilterOption = (search: string, option: AntdLabeledValue) => {
+      if (typeof filterOption === 'function') {
+        return filterOption(search, option);
+      }
 
-    if (filterOption) {
-      const searchValue = search.trim().toLowerCase();
-      if (optionFilterProps && optionFilterProps.length) {
-        return optionFilterProps.some(prop => {
-          const optionProp = option?.[prop]
-            ? String(option[prop]).trim().toLowerCase()
-            : '';
-          return optionProp.includes(searchValue);
-        });
+      if (filterOption) {
+        const searchValue = search.trim().toLowerCase();
+        if (optionFilterProps && optionFilterProps.length) {
+          return optionFilterProps.some(prop => {
+            const optionProp = option?.[prop]
+              ? String(option[prop]).trim().toLowerCase()
+              : '';
+            return optionProp.includes(searchValue);
+          });
+        }
       }
-    }
 
-    return false;
-  };
+      return false;
+    };
 
-  const handleOnDropdownVisibleChange = (isDropdownVisible: boolean) => {
-    setIsDropdownVisible(isDropdownVisible);
+    const handleOnDropdownVisibleChange = (isDropdownVisible: boolean) => {
+      setIsDropdownVisible(isDropdownVisible);
 
-    // loading is enabled when dropdown is open,
-    // disabled when dropdown is closed
-    if (loadingEnabled !== isDropdownVisible) {
-      setLoadingEnabled(isDropdownVisible);
-    }
-    // when closing dropdown, always reset loading state
-    if (!isDropdownVisible && isLoading) {
-      // delay is for the animation of closing the dropdown
-      // so the dropdown doesn't flash between "Loading..." and "No data"
-      // before closing.
-      setTimeout(() => {
-        setIsLoading(false);
-      }, 250);
-    }
-    // if no search input value, force sort options because it won't be sorted by
-    // `filterSort`.
-    if (isDropdownVisible && !inputValue && selectOptions.length > 1) {
-      const sortedOptions = selectOptions
-        .slice()
-        .sort(sortComparatorForNoSearch);
-      if (!isEqual(sortedOptions, selectOptions)) {
-        setSelectOptions(sortedOptions);
+      // loading is enabled when dropdown is open,
+      // disabled when dropdown is closed
+      if (loadingEnabled !== isDropdownVisible) {
+        setLoadingEnabled(isDropdownVisible);
+      }
+      // when closing dropdown, always reset loading state
+      if (!isDropdownVisible && isLoading) {
+        // delay is for the animation of closing the dropdown
+        // so the dropdown doesn't flash between "Loading..." and "No data"
+        // before closing.
+        setTimeout(() => {
+          setIsLoading(false);
+        }, 250);
+      }
+      // if no search input value, force sort options because it won't be sorted by
+      // `filterSort`.
+      if (isDropdownVisible && !inputValue && selectOptions.length > 1) {
+        const sortedOptions = selectOptions
+          .slice()
+          .sort(sortComparatorForNoSearch);
+        if (!isEqual(sortedOptions, selectOptions)) {
+          setSelectOptions(sortedOptions);
+        }
       }
-    }
-
-    if (onDropdownVisibleChange) {
-      onDropdownVisibleChange(isDropdownVisible);
-    }
-  };
 
-  const dropdownRender = (
-    originNode: ReactElement & { ref?: RefObject<HTMLElement> },
-  ) => {
-    if (!isDropdownVisible) {
-      originNode.ref?.current?.scrollTo({ top: 0 });
-    }
-    if (isLoading && fullSelectOptions.length === 0) {
-      return <StyledLoadingText>{t('Loading...')}</StyledLoadingText>;
-    }
-    return error ? <Error error={error} /> : originNode;
-  };
+      if (onDropdownVisibleChange) {
+        onDropdownVisibleChange(isDropdownVisible);
+      }
+    };
 
-  // use a function instead of component since every rerender of the
-  // Select component will create a new component
-  const getSuffixIcon = () => {
-    if (isLoading) {
-      return <StyledSpin size="small" />;
-    }
-    if (showSearch && isDropdownVisible) {
-      return <SearchOutlined />;
-    }
-    return <DownOutlined />;
-  };
+    const dropdownRender = (
+      originNode: ReactElement & { ref?: RefObject<HTMLElement> },
+    ) => {
+      if (!isDropdownVisible) {
+        originNode.ref?.current?.scrollTo({ top: 0 });
+      }
+      if (isLoading && fullSelectOptions.length === 0) {
+        return <StyledLoadingText>{t('Loading...')}</StyledLoadingText>;
+      }
+      return error ? <Error error={error} /> : originNode;
+    };
+
+    // use a function instead of component since every rerender of the
+    // Select component will create a new component
+    const getSuffixIcon = () => {
+      if (isLoading) {
+        return <StyledSpin size="small" />;
+      }
+      if (showSearch && isDropdownVisible) {
+        return <SearchOutlined />;
+      }
+      return <DownOutlined />;
+    };
 
-  const handleClear = () => {
-    setSelectValue(undefined);
-    if (onClear) {
-      onClear();
-    }
-  };
+    const handleClear = () => {
+      setSelectValue(undefined);
+      if (onClear) {
+        onClear();
+      }
+    };
+
+    useEffect(() => {
+      // when `options` list is updated from component prop, reset states
+      fetchedQueries.current.clear();
+      setAllValuesLoaded(false);
+      setSelectOptions(initialOptions);
+    }, [initialOptions]);
+
+    useEffect(() => {
+      setSelectValue(value);
+    }, [value]);
+
+    // Stop the invocation of the debounced function after unmounting
+    useEffect(
+      () => () => {
+        debouncedFetchPage.cancel();
+      },
+      [debouncedFetchPage],
+    );
+
+    useEffect(() => {
+      if (loadingEnabled && allowFetch) {
+        // trigger fetch every time inputValue changes
+        if (inputValue) {
+          debouncedFetchPage(inputValue, 0);
+        } else {
+          fetchPage('', 0);
+        }
+      }
+    }, [loadingEnabled, fetchPage, allowFetch, inputValue, debouncedFetchPage]);
 
-  useEffect(() => {
-    // when `options` list is updated from component prop, reset states
-    fetchedQueries.current.clear();
-    setAllValuesLoaded(false);
-    setSelectOptions(initialOptions);
-  }, [initialOptions]);
-
-  useEffect(() => {
-    setSelectValue(value);
-  }, [value]);
-
-  // Stop the invocation of the debounced function after unmounting
-  useEffect(
-    () => () => {
-      debouncedFetchPage.cancel();
-    },
-    [debouncedFetchPage],
-  );
-
-  useEffect(() => {
-    if (loadingEnabled && allowFetch) {
-      // trigger fetch every time inputValue changes
-      if (inputValue) {
-        debouncedFetchPage(inputValue, 0);
-      } else {
-        fetchPage('', 0);
+    useEffect(() => {
+      if (loading !== undefined && loading !== isLoading) {
+        setIsLoading(loading);
       }
-    }
-  }, [loadingEnabled, fetchPage, allowFetch, inputValue, debouncedFetchPage]);
+    }, [isLoading, loading]);
 
-  useEffect(() => {
-    if (loading !== undefined && loading !== isLoading) {
-      setIsLoading(loading);
-    }
-  }, [isLoading, loading]);
-
-  const clearCache = () => fetchedQueries.current.clear();
-
-  useImperativeHandle(
-    ref,
-    () => ({
-      ...(ref.current as HTMLInputElement),
-      clearCache,
-    }),
-    [ref],
-  );
-
-  return (
-    <StyledContainer>
-      {header}
-      <StyledSelect
-        allowClear={!isLoading && allowClear}
-        aria-label={ariaLabel || name}
-        dropdownRender={dropdownRender}
-        filterOption={handleFilterOption}
-        filterSort={sortComparatorWithSearch}
-        getPopupContainer={
-          getPopupContainer || (triggerNode => triggerNode.parentNode)
-        }
-        labelInValue
-        maxTagCount={MAX_TAG_COUNT}
-        mode={mappedMode}
-        notFoundContent={isLoading ? t('Loading...') : notFoundContent}
-        onDeselect={handleOnDeselect}
-        onDropdownVisibleChange={handleOnDropdownVisibleChange}
-        onPopupScroll={handlePagination}
-        onSearch={showSearch ? handleOnSearch : undefined}
-        onSelect={handleOnSelect}
-        onClear={handleClear}
-        onChange={onChange}
-        options={hasCustomLabels ? undefined : fullSelectOptions}
-        placeholder={placeholder}
-        showSearch={showSearch}
-        showArrow
-        tokenSeparators={tokenSeparators || TOKEN_SEPARATORS}
-        value={selectValue}
-        suffixIcon={getSuffixIcon()}
-        menuItemSelectedIcon={
-          invertSelection ? (
-            <StyledStopOutlined iconSize="m" />
-          ) : (
-            <StyledCheckOutlined iconSize="m" />
-          )
-        }
-        ref={ref}
-        {...props}
-      >
-        {hasCustomLabels &&
-          fullSelectOptions.map(opt => {
-            const isOptObject = typeof opt === 'object';
-            const label = isOptObject ? opt?.label || opt.value : opt;
-            const value = isOptObject ? opt.value : opt;
-            const { customLabel, ...optProps } = opt;
-            return (
-              <Option {...optProps} key={value} label={label} value={value}>
-                {isOptObject && customLabel ? customLabel : label}
-              </Option>
-            );
-          })}
-      </StyledSelect>
-    </StyledContainer>
-  );
-};
+    const clearCache = () => fetchedQueries.current.clear();
+
+    useImperativeHandle(
+      ref,
+      () => ({
+        ...(ref.current as HTMLInputElement),
+        clearCache,
+      }),
+      [ref],
+    );
+
+    return (
+      <StyledContainer>
+        {header}
+        <StyledSelect
+          allowClear={!isLoading && allowClear}
+          aria-label={ariaLabel || name}
+          dropdownRender={dropdownRender}
+          filterOption={handleFilterOption}
+          filterSort={sortComparatorWithSearch}
+          getPopupContainer={
+            getPopupContainer || (triggerNode => triggerNode.parentNode)
+          }
+          labelInValue
+          maxTagCount={MAX_TAG_COUNT}
+          mode={mappedMode}
+          notFoundContent={isLoading ? t('Loading...') : notFoundContent}
+          onDeselect={handleOnDeselect}
+          onDropdownVisibleChange={handleOnDropdownVisibleChange}
+          onPopupScroll={handlePagination}
+          onSearch={showSearch ? handleOnSearch : undefined}
+          onSelect={handleOnSelect}
+          onClear={handleClear}
+          onChange={onChange}
+          options={hasCustomLabels ? undefined : fullSelectOptions}
+          placeholder={placeholder}
+          showSearch={showSearch}
+          showArrow
+          tokenSeparators={tokenSeparators || TOKEN_SEPARATORS}
+          value={selectValue}
+          suffixIcon={getSuffixIcon()}
+          menuItemSelectedIcon={
+            invertSelection ? (
+              <StyledStopOutlined iconSize="m" />
+            ) : (
+              <StyledCheckOutlined iconSize="m" />
+            )
+          }
+          ref={ref}
+          {...props}
+        >
+          {hasCustomLabels &&
+            fullSelectOptions.map(opt => {
+              const isOptObject = typeof opt === 'object';
+              const label = isOptObject ? opt?.label || opt.value : opt;
+              const value = isOptObject ? opt.value : opt;
+              const { customLabel, ...optProps } = opt;
+              return (
+                <Option {...optProps} key={value} label={label} value={value}>
+                  {isOptObject && customLabel ? customLabel : label}
+                </Option>
+              );
+            })}
+        </StyledSelect>
+      </StyledContainer>
+    );
+  },
+);
 
-export default forwardRef(AsyncSelect);
+export default AsyncSelect;
diff --git a/superset-frontend/src/components/Select/Select.tsx b/superset-frontend/src/components/Select/Select.tsx
index 04eccec83a..80f558d64d 100644
--- a/superset-frontend/src/components/Select/Select.tsx
+++ b/superset-frontend/src/components/Select/Select.tsx
@@ -216,279 +216,284 @@ export const propertyComparator =
  * Each of the categories come with different abilities. For a comprehensive guide please refer to
  * the storybook in src/components/Select/Select.stories.tsx.
  */
-const Select = (
-  {
-    allowClear,
-    allowNewOptions = false,
-    ariaLabel,
-    filterOption = true,
-    header = null,
-    invertSelection = false,
-    labelInValue = false,
-    loading,
-    mode = 'single',
-    name,
-    notFoundContent,
-    onChange,
-    onClear,
-    onDropdownVisibleChange,
-    optionFilterProps = ['label', 'value'],
-    options,
-    placeholder = t('Select ...'),
-    showSearch = true,
-    sortComparator = DEFAULT_SORT_COMPARATOR,
-    tokenSeparators,
-    value,
-    getPopupContainer,
-    ...props
-  }: SelectProps,
-  ref: RefObject<HTMLInputElement>,
-) => {
-  const isSingleMode = mode === 'single';
-  const shouldShowSearch = allowNewOptions ? true : showSearch;
-  const [selectValue, setSelectValue] = useState(value);
-  const [inputValue, setInputValue] = useState('');
-  const [isLoading, setIsLoading] = useState(loading);
-  const [isDropdownVisible, setIsDropdownVisible] = useState(false);
-  const mappedMode = isSingleMode
-    ? undefined
-    : allowNewOptions
-    ? 'tags'
-    : 'multiple';
-
-  const sortSelectedFirst = useCallback(
-    (a: AntdLabeledValue, b: AntdLabeledValue) =>
-      selectValue && a.value !== undefined && b.value !== undefined
-        ? Number(hasOption(b.value, selectValue)) -
-          Number(hasOption(a.value, selectValue))
-        : 0,
-    [selectValue],
-  );
-  const sortComparatorWithSearch = useCallback(
-    (a: AntdLabeledValue, b: AntdLabeledValue) =>
-      sortSelectedFirst(a, b) || sortComparator(a, b, inputValue),
-    [inputValue, sortComparator, sortSelectedFirst],
-  );
-
-  const initialOptions = useMemo(
-    () => (options && Array.isArray(options) ? options.slice() : EMPTY_OPTIONS),
-    [options],
-  );
-  const initialOptionsSorted = useMemo(
-    () => initialOptions.slice().sort(sortSelectedFirst),
-    [initialOptions, sortSelectedFirst],
-  );
-
-  const [selectOptions, setSelectOptions] =
-    useState<OptionsType>(initialOptionsSorted);
-
-  // add selected values to options list if they are not in it
-  const fullSelectOptions = useMemo(() => {
-    const missingValues: OptionsType = ensureIsArray(selectValue)
-      .filter(opt => !hasOption(getValue(opt), selectOptions))
-      .map(opt =>
-        isLabeledValue(opt) ? opt : { value: opt, label: String(opt) },
-      );
-    return missingValues.length > 0
-      ? missingValues.concat(selectOptions)
-      : selectOptions;
-  }, [selectOptions, selectValue]);
-
-  const hasCustomLabels = fullSelectOptions.some(opt => !!opt?.customLabel);
-
-  const handleOnSelect = (
-    selectedItem: string | number | AntdLabeledValue | undefined,
-  ) => {
-    if (isSingleMode) {
-      setSelectValue(selectedItem);
-    } else {
-      setSelectValue(previousState => {
-        const array = ensureIsArray(previousState);
-        const value = getValue(selectedItem);
-        // Tokenized values can contain duplicated values
-        if (!hasOption(value, array)) {
-          const result = [...array, selectedItem];
-          return isLabeledValue(selectedItem)
-            ? (result as AntdLabeledValue[])
-            : (result as (string | number)[]);
-        }
-        return previousState;
-      });
-    }
-    setInputValue('');
-  };
-
-  const handleOnDeselect = (
-    value: string | number | AntdLabeledValue | undefined,
+const Select = forwardRef(
+  (
+    {
+      allowClear,
+      allowNewOptions = false,
+      ariaLabel,
+      filterOption = true,
+      header = null,
+      invertSelection = false,
+      labelInValue = false,
+      loading,
+      mode = 'single',
+      name,
+      notFoundContent,
+      onChange,
+      onClear,
+      onDropdownVisibleChange,
+      optionFilterProps = ['label', 'value'],
+      options,
+      placeholder = t('Select ...'),
+      showSearch = true,
+      sortComparator = DEFAULT_SORT_COMPARATOR,
+      tokenSeparators,
+      value,
+      getPopupContainer,
+      ...props
+    }: SelectProps,
+    ref: RefObject<HTMLInputElement>,
   ) => {
-    if (Array.isArray(selectValue)) {
-      if (isLabeledValue(value)) {
-        const array = selectValue as AntdLabeledValue[];
-        setSelectValue(array.filter(element => element.value !== value.value));
+    const isSingleMode = mode === 'single';
+    const shouldShowSearch = allowNewOptions ? true : showSearch;
+    const [selectValue, setSelectValue] = useState(value);
+    const [inputValue, setInputValue] = useState('');
+    const [isLoading, setIsLoading] = useState(loading);
+    const [isDropdownVisible, setIsDropdownVisible] = useState(false);
+    const mappedMode = isSingleMode
+      ? undefined
+      : allowNewOptions
+      ? 'tags'
+      : 'multiple';
+
+    const sortSelectedFirst = useCallback(
+      (a: AntdLabeledValue, b: AntdLabeledValue) =>
+        selectValue && a.value !== undefined && b.value !== undefined
+          ? Number(hasOption(b.value, selectValue)) -
+            Number(hasOption(a.value, selectValue))
+          : 0,
+      [selectValue],
+    );
+    const sortComparatorWithSearch = useCallback(
+      (a: AntdLabeledValue, b: AntdLabeledValue) =>
+        sortSelectedFirst(a, b) || sortComparator(a, b, inputValue),
+      [inputValue, sortComparator, sortSelectedFirst],
+    );
+
+    const initialOptions = useMemo(
+      () =>
+        options && Array.isArray(options) ? options.slice() : EMPTY_OPTIONS,
+      [options],
+    );
+    const initialOptionsSorted = useMemo(
+      () => initialOptions.slice().sort(sortSelectedFirst),
+      [initialOptions, sortSelectedFirst],
+    );
+
+    const [selectOptions, setSelectOptions] =
+      useState<OptionsType>(initialOptionsSorted);
+
+    // add selected values to options list if they are not in it
+    const fullSelectOptions = useMemo(() => {
+      const missingValues: OptionsType = ensureIsArray(selectValue)
+        .filter(opt => !hasOption(getValue(opt), selectOptions))
+        .map(opt =>
+          isLabeledValue(opt) ? opt : { value: opt, label: String(opt) },
+        );
+      return missingValues.length > 0
+        ? missingValues.concat(selectOptions)
+        : selectOptions;
+    }, [selectOptions, selectValue]);
+
+    const hasCustomLabels = fullSelectOptions.some(opt => !!opt?.customLabel);
+
+    const handleOnSelect = (
+      selectedItem: string | number | AntdLabeledValue | undefined,
+    ) => {
+      if (isSingleMode) {
+        setSelectValue(selectedItem);
       } else {
-        const array = selectValue as (string | number)[];
-        setSelectValue(array.filter(element => element !== value));
+        setSelectValue(previousState => {
+          const array = ensureIsArray(previousState);
+          const value = getValue(selectedItem);
+          // Tokenized values can contain duplicated values
+          if (!hasOption(value, array)) {
+            const result = [...array, selectedItem];
+            return isLabeledValue(selectedItem)
+              ? (result as AntdLabeledValue[])
+              : (result as (string | number)[]);
+          }
+          return previousState;
+        });
       }
-    }
-    setInputValue('');
-  };
-
-  const handleOnSearch = (search: string) => {
-    const searchValue = search.trim();
-    if (allowNewOptions && isSingleMode) {
-      const newOption = searchValue &&
-        !hasOption(searchValue, fullSelectOptions, true) && {
-          label: searchValue,
-          value: searchValue,
-          isNewOption: true,
-        };
-      const cleanSelectOptions = fullSelectOptions.filter(
-        opt => !opt.isNewOption || hasOption(opt.value, selectValue),
-      );
-      const newOptions = newOption
-        ? [newOption, ...cleanSelectOptions]
-        : cleanSelectOptions;
-      setSelectOptions(newOptions);
-    }
-    setInputValue(search);
-  };
+      setInputValue('');
+    };
+
+    const handleOnDeselect = (
+      value: string | number | AntdLabeledValue | undefined,
+    ) => {
+      if (Array.isArray(selectValue)) {
+        if (isLabeledValue(value)) {
+          const array = selectValue as AntdLabeledValue[];
+          setSelectValue(
+            array.filter(element => element.value !== value.value),
+          );
+        } else {
+          const array = selectValue as (string | number)[];
+          setSelectValue(array.filter(element => element !== value));
+        }
+      }
+      setInputValue('');
+    };
+
+    const handleOnSearch = (search: string) => {
+      const searchValue = search.trim();
+      if (allowNewOptions && isSingleMode) {
+        const newOption = searchValue &&
+          !hasOption(searchValue, fullSelectOptions, true) && {
+            label: searchValue,
+            value: searchValue,
+            isNewOption: true,
+          };
+        const cleanSelectOptions = fullSelectOptions.filter(
+          opt => !opt.isNewOption || hasOption(opt.value, selectValue),
+        );
+        const newOptions = newOption
+          ? [newOption, ...cleanSelectOptions]
+          : cleanSelectOptions;
+        setSelectOptions(newOptions);
+      }
+      setInputValue(search);
+    };
 
-  const handleFilterOption = (search: string, option: AntdLabeledValue) => {
-    if (typeof filterOption === 'function') {
-      return filterOption(search, option);
-    }
+    const handleFilterOption = (search: string, option: AntdLabeledValue) => {
+      if (typeof filterOption === 'function') {
+        return filterOption(search, option);
+      }
 
-    if (filterOption) {
-      const searchValue = search.trim().toLowerCase();
-      if (optionFilterProps && optionFilterProps.length) {
-        return optionFilterProps.some(prop => {
-          const optionProp = option?.[prop]
-            ? String(option[prop]).trim().toLowerCase()
-            : '';
-          return optionProp.includes(searchValue);
-        });
+      if (filterOption) {
+        const searchValue = search.trim().toLowerCase();
+        if (optionFilterProps && optionFilterProps.length) {
+          return optionFilterProps.some(prop => {
+            const optionProp = option?.[prop]
+              ? String(option[prop]).trim().toLowerCase()
+              : '';
+            return optionProp.includes(searchValue);
+          });
+        }
       }
-    }
 
-    return false;
-  };
+      return false;
+    };
 
-  const handleOnDropdownVisibleChange = (isDropdownVisible: boolean) => {
-    setIsDropdownVisible(isDropdownVisible);
+    const handleOnDropdownVisibleChange = (isDropdownVisible: boolean) => {
+      setIsDropdownVisible(isDropdownVisible);
 
-    // if no search input value, force sort options because it won't be sorted by
-    // `filterSort`.
-    if (isDropdownVisible && !inputValue && selectOptions.length > 1) {
-      if (!isEqual(initialOptionsSorted, selectOptions)) {
-        setSelectOptions(initialOptionsSorted);
+      // if no search input value, force sort options because it won't be sorted by
+      // `filterSort`.
+      if (isDropdownVisible && !inputValue && selectOptions.length > 1) {
+        if (!isEqual(initialOptionsSorted, selectOptions)) {
+          setSelectOptions(initialOptionsSorted);
+        }
       }
-    }
-    if (onDropdownVisibleChange) {
-      onDropdownVisibleChange(isDropdownVisible);
-    }
-  };
-
-  const dropdownRender = (
-    originNode: ReactElement & { ref?: RefObject<HTMLElement> },
-  ) => {
-    if (!isDropdownVisible) {
-      originNode.ref?.current?.scrollTo({ top: 0 });
-    }
-    if (isLoading && fullSelectOptions.length === 0) {
-      return <StyledLoadingText>{t('Loading...')}</StyledLoadingText>;
-    }
-    return originNode;
-  };
-
-  // use a function instead of component since every rerender of the
-  // Select component will create a new component
-  const getSuffixIcon = () => {
-    if (isLoading) {
-      return <StyledSpin size="small" />;
-    }
-    if (shouldShowSearch && isDropdownVisible) {
-      return <SearchOutlined />;
-    }
-    return <DownOutlined />;
-  };
+      if (onDropdownVisibleChange) {
+        onDropdownVisibleChange(isDropdownVisible);
+      }
+    };
 
-  const handleClear = () => {
-    setSelectValue(undefined);
-    if (onClear) {
-      onClear();
-    }
-  };
+    const dropdownRender = (
+      originNode: ReactElement & { ref?: RefObject<HTMLElement> },
+    ) => {
+      if (!isDropdownVisible) {
+        originNode.ref?.current?.scrollTo({ top: 0 });
+      }
+      if (isLoading && fullSelectOptions.length === 0) {
+        return <StyledLoadingText>{t('Loading...')}</StyledLoadingText>;
+      }
+      return originNode;
+    };
+
+    // use a function instead of component since every rerender of the
+    // Select component will create a new component
+    const getSuffixIcon = () => {
+      if (isLoading) {
+        return <StyledSpin size="small" />;
+      }
+      if (shouldShowSearch && isDropdownVisible) {
+        return <SearchOutlined />;
+      }
+      return <DownOutlined />;
+    };
 
-  useEffect(() => {
-    // when `options` list is updated from component prop, reset states
-    setSelectOptions(initialOptions);
-  }, [initialOptions]);
+    const handleClear = () => {
+      setSelectValue(undefined);
+      if (onClear) {
+        onClear();
+      }
+    };
 
-  useEffect(() => {
-    setSelectValue(value);
-  }, [value]);
+    useEffect(() => {
+      // when `options` list is updated from component prop, reset states
+      setSelectOptions(initialOptions);
+    }, [initialOptions]);
 
-  useEffect(() => {
-    if (loading !== undefined && loading !== isLoading) {
-      setIsLoading(loading);
-    }
-  }, [isLoading, loading]);
-
-  return (
-    <StyledContainer>
-      {header}
-      <StyledSelect
-        allowClear={!isLoading && allowClear}
-        aria-label={ariaLabel || name}
-        dropdownRender={dropdownRender}
-        filterOption={handleFilterOption}
-        filterSort={sortComparatorWithSearch}
-        getPopupContainer={
-          getPopupContainer || (triggerNode => triggerNode.parentNode)
-        }
-        labelInValue={labelInValue}
-        maxTagCount={MAX_TAG_COUNT}
-        mode={mappedMode}
-        notFoundContent={isLoading ? t('Loading...') : notFoundContent}
-        onDeselect={handleOnDeselect}
-        onDropdownVisibleChange={handleOnDropdownVisibleChange}
-        onPopupScroll={undefined}
-        onSearch={shouldShowSearch ? handleOnSearch : undefined}
-        onSelect={handleOnSelect}
-        onClear={handleClear}
-        onChange={onChange}
-        options={hasCustomLabels ? undefined : fullSelectOptions}
-        placeholder={placeholder}
-        showSearch={shouldShowSearch}
-        showArrow
-        tokenSeparators={tokenSeparators || TOKEN_SEPARATORS}
-        value={selectValue}
-        suffixIcon={getSuffixIcon()}
-        menuItemSelectedIcon={
-          invertSelection ? (
-            <StyledStopOutlined iconSize="m" />
-          ) : (
-            <StyledCheckOutlined iconSize="m" />
-          )
-        }
-        ref={ref}
-        {...props}
-      >
-        {hasCustomLabels &&
-          fullSelectOptions.map(opt => {
-            const isOptObject = typeof opt === 'object';
-            const label = isOptObject ? opt?.label || opt.value : opt;
-            const value = isOptObject ? opt.value : opt;
-            const { customLabel, ...optProps } = opt;
-            return (
-              <Option {...optProps} key={value} label={label} value={value}>
-                {isOptObject && customLabel ? customLabel : label}
-              </Option>
-            );
-          })}
-      </StyledSelect>
-    </StyledContainer>
-  );
-};
+    useEffect(() => {
+      setSelectValue(value);
+    }, [value]);
 
-export default forwardRef(Select);
+    useEffect(() => {
+      if (loading !== undefined && loading !== isLoading) {
+        setIsLoading(loading);
+      }
+    }, [isLoading, loading]);
+
+    return (
+      <StyledContainer>
+        {header}
+        <StyledSelect
+          allowClear={!isLoading && allowClear}
+          aria-label={ariaLabel || name}
+          dropdownRender={dropdownRender}
+          filterOption={handleFilterOption}
+          filterSort={sortComparatorWithSearch}
+          getPopupContainer={
+            getPopupContainer || (triggerNode => triggerNode.parentNode)
+          }
+          labelInValue={labelInValue}
+          maxTagCount={MAX_TAG_COUNT}
+          mode={mappedMode}
+          notFoundContent={isLoading ? t('Loading...') : notFoundContent}
+          onDeselect={handleOnDeselect}
+          onDropdownVisibleChange={handleOnDropdownVisibleChange}
+          onPopupScroll={undefined}
+          onSearch={shouldShowSearch ? handleOnSearch : undefined}
+          onSelect={handleOnSelect}
+          onClear={handleClear}
+          onChange={onChange}
+          options={hasCustomLabels ? undefined : fullSelectOptions}
+          placeholder={placeholder}
+          showSearch={shouldShowSearch}
+          showArrow
+          tokenSeparators={tokenSeparators || TOKEN_SEPARATORS}
+          value={selectValue}
+          suffixIcon={getSuffixIcon()}
+          menuItemSelectedIcon={
+            invertSelection ? (
+              <StyledStopOutlined iconSize="m" />
+            ) : (
+              <StyledCheckOutlined iconSize="m" />
+            )
+          }
+          ref={ref}
+          {...props}
+        >
+          {hasCustomLabels &&
+            fullSelectOptions.map(opt => {
+              const isOptObject = typeof opt === 'object';
+              const label = isOptObject ? opt?.label || opt.value : opt;
+              const value = isOptObject ? opt.value : opt;
+              const { customLabel, ...optProps } = opt;
+              return (
+                <Option {...optProps} key={value} label={label} value={value}>
+                  {isOptObject && customLabel ? customLabel : label}
+                </Option>
+              );
+            })}
+        </StyledSelect>
+      </StyledContainer>
+    );
+  },
+);
+
+export default Select;
diff --git a/superset-frontend/src/components/Select/WindowedSelect/windowed.tsx b/superset-frontend/src/components/Select/WindowedSelect/windowed.tsx
index 4c2f64aa13..a611cf36c9 100644
--- a/superset-frontend/src/components/Select/WindowedSelect/windowed.tsx
+++ b/superset-frontend/src/components/Select/WindowedSelect/windowed.tsx
@@ -68,13 +68,17 @@ export function MenuList<OptionType extends OptionTypeBase>({
 export default function windowed<OptionType extends OptionTypeBase>(
   SelectComponent: ComponentType<SelectProps<OptionType>>,
 ): WindowedSelectComponentType<OptionType> {
-  function WindowedSelect(
-    props: WindowedSelectProps<OptionType>,
-    ref: React.RefObject<Select<OptionType>>,
-  ) {
-    const { components: components_ = {}, ...restProps } = props;
-    const components = { ...components_, MenuList };
-    return <SelectComponent components={components} ref={ref} {...restProps} />;
-  }
-  return forwardRef(WindowedSelect);
+  const WindowedSelect = forwardRef(
+    (
+      props: WindowedSelectProps<OptionType>,
+      ref: React.RefObject<Select<OptionType>>,
+    ) => {
+      const { components: components_ = {}, ...restProps } = props;
+      const components = { ...components_, MenuList };
+      return (
+        <SelectComponent components={components} ref={ref} {...restProps} />
+      );
+    },
+  );
+  return WindowedSelect;
 }