diff --git a/lib/components/qr-auth-provider.react.js b/lib/components/qr-auth-provider.react.js
--- a/lib/components/qr-auth-provider.react.js
+++ b/lib/components/qr-auth-provider.react.js
@@ -4,12 +4,23 @@
 import * as React from 'react';
 
 import { useSecondaryDeviceLogIn } from '../hooks/login-hooks.js';
-import { useQRAuth } from '../hooks/qr-auth.js';
 import { uintArrayToHexString } from '../media/data-utils.js';
+import { IdentityClientContext } from '../shared/identity-client-context.js';
 import { useTunnelbroker } from '../tunnelbroker/tunnelbroker-context.js';
 import type { BackupKeys } from '../types/backup-types.js';
-import type { QRCodeAuthMessage } from '../types/tunnelbroker/peer-to-peer-message-types.js';
-import type { QRCodeAuthMessagePayload } from '../types/tunnelbroker/qr-code-auth-message-types.js';
+import {
+  tunnelbrokerMessageTypes,
+  type TunnelbrokerMessage,
+} from '../types/tunnelbroker/messages.js';
+import {
+  type QRCodeAuthMessage,
+  peerToPeerMessageTypes,
+  peerToPeerMessageValidator,
+} from '../types/tunnelbroker/peer-to-peer-message-types.js';
+import {
+  qrCodeAuthMessageTypes,
+  type QRCodeAuthMessagePayload,
+} from '../types/tunnelbroker/qr-code-auth-message-types.js';
 import { getContentSigningKey } from '../utils/crypto-utils.js';
 
 type Props = {
@@ -50,9 +61,19 @@
     performBackupRestore,
   } = props;
 
+  const [primaryDeviceID, setPrimaryDeviceID] = React.useState<?string>();
   const [qrData, setQRData] = React.useState<?QRData>();
 
