Bladeren bron

refactor SearchTypeahead

Yuki Takei 4 jaren geleden
bovenliggende
commit
b6cd3998fe

+ 2 - 2
packages/app/src/client/interfaces/react-bootstrap-typeahead.ts

@@ -1,4 +1,4 @@
-// https://github.com/ericgio/react-bootstrap-typeahead/blob/3.x/docs/Props.md
+// https://github.com/ericgio/react-bootstrap-typeahead/blob/5.x/docs/API.md
 export type TypeaheadProps = {
   dropup?: boolean,
   emptyLabel?: string,
@@ -8,6 +8,6 @@ export type TypeaheadProps = {
   onChange?: (data: unknown[]) => void,
   onBlur?: () => void,
   onFocus?: () => void,
-  onInputChange?: (text: string) => void,
+  onIncrementalSearch?: (text: string) => void,
   onKeyDown?: (input: string) => void,
 };

+ 1 - 1
packages/app/src/components/Navbar/GlobalSearch.tsx

@@ -103,7 +103,7 @@ const GlobalSearch: FC<Props> = (props: Props) => {
           onChange={gotoPage}
           onBlur={() => setFocused(false)}
           onFocus={() => setFocused(true)}
-          onInputChange={text => setText(text)}
+          onIncrementalSearch={text => setText(text)}
           onSubmit={search}
         />
         { isIndicatorShown && (

+ 8 - 10
packages/app/src/components/SearchForm.tsx

@@ -5,6 +5,7 @@ import React, {
 import { useTranslation } from 'react-i18next';
 
 import { IFocusable } from '~/client/interfaces/focusable';
+import { TypeaheadProps } from '~/client/interfaces/react-bootstrap-typeahead';
 import { IPageWithMeta } from '~/interfaces/page';
 import { IPageSearchMeta } from '~/interfaces/search';
 
@@ -80,26 +81,23 @@ const SearchFormHelp: FC<SearchFormHelpProps> = (props: SearchFormHelpProps) =>
 };
 
 
-type Props = {
+type Props = TypeaheadProps & {
   isSearchServiceReachable: boolean,
 
-  dropup?: boolean,
-  keyword?: string,
+  keywordOnInit?: string,
   disableIncrementalSearch?: boolean,
   onChange?: (data: IPageWithMeta<IPageSearchMeta>[]) => void,
-  onBlur?: () => void,
-  onFocus?: () => void,
   onSubmit?: (input: string) => void,
-  onInputChange?: (text: string) => void,
 };
 
 
 const SearchForm: ForwardRefRenderFunction<IFocusable, Props> = (props: Props, ref) => {
   const { t } = useTranslation();
   const {
-    isSearchServiceReachable, dropup,
+    isSearchServiceReachable,
+    keywordOnInit,
     disableIncrementalSearch,
-    onChange, onBlur, onFocus, onSubmit, onInputChange,
+    dropup, onChange, onBlur, onFocus, onSubmit, onIncrementalSearch,
   } = props;
 
   const [searchError, setSearchError] = useState<Error | null>(null);
@@ -134,7 +132,7 @@ const SearchForm: ForwardRefRenderFunction<IFocusable, Props> = (props: Props, r
       disableIncrementalSearch={disableIncrementalSearch}
       onChange={onChange}
       onSubmit={onSubmit}
-      onInputChange={onInputChange}
+      onIncrementalSearch={onIncrementalSearch}
       onSearchError={err => setSearchError(err)}
       onBlur={() => {
         setShownHelp(false);
@@ -149,7 +147,7 @@ const SearchForm: ForwardRefRenderFunction<IFocusable, Props> = (props: Props, r
         }
       }}
       helpElement={<SearchFormHelp isShownHelp={isShownHelp} isReachable={isSearchServiceReachable} />}
-      keywordOnInit={props.keyword}
+      keywordOnInit={keywordOnInit}
     />
   );
 };

+ 1 - 1
packages/app/src/components/SearchPage/SearchControl.tsx

@@ -66,7 +66,7 @@ const SearchControl: FC <Props> = React.memo((props: Props) => {
         <div className="flex-grow-1 mx-4">
           <SearchForm
             isSearchServiceReachable={isSearchServiceReachable}
-            keyword={keyword}
+            keywordOnInit={keyword}
             disableIncrementalSearch
             onSubmit={searchFormSubmittedHandler}
           />

+ 25 - 69
packages/app/src/components/SearchTypeahead.tsx

@@ -1,6 +1,6 @@
 import React, {
   FC, ForwardRefRenderFunction, forwardRef, useImperativeHandle,
-  KeyboardEvent, useCallback, useRef, useState, MouseEvent,
+  KeyboardEvent, useCallback, useRef, useState, MouseEvent, useEffect,
 } from 'react';
 
 import { AsyncTypeahead } from 'react-bootstrap-typeahead';
@@ -9,9 +9,9 @@ import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
 
 import { IFocusable } from '~/client/interfaces/focusable';
 import { TypeaheadProps } from '~/client/interfaces/react-bootstrap-typeahead';
-import { apiGet } from '~/client/util/apiv1-client';
-import { IFormattedSearchResult, IPageSearchMeta } from '~/interfaces/search';
+import { IPageSearchMeta } from '~/interfaces/search';
 import { IPageWithMeta } from '~/interfaces/page';
+import { useSWRxFullTextSearch } from '~/stores/search';
 
 
 type ResetFormButtonProps = {
@@ -34,7 +34,6 @@ const ResetFormButton: FC<ResetFormButtonProps> = (props: ResetFormButtonProps)
 
 
 type Props = TypeaheadProps & {
-  onSearchSuccess?: (res: IPageWithMeta<IPageSearchMeta>[]) => void,
   onSearchError?: (err: Error) => void,
   onSubmit?: (input: string) => void,
   inputName?: string,
@@ -56,16 +55,17 @@ type TypeaheadInstanceFactory = {
 
 const SearchTypeahead: ForwardRefRenderFunction<IFocusable, Props> = (props: Props, ref) => {
   const {
-    onSearchSuccess, onSearchError, onInputChange, onSubmit,
+    onSearchError, onIncrementalSearch, onSubmit,
     emptyLabel, helpElement, keywordOnInit, disableIncrementalSearch,
   } = props;
 
   // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
   const [input, setInput] = useState(props.keywordOnInit!);
-  const [pages, setPages] = useState<IPageWithMeta<IPageSearchMeta>[]>();
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  const [searchError, setSearchError] = useState<Error | null>(null);
-  const [isLoading, setLoading] = useState(false);
+
+  const keyword = disableIncrementalSearch ? null : input;
+  const { data: searchResult, error: searchError } = useSWRxFullTextSearch(keyword, { limit: 10 });
+
+  const isLoading = searchResult == null && searchError == null;
 
   const typeaheadRef = useRef<TypeaheadInstanceFactory>(null);
 
@@ -94,69 +94,21 @@ const SearchTypeahead: ForwardRefRenderFunction<IFocusable, Props> = (props: Pro
 
     setInput('');
     changeKeyword('');
-    setPages([]);
 
     focusToTypeahead();
 
-    if (onInputChange != null) {
-      onInputChange('');
+    if (onIncrementalSearch != null) {
+      onIncrementalSearch('');
     }
   };
 
-  /**
-   * Callback function which is occured when search is exit successfully
-   */
-  const searchSuccessHandler = useCallback((result: IFormattedSearchResult) => {
-    const searchResultData = result.data;
-    setPages(searchResultData);
-
-    if (onSearchSuccess != null) {
-      onSearchSuccess(searchResultData);
-    }
-  }, [onSearchSuccess]);
-
-  /**
-   * Callback function which is occured when search is exit abnormaly
-   */
-  const searchErrorHandler = useCallback((err: Error) => {
-    setSearchError(err);
-
-    if (onSearchError != null) {
-      onSearchError(err);
-    }
-  }, [onSearchError]);
-
-  const search = useCallback(async(keyword: string) => {
-    if (disableIncrementalSearch || keyword === '') {
-      return;
-    }
-
-    setLoading(true);
-
-    try {
-      const result = await apiGet('/search', { q: keyword }) as IFormattedSearchResult;
-      searchSuccessHandler(result);
-    }
-    catch (err) {
-      searchErrorHandler(err);
-    }
-    finally {
-      setLoading(false);
-    }
-
-  }, [disableIncrementalSearch, searchErrorHandler, searchSuccessHandler]);
-
-  const inputChangeHandler = useCallback((text: string) => {
+  const searchHandler = useCallback((text: string) => {
     setInput(text);
 
-    if (onInputChange != null) {
-      onInputChange(text);
-    }
-
-    if (text === '') {
-      setPages([]);
+    if (onIncrementalSearch != null) {
+      onIncrementalSearch(text);
     }
-  }, [onInputChange]);
+  }, [onIncrementalSearch]);
 
   const keyDownHandler = useCallback((event: KeyboardEvent) => {
     if (event.keyCode === 13) { // Enter key
@@ -180,6 +132,12 @@ const SearchTypeahead: ForwardRefRenderFunction<IFocusable, Props> = (props: Pro
     return <></>;
   };
 
+  useEffect(() => {
+    if (onSearchError != null && searchError != null) {
+      onSearchError(searchError);
+    }
+  }, [onSearchError, searchError]);
+
   const defaultSelected = (keywordOnInit !== '')
     ? [{ path: keywordOnInit }]
     : [];
@@ -206,19 +164,17 @@ const SearchTypeahead: ForwardRefRenderFunction<IFocusable, Props> = (props: Pro
         {...props}
         id="search-typeahead-asynctypeahead"
         ref={typeaheadRef}
-        inputProps={inputProps}
+        // inputProps={inputProps}
         isLoading={isLoading}
         labelKey={data => data?.pageData?.path || keywordOnInit || ''} // https://github.com/ericgio/react-bootstrap-typeahead/blob/master/docs/Rendering.md#labelkey-stringfunction
-        minLength={0}
-        options={pages} // Search result (Some page names)
+        options={searchResult?.data} // Search result (Some page names)
+        filterBy={() => true}
         promptText={props.helpElement}
         emptyLabel={disableIncrementalSearch ? null : getEmptyLabel()}
         align="left"
-        onSearch={search}
-        onInputChange={inputChangeHandler}
+        onSearch={searchHandler}
         onKeyDown={keyDownHandler}
         renderMenuItemChildren={renderMenuItemChildren}
-        caseSensitive={false}
         defaultSelected={defaultSelected}
         autoFocus={props.autoFocus}
         onBlur={props.onBlur}

+ 4 - 4
packages/app/src/stores/search.tsx

@@ -32,7 +32,7 @@ type ISearchConfigurationsFixed = {
 }
 
 export type ISearchConditions = ISearchConfigurationsFixed & {
-  keyword: string,
+  keyword: string | null,
   rawQuery: string,
 }
 
@@ -51,7 +51,7 @@ const createSearchQuery = (keyword: string, includeTrashPages: boolean, includeU
 };
 
 export const useSWRxFullTextSearch = (
-    keyword: string, configurations: ISearchConfigurations, disableTermManager = false,
+    keyword: string | null, configurations: ISearchConfigurations, disableTermManager = false,
 ): SWRResponse<IFormattedSearchResult, Error> & { conditions: ISearchConditions } => {
   const { data: termNumber } = useFullTextSearchTermManager(disableTermManager);
 
@@ -67,10 +67,10 @@ export const useSWRxFullTextSearch = (
     includeTrashPages: includeTrashPages ?? false,
     includeUserPages: includeUserPages ?? false,
   };
-  const rawQuery = createSearchQuery(keyword, fixedConfigurations.includeTrashPages, fixedConfigurations.includeUserPages);
+  const rawQuery = createSearchQuery(keyword ?? '', fixedConfigurations.includeTrashPages, fixedConfigurations.includeUserPages);
 
   const swrResult = useSWRImmutable(
-    ['/search', keyword, fixedConfigurations, termNumber],
+    keyword == null ? null : ['/search', keyword, fixedConfigurations, termNumber],
     (endpoint, keyword, fixedConfigurations) => {
       const {
         limit, offset, sort, order,