diff --git a/package-lock.json b/package-lock.json
index 5cc8fce9f7e1..e9fd2f3ddd05 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -272,7 +272,7 @@
         "ts-jest": "^29.1.2",
         "ts-node": "^10.9.2",
         "tsconfig-paths": "^4.2.0",
-        "type-fest": "4.20.0",
+        "type-fest": "4.34.1",
         "typescript": "^5.4.5",
         "wait-port": "^0.2.9",
         "webpack": "^5.94.0",
@@ -36147,7 +36147,9 @@
       }
     },
     "node_modules/type-fest": {
-      "version": "4.20.0",
+      "version": "4.34.1",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.34.1.tgz",
+      "integrity": "sha512-6kSc32kT0rbwxD6QL1CYe8IqdzN/J/ILMrNK+HMQCKH3insCDRY/3ITb0vcBss0a3t72fzh2YSzj8ko1HgwT3g==",
       "dev": true,
       "license": "(MIT OR CC0-1.0)",
       "engines": {
diff --git a/package.json b/package.json
index 8eec083cc0f4..9dacd650db17 100644
--- a/package.json
+++ b/package.json
@@ -339,7 +339,7 @@
     "ts-jest": "^29.1.2",
     "ts-node": "^10.9.2",
     "tsconfig-paths": "^4.2.0",
-    "type-fest": "4.20.0",
+    "type-fest": "4.34.1",
     "typescript": "^5.4.5",
     "wait-port": "^0.2.9",
     "webpack": "^5.94.0",
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index dce55b29b876..599406e5d80f 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -1,3 +1,4 @@
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
 import type {ValueOf} from 'type-fest';
 import type CONST from './CONST';
 import type {OnboardingCompanySize} from './CONST';
@@ -1088,14 +1089,33 @@ type OnyxCollectionKey = keyof OnyxCollectionValuesMapping;
 type OnyxFormKey = keyof OnyxFormValuesMapping;
 type OnyxFormDraftKey = keyof OnyxFormDraftValuesMapping;
 type OnyxValueKey = keyof OnyxValuesMapping;
-
 type OnyxKey = OnyxValueKey | OnyxCollectionKey | OnyxFormKey | OnyxFormDraftKey;
 type OnyxPagesKey = typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS_PAGES;
 
+type GetOnyxTypeForKey<K extends OnyxKey> =
+    // Forms (and draft forms) behave like value keys
+    K extends OnyxFormKey
+        ? OnyxEntry<OnyxFormValuesMapping[K]>
+        : K extends OnyxFormDraftKey
+        ? OnyxEntry<OnyxFormDraftValuesMapping[K]>
+        : // Plain non-collection values
+        K extends OnyxValueKey
+        ? OnyxEntry<OnyxValuesMapping[K]>
+        : // Exactly matching a collection key returns a collection
+        K extends OnyxCollectionKey
+        ? OnyxCollection<OnyxCollectionValuesMapping[K]>
+        : // Otherwise, if K is a string that starts with one of the collection keys,
+        // return an entry for that collection’s value type.
+        K extends string
+        ? {
+              [X in OnyxCollectionKey]: K extends `${X}${string}` ? OnyxEntry<OnyxCollectionValuesMapping[X]> : never;
+          }[OnyxCollectionKey]
+        : never;
+
 type MissingOnyxKeysError = `Error: Types don't match, OnyxKey type is missing: ${Exclude<AllOnyxKeys, OnyxKey>}`;
 /** If this type errors, it means that the `OnyxKey` type is missing some keys. */
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
 type AssertOnyxKeys = AssertTypesEqual<AllOnyxKeys, OnyxKey, MissingOnyxKeysError>;
 
 export default ONYXKEYS;
-export type {OnyxCollectionKey, OnyxCollectionValuesMapping, OnyxFormDraftKey, OnyxFormKey, OnyxFormValuesMapping, OnyxKey, OnyxPagesKey, OnyxValueKey, OnyxValues};
+export type {OnyxCollectionKey, OnyxCollectionValuesMapping, OnyxFormDraftKey, OnyxFormKey, OnyxFormValuesMapping, OnyxKey, OnyxPagesKey, OnyxValueKey, OnyxValues, GetOnyxTypeForKey};
diff --git a/src/hooks/useOnyxDerivedValue.ts b/src/hooks/useOnyxDerivedValue.ts
new file mode 100644
index 000000000000..17f534534161
--- /dev/null
+++ b/src/hooks/useOnyxDerivedValue.ts
@@ -0,0 +1,18 @@
+import {useSyncExternalStore} from 'react';
+import type {ValueOf} from 'type-fest';
+import type {ONYX_DERIVED_VALUES} from '@libs/OnyxDerived';
+import OnyxDerived from '@libs/OnyxDerived';
+
+/**
+ * Hook that subscribes to a derived Onyx value.
+ *
+ * Pass in one of the derived configs from ONYX_DERIVED_VALUES.
+ * This hook uses React's useSyncExternalStore to subscribe to the derived store,
+ * so that your component re-renders only when the derived value changes.
+ */
+function useDerivedOnyxValue(config: ValueOf<typeof ONYX_DERIVED_VALUES>): ReturnType<typeof config.compute> {
+    const store = OnyxDerived.getDerivedValueStore(config);
+    return useSyncExternalStore(store.subscribe, () => store.currentValue);
+}
+
+export default useDerivedOnyxValue;
diff --git a/src/libs/OnyxDerived.ts b/src/libs/OnyxDerived.ts
new file mode 100644
index 000000000000..e77ae9395bfe
--- /dev/null
+++ b/src/libs/OnyxDerived.ts
@@ -0,0 +1,199 @@
+import Onyx from 'react-native-onyx';
+import OnyxUtils from 'react-native-onyx/dist/OnyxUtils';
+import type {NonEmptyTuple, ValueOf} from 'type-fest';
+import CONST from '@src/CONST';
+import type {GetOnyxTypeForKey, OnyxKey} from '@src/ONYXKEYS';
+import ONYXKEYS from '@src/ONYXKEYS';
+import {isThread} from './ReportUtils';
+
+/**
+ * A derived value configuration describes:
+ *  - a unique key,
+ *  - a tuple of Onyx keys to subscribe to (dependencies),
+ *  - a compute function that derives a value from the dependent Onyx values.
+ *    The compute function receives a single argument that's a tuple of the onyx values for the declared dependencies.
+ *    For example, if your dependencies are `['report_', 'account'], then compute will receive a [OnyxCollection<Report>, OnyxEntry<Account>]
+ */
+type OnyxDerivedValueConfig<Key extends string, Deps extends NonEmptyTuple<OnyxKey>> = {
+    key: Key;
+    dependencies: Deps;
+    compute: (args: {
+        -readonly [Index in keyof Deps]: GetOnyxTypeForKey<Deps[Index]>;
+    }) => unknown;
+};
+
+/**
+ * Helper function to create a derived value config. This function is just here to help TypeScript infer Deps, so instead of writing this:
+ *
+ * const conciergeChatReportIDConfig: OnyxDerivedValueConfig<typeof ONYXKEYS.COLLECTION.REPORT, typeof ONYXKEYS.CONCIERGE_REPORT_ID> = {
+ *     dependencies: [ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.CONCIERGE_REPORT_ID],
+ *     ...
+ * };
+ *
+ * We can just write this:
+ *
+ * const conciergeChatReportIDConfig = createOnyxDerivedValueConfig({
+ *     dependencies: [ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.CONCIERGE_REPORT_ID]
+ * })
+ */
+function createOnyxDerivedValueConfig<Key extends string, Deps extends NonEmptyTuple<OnyxKey>>(config: OnyxDerivedValueConfig<Key, Deps>): OnyxDerivedValueConfig<Key, Deps> {
+    return config;
+}
+
+/**
+ * Global map of derived configs.
+ * This object holds our derived value configurations.
+ */
+const ONYX_DERIVED_VALUES = {
+    CONCIERGE_CHAT_REPORT_ID: createOnyxDerivedValueConfig({
+        key: 'conciergeChatReportID',
+        dependencies: [ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.CONCIERGE_REPORT_ID],
+        compute: ([reports, conciergeChatReportID]) => {
+            if (!reports) {
+                return null;
+            }
+
+            const conciergeReport = Object.values(reports).find((report) => {
+                if (!report?.participants || isThread(report)) {
+                    return false;
+                }
+
+                const participantAccountIDs = new Set(Object.keys(report.participants));
+                if (participantAccountIDs.size !== 2) {
+                    return false;
+                }
+
+                return participantAccountIDs.has(CONST.ACCOUNT_ID.CONCIERGE.toString()) || report?.reportID === conciergeChatReportID;
+            });
+
+            return conciergeReport?.reportID;
+        },
+    }),
+} as const;
+
+/**
+ * Interface for a derived value store.
+ *
+ * A derived value store holds the current derived value and a subscribe function.
+ * When any dependency updates, the store recalculates the derived value and notifies its subscribers.
+ */
+type DerivedValueStore<T> = {
+    currentValue: T;
+    subscribe: (cb: () => void) => () => void;
+};
+
+/**
+ * Get the union of all derived config keys from our ONYX_DERIVED_VALUES map.
+ */
+type DerivedValueStoreKey = ValueOf<typeof ONYX_DERIVED_VALUES>['key'];
+
+/**
+ * This type maps each config key (from ONYX_DERIVED_VALUES)
+ * to a DerivedValueStore whose currentValue type is the return type of that config's compute function.
+ */
+type DerivedValueStoreMap = {
+    [K in DerivedValueStoreKey]: DerivedValueStore<
+        ReturnType<
+            // Extract from the union of derived configs the one with key K, then get its compute function.
+            Extract<ValueOf<typeof ONYX_DERIVED_VALUES>, {key: K}>['compute']
+        >
+    >;
+};
+
+/**
+ * Global object holding our derived value stores.
+ * We use a Partial so that we can lazily create each store when needed.
+ */
+const derivedValueStores: Partial<DerivedValueStoreMap> = {};
+
+/**
+ * Creates a new derived value store for a given config.
+ *
+ * For each dependency key in the config, we create an array (currentOnyxValues)
+ * that will hold the latest Onyx values. Then we call Onyx.connect for each dependency,
+ * updating the corresponding entry in currentOnyxValues whenever the value changes.
+ * After each update, we recalculate the derived value using the config.compute function.
+ */
+function createDerivedValueStore(config: ValueOf<typeof ONYX_DERIVED_VALUES>): DerivedValueStore<ReturnType<typeof config.compute>> {
+    // Create an array to hold the current values for each dependency.
+    // We cast its type to match the tuple expected by config.compute.
+    const currentOnyxValues = new Array(config.dependencies.length) as Parameters<typeof config.compute>[0];
+    let derivedValue = config.compute(currentOnyxValues);
+
+    const subscribers = new Set<() => void>();
+
+    // Function to re-calculate the derived value and notify subscribers if it changes.
+    function recomputeValue() {
+        const newDerivedValue = config.compute(currentOnyxValues);
+        // If the derived value has changed, notify all subscribers.
+        if (newDerivedValue !== derivedValue) {
+            derivedValue = newDerivedValue;
+            for (const subscriber of subscribers) {
+                subscriber();
+            }
+        }
+    }
+
+    // Create Onyx subscriptions for each dependency.
+    const connections = config.dependencies.map((onyxKey, index) =>
+        OnyxUtils.isCollectionKey(onyxKey)
+            ? Onyx.connect({
+                  key: onyxKey,
+                  waitForCollectionCallback: true,
+                  callback: (value) => {
+                      currentOnyxValues[index] = value;
+                      recomputeValue();
+                  },
+              })
+            : Onyx.connect({
+                  key: onyxKey,
+                  callback: (value) => {
+                      currentOnyxValues[index] = value;
+                      recomputeValue();
+                  },
+              }),
+    );
+
+    return {
+        get currentValue() {
+            return derivedValue;
+        },
+        subscribe(cb: () => void) {
+            subscribers.add(cb);
+            return () => {
+                subscribers.delete(cb);
+                if (subscribers.size === 0) {
+                    for (const connection of connections) {
+                        Onyx.disconnect(connection);
+                    }
+                }
+            };
+        },
+    };
+}
+
+/**
+ * Retrieves (or creates if necessary) the derived value store for the given config.
+ *
+ * We use the config's literal key to index into our global derivedValueStores object.
+ * If a store for that key doesn't exist yet, we create it.
+ */
+function getDerivedValueStore(config: ValueOf<typeof ONYX_DERIVED_VALUES>) {
+    let store = derivedValueStores[config.key];
+    if (!store) {
+        store = createDerivedValueStore(config);
+        derivedValueStores[config.key] = store;
+    }
+    return store;
+}
+
+function get(config: ValueOf<typeof ONYX_DERIVED_VALUES>) {
+    const store = getDerivedValueStore(config);
+    return store.currentValue;
+}
+
+export {ONYX_DERIVED_VALUES};
+export default {
+    get,
+    getDerivedValueStore,
+};
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index f0defab02586..8701dbebe400 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -16,6 +16,7 @@ import {FallbackAvatar, IntacctSquare, NetSuiteSquare, NSQSSquare, QBOSquare, Xe
 import * as defaultGroupAvatars from '@components/Icon/GroupDefaultAvatars';
 import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars';
 import type {MoneyRequestAmountInputProps} from '@components/MoneyRequestAmountInput';
+import OnyxDerived, {ONYX_DERIVED_VALUES} from '@libs/OnyxDerived';
 import type {IOUAction, IOUType, OnboardingPurpose} from '@src/CONST';
 import CONST from '@src/CONST';
 import type {ParentNavigationSummaryParams} from '@src/languages/params';
@@ -728,12 +729,6 @@ let isAnonymousUser = false;
 // Example case: when we need to get a report name of a thread which is dependent on a report action message.
 const parsedReportActionMessageCache: Record<string, string> = {};
 
-let conciergeChatReportID: string | undefined;
-Onyx.connect({
-    key: ONYXKEYS.CONCIERGE_REPORT_ID,
-    callback: (value) => (conciergeChatReportID = value),
-});
-
 const defaultAvatarBuildingIconTestID = 'SvgDefaultAvatarBuilding Icon';
 Onyx.connect({
     key: ONYXKEYS.SESSION,
@@ -1519,21 +1514,11 @@ function getReportNotificationPreference(report: OnyxEntry<Report>): ValueOf<typ
     return participant?.notificationPreference ?? CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
 }
 
-const CONCIERGE_ACCOUNT_ID_STRING = CONST.ACCOUNT_ID.CONCIERGE.toString();
 /**
  * Only returns true if this is our main 1:1 DM report with Concierge.
  */
 function isConciergeChatReport(report: OnyxInputOrEntry<Report>): boolean {
-    if (!report?.participants || isThread(report)) {
-        return false;
-    }
-
-    const participantAccountIDs = new Set(Object.keys(report.participants));
-    if (participantAccountIDs.size !== 2) {
-        return false;
-    }
-
-    return participantAccountIDs.has(CONCIERGE_ACCOUNT_ID_STRING) || report?.reportID === conciergeChatReportID;
+    return !!report && report?.reportID === OnyxDerived.get(ONYX_DERIVED_VALUES.CONCIERGE_CHAT_REPORT_ID);
 }
 
 function findSelfDMReportID(): string | undefined {