-  const { setUnauthorizedDeviceID } = useTunnelbroker();
+  const {
+    setUnauthorizedDeviceID,
+    addListener,
+    removeListener,
+    socketState,
+    sendMessage,
+  } = useTunnelbroker();
+
+  const identityContext = React.useContext(IdentityClientContext);
+  const identityClient = identityContext?.identityClient;
 
   const generateQRCode = React.useCallback(async () => {
     try {
@@ -82,24 +103,116 @@
     [logInSecondaryDevice, onLoginError, generateQRCode],
   );
 
-  const qrAuthInput = React.useMemo(
-    () => ({
-      secondaryDeviceID: qrData?.deviceID,
-      aesKey: qrData?.aesKey,
-      performSecondaryDeviceRegistration: performLogIn,
-      composeMessage: composeTunnelbrokerMessage,
-      processMessage: processTunnelbrokerMessage,
-      performBackupRestore,
-    }),
+  React.useEffect(() => {
+    if (!qrData || !socketState.isAuthorized || !primaryDeviceID) {
+      return;
+    }
+
+    void (async () => {
+      const message = await composeTunnelbrokerMessage(qrData?.aesKey, {
+        type: qrCodeAuthMessageTypes.SECONDARY_DEVICE_REGISTRATION_SUCCESS,
+        requestBackupKeys: true,
+      });
+      await sendMessage({
+        deviceID: primaryDeviceID,
+        payload: JSON.stringify(message),
+      });
+    })();
+  }, [
+    sendMessage,
+    primaryDeviceID,
+    qrData,
+    socketState,
+    composeTunnelbrokerMessage,
+  ]);
+
+  const tunnelbrokerMessageListener = React.useCallback(
+    async (message: TunnelbrokerMessage) => {
+      invariant(identityClient, 'identity context not set');
+      if (
+        !qrData?.aesKey ||
+        message.type !== tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE
+      ) {
+        return;
+      }
+
+      let innerMessage;
+      try {
+        innerMessage = JSON.parse(message.payload);
+      } catch {
+        return;
+      }
+      if (
+        !peerToPeerMessageValidator.is(innerMessage) ||
+        innerMessage.type !== peerToPeerMessageTypes.QR_CODE_AUTH_MESSAGE
+      ) {
+        return;
+      }
+
+      let qrCodeAuthMessage;
+      try {
+        qrCodeAuthMessage = await processTunnelbrokerMessage(
+          qrData?.aesKey,
+          innerMessage,
+        );
+      } catch (err) {
+        console.warn('Failed to decrypt Tunnelbroker QR auth message:', err);
+        return;
+      }
+
+      if (
+        qrCodeAuthMessage &&
+        qrCodeAuthMessage.type ===
+          qrCodeAuthMessageTypes.BACKUP_DATA_KEY_MESSAGE
+      ) {
+        const { backupID, backupDataKey, backupLogDataKey } = qrCodeAuthMessage;
+        void performBackupRestore?.({
+          backupID,
+          backupDataKey,
+          backupLogDataKey,
+        });
+        return;
+      }
+
+      if (
+        !qrCodeAuthMessage ||
+        qrCodeAuthMessage.type !==
+          qrCodeAuthMessageTypes.DEVICE_LIST_UPDATE_SUCCESS
+      ) {
+        return;
+      }
+      const { primaryDeviceID: receivedPrimaryDeviceID, userID } =
+        qrCodeAuthMessage;
+      setPrimaryDeviceID(receivedPrimaryDeviceID);
+
+      await performLogIn(userID);
+      setUnauthorizedDeviceID(null);
+    },
     [
-      qrData,
+      identityClient,
+      qrData?.aesKey,
       performLogIn,
-      composeTunnelbrokerMessage,
+      setUnauthorizedDeviceID,
       processTunnelbrokerMessage,
       performBackupRestore,
     ],
   );
-  useQRAuth(qrAuthInput);
+
+  React.useEffect(() => {
+    if (!qrData?.deviceID) {
+      return undefined;
+    }
+
+    addListener(tunnelbrokerMessageListener);
+    return () => {
+      removeListener(tunnelbrokerMessageListener);
+    };
+  }, [
+    addListener,
+    removeListener,
+    tunnelbrokerMessageListener,
+    qrData?.deviceID,
+  ]);
 
   const value = React.useMemo(
     () => ({
diff --git a/lib/hooks/qr-auth.js b/lib/hooks/qr-auth.js
deleted file mode 100644
--- a/lib/hooks/qr-auth.js
+++ /dev/null
@@ -1,174 +0,0 @@
-// @flow
-
-import invariant from 'invariant';
-import * as React from 'react';
-
-import { IdentityClientContext } from '../shared/identity-client-context.js';
-import { useTunnelbroker } from '../tunnelbroker/tunnelbroker-context.js';
-import type { BackupKeys } from '../types/backup-types.js';
-import {
-  tunnelbrokerMessageTypes,
-  type TunnelbrokerMessage,
-} from '../types/tunnelbroker/messages.js';
-import {
-  peerToPeerMessageTypes,
-  peerToPeerMessageValidator,
-  type QRCodeAuthMessage,
-} from '../types/tunnelbroker/peer-to-peer-message-types.js';
-import {
-  qrCodeAuthMessageTypes,
-  type QRCodeAuthMessagePayload,
-} from '../types/tunnelbroker/qr-code-auth-message-types.js';
-
-type QRAuthHandlerInput = {
-  +secondaryDeviceID: ?string,
-  +aesKey: ?string,
-  +performSecondaryDeviceRegistration: (userID: string) => Promise<void>,
-  +composeMessage: (
-    encryptionKey: string,
-    payload: QRCodeAuthMessagePayload,
-  ) => Promise<QRCodeAuthMessage>,
-  +processMessage: (
-    encryptionKey: string,
-    message: QRCodeAuthMessage,
-  ) => Promise<?QRCodeAuthMessagePayload>,
-  +performBackupRestore?: (backupKeys: BackupKeys) => Promise<void>,
-};
-
-function useQRAuth(input: QRAuthHandlerInput) {
-  const {
-    secondaryDeviceID,
-    aesKey,
-    processMessage,
-    composeMessage,
-    performSecondaryDeviceRegistration,
-    performBackupRestore,
-  } = input;
-  const [primaryDeviceID, setPrimaryDeviceID] = React.useState<?string>();
-  const {
-    setUnauthorizedDeviceID,
-    addListener,
-    removeListener,
-    socketState,
-    sendMessage,
-  } = useTunnelbroker();
-
-  const identityContext = React.useContext(IdentityClientContext);
-  const identityClient = identityContext?.identityClient;
-
-  React.useEffect(() => {
-    if (
-      !secondaryDeviceID ||
-      !aesKey ||
-      !socketState.isAuthorized ||
-      !primaryDeviceID
-    ) {
-      return;
-    }
-
-    void (async () => {
-      const message = await composeMessage(aesKey, {
-        type: qrCodeAuthMessageTypes.SECONDARY_DEVICE_REGISTRATION_SUCCESS,
-        requestBackupKeys: true,
-      });
-      await sendMessage({
-        deviceID: primaryDeviceID,
-        payload: JSON.stringify(message),
-      });
-    })();
-  }, [
-    sendMessage,
-    primaryDeviceID,
-    aesKey,
-    secondaryDeviceID,
-    composeMessage,
-    socketState,
-  ]);
-
-  const tunnelbrokerMessageListener = React.useCallback(
-    async (message: TunnelbrokerMessage) => {
-      invariant(identityClient, 'identity context not set');
-      if (
-        !aesKey ||
-        message.type !== tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE
-      ) {
-        return;
-      }
-
-      let innerMessage;
-      try {
-        innerMessage = JSON.parse(message.payload);
-      } catch {
-        return;
-      }
-      if (
-        !peerToPeerMessageValidator.is(innerMessage) ||
-        innerMessage.type !== peerToPeerMessageTypes.QR_CODE_AUTH_MESSAGE
-      ) {
-        return;
-      }
-
-      let qrCodeAuthMessage;
-      try {
-        qrCodeAuthMessage = await processMessage(aesKey, innerMessage);
-      } catch (err) {
-        console.warn('Failed to decrypt Tunnelbroker QR auth message:', err);
-        return;
-      }
-
-      if (
-        qrCodeAuthMessage &&
-        qrCodeAuthMessage.type ===
-          qrCodeAuthMessageTypes.BACKUP_DATA_KEY_MESSAGE
-      ) {
-        const { backupID, backupDataKey, backupLogDataKey } = qrCodeAuthMessage;
-        void performBackupRestore?.({
-          backupID,
-          backupDataKey,
-          backupLogDataKey,
-        });
-        return;
-      }
-
-      if (
-        !qrCodeAuthMessage ||
-        qrCodeAuthMessage.type !==
-          qrCodeAuthMessageTypes.DEVICE_LIST_UPDATE_SUCCESS
-      ) {
-        return;
-      }
-      const { primaryDeviceID: receivedPrimaryDeviceID, userID } =
-        qrCodeAuthMessage;
-      setPrimaryDeviceID(receivedPrimaryDeviceID);
-
-      await performSecondaryDeviceRegistration(userID);
-      setUnauthorizedDeviceID(null);
-    },
-    [
-      setUnauthorizedDeviceID,
-      identityClient,
-      aesKey,
-      performSecondaryDeviceRegistration,
-      performBackupRestore,
-      processMessage,
-    ],
-  );
-
-  React.useEffect(() => {
-    if (!secondaryDeviceID) {
-      return undefined;
-    }
-
-    addListener(tunnelbrokerMessageListener);
-    return () => {
-      removeListener(tunnelbrokerMessageListener);
-    };
-  }, [
-    secondaryDeviceID,
-    addListener,
-    removeListener,
-    tunnelbrokerMessageListener,
-  ]);
-}
-
-export { useQRAuth };