diff --git a/lib/selectors/socket-selectors.js b/lib/selectors/socket-selectors.js
--- a/lib/selectors/socket-selectors.js
+++ b/lib/selectors/socket-selectors.js
@@ -7,13 +7,11 @@
   currentAsOfSelector,
 } from './keyserver-selectors.js';
 import { currentCalendarQuery } from './nav-selectors.js';
+import type { BoundStateSyncSpec } from '../shared/state-sync/state-sync-spec.js';
 import { stateSyncSpecs } from '../shared/state-sync/state-sync-specs.js';
 import threadWatcher from '../shared/thread-watcher.js';
 import type { SignedIdentityKeysBlob } from '../types/crypto-types.js';
-import {
-  type RawEntryInfos,
-  type CalendarQuery,
-} from '../types/entry-types.js';
+import { type CalendarQuery } from '../types/entry-types.js';
 import type { AppState } from '../types/redux-types.js';
 import type { ClientReportCreationRequest } from '../types/report-types.js';
 import {
@@ -23,11 +21,9 @@
 } from '../types/request-types.js';
 import type { SessionState } from '../types/session-types.js';
 import type { OneTimeKeyGenerator } from '../types/socket-types.js';
-import { type RawThreadInfos } from '../types/thread-types.js';
-import { type CurrentUserInfo, type UserInfos } from '../types/user-types.js';
 import { getConfig } from '../utils/config.js';
 import { minimumOneTimeKeysRequired } from '../utils/crypto-utils.js';
-import { hash, values } from '../utils/objects.js';
+import { values } from '../utils/objects.js';
 
 const queuedReports: (
   state: AppState,
@@ -38,6 +34,36 @@
   ): $ReadOnlyArray<ClientReportCreationRequest> => mainQueuedReports,
 );
 
+// We pass all selectors specified in stateSyncSpecs and get the resulting
+// BoundStateSyncSpecs in the specs array. We do it so we don't have to
+// modify the selector when we add a new spec.
+const stateSyncSpecSelectors = values(stateSyncSpecs).map(
+  spec => spec.selector,
+);
+const boundStateSyncSpecsSelector: AppState => {
+  specsPerHashKey: { +[string]: BoundStateSyncSpec<mixed, mixed, mixed> },
+  specPerInnerHashKey: { +[string]: BoundStateSyncSpec<mixed, mixed, mixed> },
+} =
+  // The FlowFixMe is needed because createSelector types require flow
+  // to know the number of subselectors at compile time.
+  // $FlowFixMe
+  createSelector(stateSyncSpecSelectors, (...specs) => {
+    const boundSpecs = (specs: BoundStateSyncSpec<mixed, mixed, mixed>[]);
+    // We create a map from `hashKey` to a given spec for easier lookup later
+    const specsPerHashKey = Object.fromEntries(
+      boundSpecs.map(spec => [spec.hashKey, spec]),
+    );
+
+    // We do the same for innerHashKey
+    const specPerInnerHashKey = Object.fromEntries(
+      boundSpecs
+        .filter(spec => spec.innerHashSpec?.hashKey)
+        .map(spec => [spec.innerHashSpec?.hashKey, spec]),
+    );
+
+    return { specsPerHashKey, specPerInnerHashKey };
+  });
+
 const getClientResponsesSelector: (
   state: AppState,
 ) => (
@@ -47,16 +73,10 @@
   getInitialNotificationsEncryptedMessage: ?() => Promise<string>,
   serverRequests: $ReadOnlyArray<ClientServerRequest>,
 ) => Promise<$ReadOnlyArray<ClientClientResponse>> = createSelector(
-  (state: AppState) => state.threadStore.threadInfos,
-  (state: AppState) => state.entryStore.entryInfos,
-  (state: AppState) => state.userStore.userInfos,
-  (state: AppState) => state.currentUserInfo,
+  boundStateSyncSpecsSelector,
   currentCalendarQuery,
   (
-    threadInfos: RawThreadInfos,
-    entryInfos: RawEntryInfos,
-    userInfos: UserInfos,
-    currentUserInfo: ?CurrentUserInfo,
+    { specsPerHashKey, specPerInnerHashKey },
     calendarQuery: (calendarActive: boolean) => CalendarQuery,
   ) => {
     return async (
@@ -87,47 +107,26 @@
         } else if (serverRequest.type === serverRequestTypes.CHECK_STATE) {
           const query = calendarQuery(calendarActive);
 
-          const convertedInfos = {
-            [stateSyncSpecs.entries.hashKey]:
-              stateSyncSpecs.entries.convertClientToServerInfos(
-                entryInfos,
-                query,
-              ),
-            [stateSyncSpecs.threads.hashKey]:
-              stateSyncSpecs.threads.convertClientToServerInfos(
-                threadInfos,
-                query,
-              ),
-            [stateSyncSpecs.users.hashKey]:
-              stateSyncSpecs.users.convertClientToServerInfos(userInfos, query),
-            [stateSyncSpecs.currentUser.hashKey]: currentUserInfo
-              ? stateSyncSpecs.currentUser.convertClientToServerInfos(
-                  currentUserInfo,
-                  query,
-                )
-              : currentUserInfo,
-          };
-
-          const specPerInnerHashKey = Object.fromEntries(
-            values(stateSyncSpecs)
-              .filter(spec => spec.innerHashSpec?.hashKey)
-              .map(spec => [spec.innerHashSpec?.hashKey, spec]),
-          );
           const hashResults = {};
           for (const key in serverRequest.hashesToCheck) {
             const expectedHashValue = serverRequest.hashesToCheck[key];
             let hashValue;
-            if (convertedInfos[key]) {
-              hashValue = hash(convertedInfos[key]);
+            const [specKey, id] = key.split('|');
+            if (id) {
+              hashValue = specPerInnerHashKey[specKey]?.getInfoHash(id);
             } else {
-              const [keyPrefix, id] = key.split('|');
-              const innerSpec = specPerInnerHashKey[keyPrefix];
-              if (!innerSpec || !id) {
-                continue;
-              }
-              hashValue = hash(convertedInfos[innerSpec.hashKey][id]);
+              hashValue = specsPerHashKey[specKey]?.getAllInfosHash(query);
+            }
+
+            // If hashValue values is null then we are still calculating
+            // the hashes in the background. In this case we return true
+            // to skip this state check. Future state checks (after the hash
+            // calculation complete) will be handled normally.
+            if (!hashValue) {
+              hashResults[key] = true;
+            } else {
+              hashResults[key] = expectedHashValue === hashValue;
             }
-            hashResults[key] = expectedHashValue === hashValue;
           }
 
           const { failUnmentioned } = serverRequest;
@@ -136,7 +135,13 @@
             if (!failUnmentioned?.[spec.hashKey] || !innerHashKey) {
               continue;
             }
-            for (const id in convertedInfos[spec.hashKey]) {
+
+            const ids = spec.getIDs(query);
+            if (!ids) {
+              continue;
+            }
+
+            for (const id of ids) {
               const key = `${innerHashKey}|${id}`;
               const hashResult = hashResults[key];
               if (hashResult === null || hashResult === undefined) {