|
1 | | -import {deepEqual, shallowEqual} from 'fast-equals'; |
| 1 | +import {shallowEqual} from 'fast-equals'; |
2 | 2 | import type {ValueOf} from 'type-fest'; |
3 | 3 | import _ from 'underscore'; |
4 | 4 | import DevTools from './DevTools'; |
@@ -506,8 +506,8 @@ function getCachedCollection<TKey extends CollectionKeyBase>(collectionKey: TKey |
506 | 506 | return filteredCollection; |
507 | 507 | } |
508 | 508 |
|
509 | | - // Return a copy to avoid mutations affecting the cache |
510 | | - return {...collectionData}; |
| 509 | + // Snapshot is frozen — safe to return by reference |
| 510 | + return collectionData; |
511 | 511 | } |
512 | 512 |
|
513 | 513 | // Fallback to original implementation if collection data not available |
@@ -542,78 +542,50 @@ function keysChanged<TKey extends CollectionKeyBase>( |
542 | 542 | partialCollection: OnyxCollection<KeyValueMapping[TKey]>, |
543 | 543 | partialPreviousCollection: OnyxCollection<KeyValueMapping[TKey]> | undefined, |
544 | 544 | ): void { |
545 | | - // We prepare the "cached collection" which is the entire collection + the new partial data that |
546 | | - // was merged in via mergeCollection(). |
547 | 545 | const cachedCollection = getCachedCollection(collectionKey); |
548 | | - |
549 | 546 | const previousCollection = partialPreviousCollection ?? {}; |
| 547 | + const changedMemberKeys = Object.keys(partialCollection ?? {}); |
| 548 | + |
| 549 | + // Use indexed lookup instead of scanning all subscribers |
| 550 | + const collectionSubscriberIDs = onyxKeyToSubscriptionIDs.get(collectionKey) ?? []; |
| 551 | + const memberSubscriberIDs: number[] = []; |
| 552 | + for (const memberKey of changedMemberKeys) { |
| 553 | + const ids = onyxKeyToSubscriptionIDs.get(memberKey); |
| 554 | + if (ids) { |
| 555 | + for (const id of ids) { |
| 556 | + memberSubscriberIDs.push(id); |
| 557 | + } |
| 558 | + } |
| 559 | + } |
550 | 560 |
|
551 | | - // We are iterating over all subscribers similar to keyChanged(). However, we are looking for subscribers who are subscribing to either a collection key or |
552 | | - // individual collection key member for the collection that is being updated. It is important to note that the collection parameter cane be a PARTIAL collection |
553 | | - // and does not represent all of the combined keys and values for a collection key. It is just the "new" data that was merged in via mergeCollection(). |
554 | | - const stateMappingKeys = Object.keys(callbackToStateMapping); |
| 561 | + // Notify collection-level subscribers |
| 562 | + for (const subID of collectionSubscriberIDs) { |
| 563 | + const subscriber = callbackToStateMapping[subID]; |
| 564 | + if (!subscriber || typeof subscriber.callback !== 'function') continue; |
555 | 565 |
|
556 | | - for (const stateMappingKey of stateMappingKeys) { |
557 | | - const subscriber = callbackToStateMapping[stateMappingKey]; |
558 | | - if (!subscriber) { |
559 | | - continue; |
560 | | - } |
| 566 | + lastConnectionCallbackData.set(subscriber.subscriptionID, cachedCollection); |
561 | 567 |
|
562 | | - // Skip iteration if we do not have a collection key or a collection member key on this subscriber |
563 | | - if (!Str.startsWith(subscriber.key, collectionKey)) { |
| 568 | + if (subscriber.waitForCollectionCallback) { |
| 569 | + subscriber.callback(cachedCollection, subscriber.key, partialCollection); |
564 | 570 | continue; |
565 | 571 | } |
566 | 572 |
|
567 | | - /** |
568 | | - * e.g. Onyx.connect({key: ONYXKEYS.COLLECTION.REPORT, callback: ...}); |
569 | | - */ |
570 | | - const isSubscribedToCollectionKey = subscriber.key === collectionKey; |
571 | | - |
572 | | - /** |
573 | | - * e.g. Onyx.connect({key: `${ONYXKEYS.COLLECTION.REPORT}{reportID}`, callback: ...}); |
574 | | - */ |
575 | | - const isSubscribedToCollectionMemberKey = OnyxKeys.isCollectionMemberKey(collectionKey, subscriber.key); |
576 | | - |
577 | | - // Regular Onyx.connect() subscriber found. |
578 | | - if (typeof subscriber.callback === 'function') { |
579 | | - // If they are subscribed to the collection key and using waitForCollectionCallback then we'll |
580 | | - // send the whole cached collection. |
581 | | - if (isSubscribedToCollectionKey) { |
582 | | - lastConnectionCallbackData.set(subscriber.subscriptionID, cachedCollection); |
583 | | - |
584 | | - if (subscriber.waitForCollectionCallback) { |
585 | | - subscriber.callback(cachedCollection, subscriber.key, partialCollection); |
586 | | - continue; |
587 | | - } |
588 | | - |
589 | | - // If they are not using waitForCollectionCallback then we notify the subscriber with |
590 | | - // the new merged data but only for any keys in the partial collection. |
591 | | - const dataKeys = Object.keys(partialCollection ?? {}); |
592 | | - for (const dataKey of dataKeys) { |
593 | | - if (deepEqual(cachedCollection[dataKey], previousCollection[dataKey])) { |
594 | | - continue; |
595 | | - } |
596 | | - |
597 | | - subscriber.callback(cachedCollection[dataKey], dataKey); |
598 | | - } |
599 | | - continue; |
600 | | - } |
601 | | - |
602 | | - // And if the subscriber is specifically only tracking a particular collection member key then we will |
603 | | - // notify them with the cached data for that key only. |
604 | | - if (isSubscribedToCollectionMemberKey) { |
605 | | - if (deepEqual(cachedCollection[subscriber.key], previousCollection[subscriber.key])) { |
606 | | - continue; |
607 | | - } |
| 573 | + // Not using waitForCollectionCallback — notify per changed key |
| 574 | + for (const dataKey of changedMemberKeys) { |
| 575 | + if (cachedCollection[dataKey] === previousCollection[dataKey]) continue; // === instead of deepEqual |
| 576 | + subscriber.callback(cachedCollection[dataKey], dataKey); |
| 577 | + } |
| 578 | + } |
608 | 579 |
|
609 | | - const subscriberCallback = subscriber.callback as DefaultConnectCallback<TKey>; |
610 | | - subscriberCallback(cachedCollection[subscriber.key], subscriber.key as TKey); |
611 | | - lastConnectionCallbackData.set(subscriber.subscriptionID, cachedCollection[subscriber.key]); |
612 | | - continue; |
613 | | - } |
| 580 | + // Notify member-level subscribers |
| 581 | + for (const subID of memberSubscriberIDs) { |
| 582 | + const subscriber = callbackToStateMapping[subID]; |
| 583 | + if (!subscriber || typeof subscriber.callback !== 'function') continue; |
| 584 | + if (cachedCollection[subscriber.key] === previousCollection[subscriber.key]) continue; // === instead of deepEqual |
614 | 585 |
|
615 | | - continue; |
616 | | - } |
| 586 | + const subscriberCallback = subscriber.callback as DefaultConnectCallback<TKey>; |
| 587 | + subscriberCallback(cachedCollection[subscriber.key], subscriber.key as TKey); |
| 588 | + lastConnectionCallbackData.set(subscriber.subscriptionID, cachedCollection[subscriber.key]); |
617 | 589 | } |
618 | 590 | } |
619 | 591 |
|
@@ -679,7 +651,8 @@ function keyChanged<TKey extends OnyxKey>( |
679 | 651 | cachedCollections[subscriber.key] = cachedCollection; |
680 | 652 | } |
681 | 653 |
|
682 | | - cachedCollection[key] = value; |
| 654 | + // The cache is always updated before keyChanged runs, so the frozen snapshot |
| 655 | + // already contains the new value — no need to copy or patch it. |
683 | 656 | lastConnectionCallbackData.set(subscriber.subscriptionID, cachedCollection); |
684 | 657 | subscriber.callback(cachedCollection, subscriber.key, {[key]: value}); |
685 | 658 | continue; |
|
0 commit comments