Przeglądaj źródła

WIP: convert to functional component

Yuki Takei 4 lat temu
rodzic
commit
69f9199fd4

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

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

+ 108 - 153
packages/app/src/components/SearchForm.tsx

@@ -1,177 +1,132 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { withUnstatedContainers } from './UnstatedUtils';
-import AppContainer from '~/client/services/AppContainer';
-
-import SearchTypeahead from './SearchTypeahead';
-
-// SearchTypeahead wrapper
-class SearchForm extends React.Component {
+import React, { FC, ForwardRefRenderFunction, useState } from 'react';
+import { useTranslation } from 'react-i18next';
 
 
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      searchError: null,
-      isShownHelp: false,
-    };
-
-    this.onSearchError = this.onSearchError.bind(this);
-    this.onChange = this.onChange.bind(this);
-    this.onBlur = this.onBlur.bind(this);
-    this.onFocus = this.onFocus.bind(this);
-  }
+import AppContainer from '~/client/services/AppContainer';
+import { IFocusable } from '~/client/interfaces/focusable';
 
 
-  componentDidMount() {
-  }
+import { withUnstatedContainers } from './UnstatedUtils';
 
 
-  componentWillUnmount() {
-  }
+import SearchTypeahead from './SearchTypeahead';
 
 
-  onSearchError(err) {
-    this.setState({
-      searchError: err,
-    });
-  }
 
 
-  onChange(selected) {
-    const page = selected[0]; // should be single page selected
+type SearchFormHelpProps = {
+  isReachable: boolean,
+  isShownHelp: boolean,
+}
 
 
-    // navigate to page
-    if (page != null) {
-      window.location = page.path;
-    }
-  }
+const SearchFormHelp: FC<SearchFormHelpProps> = (props: SearchFormHelpProps) => {
+  const { t } = useTranslation();
 
 
-  onBlur() {
-    this.setState({
-      isShownHelp: false,
-    });
+  const { isReachable, isShownHelp } = props;
 
 
-    this.getHelpElement();
+  if (!isReachable) {
+    return (
+      <>
+        <h5 className="text-danger">Error occured on Search Service</h5>
+        Try to reconnect from management page.
+      </>
+    );
   }
   }
 
 
-  onFocus() {
-    this.setState({
-      isShownHelp: true,
-    });
+  if (!isShownHelp) {
+    return <></>;
   }
   }
 
 
-  getHelpElement() {
-    const { t, appContainer } = this.props;
-    const { isShownHelp } = this.state;
-
-    const config = appContainer.getConfig();
-    const isReachable = config.isSearchServiceReachable;
-
-
-    if (!isReachable) {
-      return (
-        <>
-          <h5 className="text-danger">Error occured on Search Service</h5>
-          Try to reconnect from management page.
-        </>
-      );
-    }
-
-    if (!isShownHelp) {
-      return <></>;
-    }
+  return (
+    <table className="table grw-search-table search-help m-0">
+      <caption className="text-left text-primary p-2">
+        <h5 className="h6"><i className="icon-magnifier pr-2 mb-2" />{ t('search_help.title') }</h5>
+      </caption>
+      <tbody>
+        <tr>
+          <th className="py-2">
+            <code>word1</code> <code>word2</code><br></br>
+            <small>({ t('search_help.and.syntax help') })</small>
+          </th>
+          <td><h6 className="m-0">{ t('search_help.and.desc', { word1: 'word1', word2: 'word2' }) }</h6></td>
+        </tr>
+        <tr>
+          <th className="py-2">
+            <code>&quot;This is GROWI&quot;</code><br></br>
+            <small>({ t('search_help.phrase.syntax help') })</small>
+          </th>
+          <td><h6 className="m-0">{ t('search_help.phrase.desc', { phrase: 'This is GROWI' }) }</h6></td>
+        </tr>
+        <tr>
+          <th className="py-2"><code>-keyword</code></th>
+          <td><h6 className="m-0">{ t('search_help.exclude.desc', { word: 'keyword' }) }</h6></td>
+        </tr>
+        <tr>
+          <th className="py-2"><code>prefix:/user/</code></th>
+          <td><h6 className="m-0">{ t('search_help.prefix.desc', { path: '/user/' }) }</h6></td>
+        </tr>
+        <tr>
+          <th className="py-2"><code>-prefix:/user/</code></th>
+          <td><h6 className="m-0">{ t('search_help.exclude_prefix.desc', { path: '/user/' }) }</h6></td>
+        </tr>
+        <tr>
+          <th className="py-2"><code>tag:wiki</code></th>
+          <td><h6 className="m-0">{ t('search_help.tag.desc', { tag: 'wiki' }) }</h6></td>
+        </tr>
+        <tr>
+          <th className="py-2"><code>-tag:wiki</code></th>
+          <td><h6 className="m-0">{ t('search_help.exclude_tag.desc', { tag: 'wiki' }) }</h6></td>
+        </tr>
+      </tbody>
+    </table>
+  );
+};
 
 
-    return (
-      <table className="table grw-search-table search-help m-0">
-        <caption className="text-left text-primary p-2">
-          <h5 className="h6"><i className="icon-magnifier pr-2 mb-2" />{ t('search_help.title') }</h5>
-        </caption>
-        <tbody>
-          <tr>
-            <th className="py-2">
-              <code>word1</code> <code>word2</code><br></br>
-              <small>({ t('search_help.and.syntax help') })</small>
-            </th>
-            <td><h6 className="m-0">{ t('search_help.and.desc', { word1: 'word1', word2: 'word2' }) }</h6></td>
-          </tr>
-          <tr>
-            <th className="py-2">
-              <code>&quot;This is GROWI&quot;</code><br></br>
-              <small>({ t('search_help.phrase.syntax help') })</small>
-            </th>
-            <td><h6 className="m-0">{ t('search_help.phrase.desc', { phrase: 'This is GROWI' }) }</h6></td>
-          </tr>
-          <tr>
-            <th className="py-2"><code>-keyword</code></th>
-            <td><h6 className="m-0">{ t('search_help.exclude.desc', { word: 'keyword' }) }</h6></td>
-          </tr>
-          <tr>
-            <th className="py-2"><code>prefix:/user/</code></th>
-            <td><h6 className="m-0">{ t('search_help.prefix.desc', { path: '/user/' }) }</h6></td>
-          </tr>
-          <tr>
-            <th className="py-2"><code>-prefix:/user/</code></th>
-            <td><h6 className="m-0">{ t('search_help.exclude_prefix.desc', { path: '/user/' }) }</h6></td>
-          </tr>
-          <tr>
-            <th className="py-2"><code>tag:wiki</code></th>
-            <td><h6 className="m-0">{ t('search_help.tag.desc', { tag: 'wiki' }) }</h6></td>
-          </tr>
-          <tr>
-            <th className="py-2"><code>-tag:wiki</code></th>
-            <td><h6 className="m-0">{ t('search_help.exclude_tag.desc', { tag: 'wiki' }) }</h6></td>
-          </tr>
-        </tbody>
-      </table>
-    );
-  }
 
 
-  render() {
-    const { t, appContainer, dropup } = this.props;
+type Props = {
+  appContainer: AppContainer,
 
 
-    const config = appContainer.getConfig();
-    const isReachable = config.isSearchServiceReachable;
+  dropup?: boolean,
+  keyword?: string,
+  onSubmit?: (input: string) => void,
+  onInputChange?: (text: string) => void,
+};
 
 
-    const placeholder = isReachable
-      ? 'Search ...'
-      : 'Error on Search Service';
-    const emptyLabel = (this.state.searchError !== null)
-      ? 'Error on searching.'
-      : t('search.search page bodies');
 
 
-    return (
-      <SearchTypeahead
-        dropup={dropup}
-        onChange={this.onChange}
-        onSubmit={this.props.onSubmit}
-        onInputChange={this.props.onInputChange}
-        onSearchError={this.onSearchError}
-        emptyLabel={emptyLabel}
-        placeholder={placeholder}
-        helpElement={this.getHelpElement()}
-        keywordOnInit={this.props.keyword}
-        onBlur={this.onBlur}
-        onFocus={this.onFocus}
-      />
-    );
-  }
-
-}
+const SearchForm: ForwardRefRenderFunction<IFocusable, Props> = (props: Props, ref) => {
+  const { t } = useTranslation();
+  const {
+    appContainer, dropup, onSubmit, onInputChange,
+  } = props;
+
+  const [searchError, setSearchError] = useState<Error | null>(null);
+  const [isShownHelp, setShownHelp] = useState(false);
+
+  const config = appContainer.getConfig();
+  const isReachable = config.isSearchServiceReachable;
+
+  const placeholder = isReachable
+    ? 'Search ...'
+    : 'Error on Search Service';
+
+  const emptyLabel = (searchError != null)
+    ? 'Error on searching.'
+    : t('search.search page bodies');
+
+  return (
+    <SearchTypeahead
+      dropup={dropup}
+      emptyLabel={emptyLabel}
+      placeholder={placeholder}
+      onSubmit={onSubmit}
+      onInputChange={onInputChange}
+      onSearchError={err => setSearchError(err)}
+      onBlur={() => setShownHelp(false)}
+      onFocus={() => setShownHelp(true)}
+      helpElement={<SearchFormHelp isShownHelp={isShownHelp} isReachable={isReachable} />}
+      keywordOnInit={props.keyword}
+    />
+  );
+};
 
 
 /**
 /**
  * Wrapper component for using unstated
  * Wrapper component for using unstated
  */
  */
 const SearchFormWrapper = withUnstatedContainers(SearchForm, [AppContainer]);
 const SearchFormWrapper = withUnstatedContainers(SearchForm, [AppContainer]);
 
 
