diff --git a/app/soapbox/features/compose/components/search_results.js b/app/soapbox/features/compose/components/search_results.js
deleted file mode 100644
index 05cbe2663..000000000
--- a/app/soapbox/features/compose/components/search_results.js
+++ /dev/null
@@ -1,174 +0,0 @@
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { FormattedMessage } from 'react-intl';
-import { defineMessages, injectIntl } from 'react-intl';
-
-import ScrollableList from 'soapbox/components/scrollable_list';
-import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder_account';
-import PlaceholderHashtag from 'soapbox/features/placeholder/components/placeholder_hashtag';
-import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder_status';
-
-import Hashtag from '../../../components/hashtag';
-import { Tabs } from '../../../components/ui';
-import AccountContainer from '../../../containers/account_container';
-import StatusContainer from '../../../containers/status_container';
-
-const messages = defineMessages({
- accounts: { id: 'search_results.accounts', defaultMessage: 'People' },
- statuses: { id: 'search_results.statuses', defaultMessage: 'Posts' },
- hashtags: { id: 'search_results.hashtags', defaultMessage: 'Hashtags' },
-});
-
-export default @injectIntl
-class SearchResults extends ImmutablePureComponent {
-
- static propTypes = {
- value: PropTypes.string,
- results: ImmutablePropTypes.map.isRequired,
- submitted: PropTypes.bool,
- expandSearch: PropTypes.func.isRequired,
- selectedFilter: PropTypes.string.isRequired,
- selectFilter: PropTypes.func.isRequired,
- features: PropTypes.object.isRequired,
- suggestions: ImmutablePropTypes.list,
- trendingStatuses: ImmutablePropTypes.list,
- trends: ImmutablePropTypes.list,
- intl: PropTypes.object.isRequired,
- };
-
- handleLoadMore = () => this.props.expandSearch(this.props.selectedFilter);
-
- handleSelectFilter = newActiveFilter => this.props.selectFilter(newActiveFilter);
-
- componentDidMount() {
- this.props.fetchTrendingStatuses();
- }
-
- renderFilterBar() {
- const { intl, selectedFilter } = this.props;
-
- const items = [
- {
- text: intl.formatMessage(messages.accounts),
- action: () => this.handleSelectFilter('accounts'),
- name: 'accounts',
- },
- {
- text: intl.formatMessage(messages.statuses),
- action: () => this.handleSelectFilter('statuses'),
- name: 'statuses',
- },
- {
- text: intl.formatMessage(messages.hashtags),
- action: () => this.handleSelectFilter('hashtags'),
- name: 'hashtags',
- },
- ];
-
- return ;
- }
-
- render() {
- const { value, results, submitted, selectedFilter, suggestions, trendingStatuses, trends } = this.props;
-
- let searchResults;
- let hasMore = false;
- let loaded;
- let noResultsMessage;
- let placeholderComponent = PlaceholderStatus;
-
- if (selectedFilter === 'accounts') {
- hasMore = results.get('accountsHasMore');
- loaded = results.get('accountsLoaded');
- placeholderComponent = PlaceholderAccount;
-
- if (results.get('accounts') && results.get('accounts').size > 0) {
- searchResults = results.get('accounts').map(accountId => );
- } else if (!submitted && suggestions && !suggestions.isEmpty()) {
- searchResults = suggestions.map(suggestion => );
- } else if (loaded) {
- noResultsMessage = (
-
-
-
- );
- }
- }
-
- if (selectedFilter === 'statuses') {
- hasMore = results.get('statusesHasMore');
- loaded = results.get('statusesLoaded');
-
- if (results.get('statuses') && results.get('statuses').size > 0) {
- searchResults = results.get('statuses').map(statusId => );
- } else if (!submitted && trendingStatuses && !trendingStatuses.isEmpty()) {
- searchResults = trendingStatuses.map(statusId => );
- } else if (loaded) {
- noResultsMessage = (
-
-
-
- );
- }
- }
-
- if (selectedFilter === 'hashtags') {
- hasMore = results.get('hashtagsHasMore');
- loaded = results.get('hashtagsLoaded');
- placeholderComponent = PlaceholderHashtag;
-
- if (results.get('hashtags') && results.get('hashtags').size > 0) {
- searchResults = results.get('hashtags').map(hashtag => );
- } else if (!submitted && suggestions && !suggestions.isEmpty()) {
- searchResults = trends.map(hashtag => );
- } else if (loaded) {
- noResultsMessage = (
-
-
-
- );
- }
- }
-
- return (
- <>
- {this.renderFilterBar()}
-
- {noResultsMessage || (
-
- {searchResults}
-
- )}
- >
- );
- }
-
-}
diff --git a/app/soapbox/features/compose/components/search_results.tsx b/app/soapbox/features/compose/components/search_results.tsx
new file mode 100644
index 000000000..1dc244920
--- /dev/null
+++ b/app/soapbox/features/compose/components/search_results.tsx
@@ -0,0 +1,187 @@
+import classNames from 'classnames';
+import React, { useEffect } from 'react';
+import { FormattedMessage } from 'react-intl';
+import { defineMessages, useIntl } from 'react-intl';
+
+import { fetchTrendingStatuses } from 'soapbox/actions/trending_statuses';
+import ScrollableList from 'soapbox/components/scrollable_list';
+import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder_account';
+import PlaceholderHashtag from 'soapbox/features/placeholder/components/placeholder_hashtag';
+import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder_status';
+import { useAppDispatch } from 'soapbox/hooks';
+
+import Hashtag from '../../../components/hashtag';
+import { Tabs } from '../../../components/ui';
+import AccountContainer from '../../../containers/account_container';
+import StatusContainer from '../../../containers/status_container';
+
+import type { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+
+const messages = defineMessages({
+ accounts: { id: 'search_results.accounts', defaultMessage: 'People' },
+ statuses: { id: 'search_results.statuses', defaultMessage: 'Posts' },
+ hashtags: { id: 'search_results.hashtags', defaultMessage: 'Hashtags' },
+});
+
+type SearchFilter = 'accounts' | 'statuses' | 'hashtags';
+
+interface ISearchResults {
+ value: string,
+ results: ImmutableMap,
+ submitted: boolean,
+ expandSearch: (filter: SearchFilter) => void,
+ selectedFilter: SearchFilter,
+ selectFilter: (filter: SearchFilter) => void,
+ suggestions: ImmutableList,
+ trendingStatuses: ImmutableList,
+ trends: ImmutableList,
+}
+
+/** Displays search results depending on the active tab. */
+const SearchResults: React.FC = ({
+ value,
+ results,
+ submitted,
+ expandSearch,
+ selectedFilter,
+ selectFilter,
+ suggestions,
+ trendingStatuses,
+ trends,
+}) => {
+ const intl = useIntl();
+ const dispatch = useAppDispatch();
+
+ const handleLoadMore = () => expandSearch(selectedFilter);
+ const handleSelectFilter = (newActiveFilter: SearchFilter) => selectFilter(newActiveFilter);
+
+ useEffect(() => {
+ dispatch(fetchTrendingStatuses());
+ }, []);
+
+ const renderFilterBar = () => {
+ const items = [
+ {
+ text: intl.formatMessage(messages.accounts),
+ action: () => handleSelectFilter('accounts'),
+ name: 'accounts',
+ },
+ {
+ text: intl.formatMessage(messages.statuses),
+ action: () => handleSelectFilter('statuses'),
+ name: 'statuses',
+ },
+ {
+ text: intl.formatMessage(messages.hashtags),
+ action: () => handleSelectFilter('hashtags'),
+ name: 'hashtags',
+ },
+ ];
+
+ return ;
+ };
+
+ let searchResults;
+ let hasMore = false;
+ let loaded;
+ let noResultsMessage;
+ let placeholderComponent: React.ComponentType = PlaceholderStatus;
+
+ if (selectedFilter === 'accounts') {
+ hasMore = results.get('accountsHasMore');
+ loaded = results.get('accountsLoaded');
+ placeholderComponent = PlaceholderAccount;
+
+ if (results.get('accounts') && results.get('accounts').size > 0) {
+ searchResults = results.get('accounts').map((accountId: string) => );
+ } else if (!submitted && suggestions && !suggestions.isEmpty()) {
+ searchResults = suggestions.map(suggestion => );
+ } else if (loaded) {
+ noResultsMessage = (
+
+
+
+ );
+ }
+ }
+
+ if (selectedFilter === 'statuses') {
+ hasMore = results.get('statusesHasMore');
+ loaded = results.get('statusesLoaded');
+
+ if (results.get('statuses') && results.get('statuses').size > 0) {
+ searchResults = results.get('statuses').map((statusId: string) => (
+ // @ts-ignore
+
+ ));
+ } else if (!submitted && trendingStatuses && !trendingStatuses.isEmpty()) {
+ searchResults = trendingStatuses.map(statusId => (
+ // @ts-ignore
+
+ ));
+ } else if (loaded) {
+ noResultsMessage = (
+
+
+
+ );
+ }
+ }
+
+ if (selectedFilter === 'hashtags') {
+ hasMore = results.get('hashtagsHasMore');
+ loaded = results.get('hashtagsLoaded');
+ placeholderComponent = PlaceholderHashtag;
+
+ if (results.get('hashtags') && results.get('hashtags').size > 0) {
+ searchResults = results.get('hashtags').map((hashtag: ImmutableMap) => );
+ } else if (!submitted && suggestions && !suggestions.isEmpty()) {
+ searchResults = trends.map(hashtag => );
+ } else if (loaded) {
+ noResultsMessage = (
+
+
+
+ );
+ }
+ }
+
+ return (
+ <>
+ {renderFilterBar()}
+
+ {noResultsMessage || (
+
+ {searchResults}
+
+ )}
+ >
+ );
+};
+
+export default SearchResults;