diff --git a/app/soapbox/features/home_timeline/index.js b/app/soapbox/features/home_timeline/index.js
deleted file mode 100644
index f93887713..000000000
--- a/app/soapbox/features/home_timeline/index.js
+++ /dev/null
@@ -1,112 +0,0 @@
-import { OrderedSet as ImmutableOrderedSet } from 'immutable';
-import PropTypes from 'prop-types';
-import React from 'react';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { connect } from 'react-redux';
-import { Link } from 'react-router-dom';
-
-import { getFeatures } from 'soapbox/utils/features';
-
-import { expandHomeTimeline } from '../../actions/timelines';
-import { Column } from '../../components/ui';
-import Timeline from '../ui/components/timeline';
-
-const messages = defineMessages({
- title: { id: 'column.home', defaultMessage: 'Home' },
-});
-
-const mapStateToProps = state => {
- const instance = state.get('instance');
- const features = getFeatures(instance);
-
- return {
- hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
- isPartial: state.getIn(['timelines', 'home', 'isPartial']),
- siteTitle: state.getIn(['instance', 'title']),
- isLoading: state.getIn(['timelines', 'home', 'isLoading'], true),
- loadingFailed: state.getIn(['timelines', 'home', 'loadingFailed'], false),
- isEmpty: state.getIn(['timelines', 'home', 'items'], ImmutableOrderedSet()).isEmpty(),
- features,
- };
-};
-
-export default @connect(mapStateToProps)
-@injectIntl
-class HomeTimeline extends React.PureComponent {
-
- static propTypes = {
- dispatch: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- hasUnread: PropTypes.bool,
- isPartial: PropTypes.bool,
- siteTitle: PropTypes.string,
- isLoading: PropTypes.bool,
- loadingFailed: PropTypes.bool,
- isEmpty: PropTypes.bool,
- features: PropTypes.object.isRequired,
- };
-
- state = {
- done: false,
- }
-
- handleLoadMore = maxId => {
- this.props.dispatch(expandHomeTimeline({ maxId }));
- }
-
- componentDidMount() {
- this._checkIfReloadNeeded(false, this.props.isPartial);
- }
-
- componentDidUpdate(prevProps) {
- this._checkIfReloadNeeded(prevProps.isPartial, this.props.isPartial);
- }
-
- componentWillUnmount() {
- this._stopPolling();
- }
-
- _checkIfReloadNeeded(wasPartial, isPartial) {
- const { dispatch } = this.props;
-
- if (wasPartial === isPartial) {
- return;
- } else if (!wasPartial && isPartial) {
- this.polling = setInterval(() => {
- dispatch(expandHomeTimeline());
- }, 3000);
- } else if (wasPartial && !isPartial) {
- this._stopPolling();
- }
- }
-
- _stopPolling() {
- if (this.polling) {
- clearInterval(this.polling);
- this.polling = null;
- }
- }
-
- handleRefresh = () => {
- const { dispatch } = this.props;
- return dispatch(expandHomeTimeline());
- }
-
- render() {
- const { intl, siteTitle } = this.props;
-
- return (
-
- }} />}
- />
-
- );
- }
-
-}
diff --git a/app/soapbox/features/home_timeline/index.tsx b/app/soapbox/features/home_timeline/index.tsx
new file mode 100644
index 000000000..79cca2b9d
--- /dev/null
+++ b/app/soapbox/features/home_timeline/index.tsx
@@ -0,0 +1,71 @@
+import React, { useEffect, useRef } from 'react';
+import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
+import { Link } from 'react-router-dom';
+
+import { expandHomeTimeline } from 'soapbox/actions/timelines';
+import { Column } from 'soapbox/components/ui';
+import Timeline from 'soapbox/features/ui/components/timeline';
+import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
+
+const messages = defineMessages({
+ title: { id: 'column.home', defaultMessage: 'Home' },
+});
+
+const HomeTimeline: React.FC = () => {
+ const intl = useIntl();
+ const dispatch = useAppDispatch();
+ const polling = useRef(null);
+
+ const isPartial = useAppSelector(state => state.timelines.getIn(['home', 'isPartial']) === true);
+ const siteTitle = useAppSelector(state => state.instance.title);
+
+ const handleLoadMore = (maxId: string) => {
+ dispatch(expandHomeTimeline({ maxId }));
+ };
+
+ useEffect(() => {
+ checkIfReloadNeeded();
+
+ return () => {
+ stopPolling();
+ };
+ }, [isPartial]);
+
+ // Mastodon generates the feed in Redis, and can return a partial timeline
+ // (HTTP 206) for new users. Poll until we get a full page of results.
+ const checkIfReloadNeeded = () => {
+ if (isPartial) {
+ polling.current = setInterval(() => {
+ dispatch(expandHomeTimeline());
+ }, 3000);
+ } else {
+ stopPolling();
+ }
+ };
+
+ const stopPolling = () => {
+ if (polling.current) {
+ clearInterval(polling.current);
+ polling.current = null;
+ }
+ };
+
+ const handleRefresh = () => {
+ return dispatch(expandHomeTimeline());
+ };
+
+ return (
+
+ }} />}
+ />
+
+ );
+};
+
+export default HomeTimeline;