-SearchForm.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-
-  dropup: PropTypes.bool,
-  keyword: PropTypes.string,
-  onSubmit: PropTypes.func.isRequired,
-  onInputChange: PropTypes.func,
-};
-
-SearchForm.defaultProps = {
-  onInputChange: () => {},
-};
-
 export default SearchFormWrapper;
 export default SearchFormWrapper;

+ 2 - 9
packages/app/src/components/SearchTypeahead.tsx

@@ -10,6 +10,7 @@ import { AsyncTypeahead } from 'react-bootstrap-typeahead';
 import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
 import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
 
 
 import { IFocusable } from '~/client/interfaces/focusable';
 import { IFocusable } from '~/client/interfaces/focusable';
+import { TypeaheadProps } from '~/client/interfaces/react-bootstrap-typeahead';
 import { apiGet } from '~/client/util/apiv1-client';
 import { apiGet } from '~/client/util/apiv1-client';
 import { IPage } from '~/interfaces/page';
 import { IPage } from '~/interfaces/page';
 
 
@@ -36,21 +37,13 @@ const ResetFormButton: FC<ResetFormButtonProps> = (props: ResetFormButtonProps)
 };
 };
 
 
 
 
-type Props = {
+type Props = TypeaheadProps & {
   onSearchSuccess?: (res: IPage[]) => void,
   onSearchSuccess?: (res: IPage[]) => void,
   onSearchError?: (err: Error) => void,
   onSearchError?: (err: Error) => void,
-  onChange?: () => void,
-  onBlur?: () => void,
-  onFocus?: () => void,
   onSubmit?: (input: string) => void,
   onSubmit?: (input: string) => void,
-  onInputChange?: (text: string) => void,
   inputName?: string,
   inputName?: string,
-  emptyLabel?: string,
-  emptyLabelExceptError?: string,
-  placeholder?: string,
   keywordOnInit?: string,
   keywordOnInit?: string,
   helpElement?: any,
   helpElement?: any,
-  autoFocus?: boolean,
   behaviorOfResetBtn?: 'restore' | 'clear',
   behaviorOfResetBtn?: 'restore' | 'clear',
 };
 };