diff --git a/.eslintrc.js b/.eslintrc.js
index c888671ce..b4d5efd0e 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -4,6 +4,7 @@ module.exports = {
   extends: [
     'eslint:recommended',
     'plugin:react/recommended',
+    'plugin:react-hooks/recommended',
     'plugin:jsx-a11y/recommended',
     'plugin:import/recommended',
     'plugin:promise/recommended',
@@ -284,6 +285,7 @@ module.exports = {
         'eslint:recommended',
         'plugin:@typescript-eslint/recommended',
         'plugin:react/recommended',
+        'plugin:react-hooks/recommended',
         'plugin:jsx-a11y/recommended',
         'plugin:import/recommended',
         'plugin:import/typescript',
diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js
index 41cb99e2d..7831f261e 100644
--- a/app/javascript/mastodon/actions/streaming.js
+++ b/app/javascript/mastodon/actions/streaming.js
@@ -52,8 +52,10 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
     /**
      * @param {function(Function, Function): void} fallback
      */
+
     const useFallback = fallback => {
       fallback(dispatch, () => {
+        // eslint-disable-next-line react-hooks/rules-of-hooks -- this is not a react hook
         pollingId = setTimeout(() => useFallback(fallback), 20000 + randomUpTo(20000));
       });
     };
diff --git a/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js
index 5ec937a39..9d9a59c41 100644
--- a/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js
+++ b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js
@@ -72,6 +72,7 @@ const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({
   },
 
   onPickEmoji: emoji => {
+    // eslint-disable-next-line react-hooks/rules-of-hooks -- this is not a react hook
     dispatch(useEmoji(emoji));
 
     if (onPickEmoji) {
diff --git a/app/javascript/mastodon/features/compose/containers/language_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/language_dropdown_container.js
index 2a040a13f..5560fe609 100644
--- a/app/javascript/mastodon/features/compose/containers/language_dropdown_container.js
+++ b/app/javascript/mastodon/features/compose/containers/language_dropdown_container.js
@@ -26,6 +26,7 @@ const mapDispatchToProps = dispatch => ({
   },
 
   onClose (value) {
+    // eslint-disable-next-line react-hooks/rules-of-hooks -- this is not a react hook
     dispatch(useLanguage(value));
   },
 
diff --git a/package.json b/package.json
index f5d7dd09f..76deabc26 100644
--- a/package.json
+++ b/package.json
@@ -189,6 +189,7 @@
     "eslint-plugin-jsx-a11y": "~6.7.1",
     "eslint-plugin-promise": "~6.1.1",
     "eslint-plugin-react": "~7.32.2",
+    "eslint-plugin-react-hooks": "^4.6.0",
     "husky": "^8.0.3",
     "jest": "^29.5.0",
     "jest-environment-jsdom": "^29.5.0",
diff --git a/yarn.lock b/yarn.lock
index 00c6a6480..3114f26a3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5125,6 +5125,11 @@ eslint-plugin-promise@~6.1.1:
   resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz#269a3e2772f62875661220631bd4dafcb4083816"
   integrity sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==
 
+eslint-plugin-react-hooks@^4.6.0:
+  version "4.6.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3"
+  integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==
+
 eslint-plugin-react@~7.32.2:
   version "7.32.2"
   resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz#e71f21c7c265ebce01bcbc9d0955170c55571f10"