diff --git a/app/soapbox/components/helmet.js b/app/soapbox/components/helmet.js
deleted file mode 100644
index 9e86883b4..000000000
--- a/app/soapbox/components/helmet.js
+++ /dev/null
@@ -1,87 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Helmet } from'react-helmet';
-import { connect } from 'react-redux';
-import { withRouter } from 'react-router-dom';
-
-import { getSettings } from 'soapbox/actions/settings';
-// import sourceCode from 'soapbox/utils/code';
-import FaviconService from 'soapbox/utils/favicon_service';
-
-FaviconService.initFaviconService();
-
-const getNotifTotals = state => {
- const notifications = state.getIn(['notifications', 'unread'], 0);
- const chats = state.getIn(['chats', 'items']).reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0);
- const reports = state.getIn(['admin', 'openReports']).count();
- const approvals = state.getIn(['admin', 'awaitingApproval']).count();
- return notifications + chats + reports + approvals;
-};
-
-const mapStateToProps = state => {
- const settings = getSettings(state);
-
- return {
- siteTitle: state.getIn(['instance', 'title']),
- unreadCount: getNotifTotals(state),
- demetricator: settings.get('demetricator'),
- };
-};
-
-class SoapboxHelmet extends React.Component {
-
- static propTypes = {
- siteTitle: PropTypes.string,
- children: PropTypes.node,
- unreadCount: PropTypes.number,
- demetricator: PropTypes.bool,
- };
-
- hasUnread = () => {
- const { unreadCount, demetricator } = this.props;
- return !(unreadCount < 1 || demetricator);
- }
-
- addCounter = title => {
- const { unreadCount } = this.props;
- const hasUnread = this.hasUnread();
- return hasUnread ? `(${unreadCount}) ${title}` : title;
- }
-
- updateFaviconBadge = () => {
- const hasUnread = this.hasUnread();
-
- if (hasUnread) {
- FaviconService.drawFaviconBadge();
- } else {
- FaviconService.clearFaviconBadge();
- }
- }
-
- componentDidUpdate(prevProps) {
- if (this.props.unreadCount !== prevProps.unreadCount || this.props.demetricator !== prevProps.demetricator) {
- this.updateFaviconBadge();
- }
- }
-
- componentDidMount() {
- this.updateFaviconBadge();
- }
-
- render() {
- const { siteTitle, children } = this.props;
-
- return (
-
- {children}
-
- );
- }
-
-}
-
-export default withRouter(connect(mapStateToProps)(SoapboxHelmet));
diff --git a/app/soapbox/components/helmet.tsx b/app/soapbox/components/helmet.tsx
new file mode 100644
index 000000000..7eee6538c
--- /dev/null
+++ b/app/soapbox/components/helmet.tsx
@@ -0,0 +1,53 @@
+import * as React from 'react';
+import { Helmet as ReactHelmet } from'react-helmet';
+
+import { useAppSelector, useSettings } from 'soapbox/hooks';
+import FaviconService from 'soapbox/utils/favicon_service';
+
+FaviconService.initFaviconService();
+
+const getNotifTotals = (state: any) => {
+ const notifications = state.getIn(['notifications', 'unread'], 0);
+ const chats = state.getIn(['chats', 'items']).reduce((acc: any, curr: any) => acc + Math.min(curr.get('unread', 0), 1), 0);
+ const reports = state.getIn(['admin', 'openReports']).count();
+ const approvals = state.getIn(['admin', 'awaitingApproval']).count();
+ return notifications + chats + reports + approvals;
+};
+
+const Helmet: React.FC = ({ children }) => {
+ const settings = useSettings();
+
+ const title = useAppSelector((state) => state.instance.get('title'));
+ const unreadCount = useAppSelector((state) => getNotifTotals(state));
+ const demetricator = useAppSelector((state) => settings.get('demetricator'));
+
+ const hasUnreadNotifications = React.useMemo(() => !(unreadCount < 1 || demetricator), [unreadCount, demetricator]);
+
+ const addCounter = (string: string) => {
+ return hasUnreadNotifications ? `(${unreadCount}) ${title}` : title;
+ };
+
+ const updateFaviconBadge = () => {
+ if (hasUnreadNotifications) {
+ FaviconService.drawFaviconBadge();
+ } else {
+ FaviconService.clearFaviconBadge();
+ }
+ };
+
+ React.useEffect(() => {
+ updateFaviconBadge();
+ }, [unreadCount, demetricator]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default Helmet;
diff --git a/app/soapbox/containers/soapbox.js b/app/soapbox/containers/soapbox.js
index f9ddf8bb1..c1a7db84e 100644
--- a/app/soapbox/containers/soapbox.js
+++ b/app/soapbox/containers/soapbox.js
@@ -166,15 +166,16 @@ class SoapboxMount extends React.PureComponent {
return (
+
+
+
+ {themeCss && }
+
+
+
<>
-
-
-
- {themeCss && }
-
-
diff --git a/package.json b/package.json
index f4baa47fc..4ec9a6372 100644
--- a/package.json
+++ b/package.json
@@ -70,6 +70,7 @@
"@types/escape-html": "^1.0.1",
"@types/http-link-header": "^1.0.3",
"@types/lodash": "^4.14.180",
+ "@types/react-helmet": "^6.1.5",
"@types/react-router-dom": "^5.3.3",
"@types/uuid": "^8.3.4",
"array-includes": "^3.0.3",
diff --git a/yarn.lock b/yarn.lock
index 8cb374efb..26d2650a6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2095,6 +2095,13 @@
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df"
integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==
+"@types/react-helmet@^6.1.5":
+ version "6.1.5"
+ resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.5.tgz#35f89a6b1646ee2bc342a33a9a6c8777933f9083"
+ integrity sha512-/ICuy7OHZxR0YCAZLNg9r7I9aijWUWvxaPR6uTuyxe8tAj5RL4Sw1+R6NhXUtOsarkGYPmaHdBDvuXh2DIN/uA==
+ dependencies:
+ "@types/react" "*"
+
"@types/react-redux@^7.1.16":
version "7.1.18"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.18.tgz#2bf8fd56ebaae679a90ebffe48ff73717c438e04"