@@ -385,7 +385,7 @@ class OnyxCache {
385385 // Initialize frozen snapshots for collection keys
386386 for ( const collectionKey of collectionKeys ) {
387387 if ( ! this . collectionSnapshots . has ( collectionKey ) ) {
388- this . collectionSnapshots . set ( collectionKey , Object . freeze ( { } ) ) ;
388+ this . collectionSnapshots . set ( collectionKey , FROZEN_EMPTY_COLLECTION ) ;
389389 }
390390 }
391391 }
@@ -403,6 +403,7 @@ class OnyxCache {
403403
404404 const members : NonUndefined < OnyxCollection < KeyValueMapping [ OnyxKey ] > > = { } ;
405405 let hasMemberChanges = false ;
406+ let hasMembers = false ;
406407
407408 // Use the indexed forward lookup for O(collectionMembers) iteration.
408409 // Falls back to scanning all storageKeys if the index isn't populated yet.
@@ -421,6 +422,7 @@ class OnyxCache {
421422 // and should not be included in the frozen collection snapshot.
422423 if ( val !== undefined && val !== null ) {
423424 members [ key ] = val ;
425+ hasMembers = true ;
424426
425427 // Check if this member's reference changed from the old snapshot
426428 if ( ! hasMemberChanges && ( ! previousSnapshot || previousSnapshot [ key ] !== val ) ) {
@@ -449,6 +451,14 @@ class OnyxCache {
449451 return ;
450452 }
451453
454+ // When the collection has no members, reuse one shared empty object for every empty
455+ // collection. That way reads can tell a collection is empty with a quick `===` check
456+ // instead of looping over its keys every time.
457+ if ( ! hasMembers ) {
458+ this . collectionSnapshots . set ( collectionKey , FROZEN_EMPTY_COLLECTION ) ;
459+ return ;
460+ }
461+
452462 Object . freeze ( members ) ;
453463
454464 this . collectionSnapshots . set ( collectionKey , members ) ;
@@ -466,18 +476,21 @@ class OnyxCache {
466476 }
467477
468478 const snapshot = this . collectionSnapshots . get ( collectionKey ) ;
469- if ( utils . isEmptyObject ( snapshot ) ) {
470- // We check storageKeys.size (not collection-specific keys) to distinguish
471- // "init complete, this collection is genuinely empty" from "init not done yet."
472- // During init, setAllKeys loads ALL keys at once — so if any key exists,
473- // the full storage picture is loaded and an empty collection is truly empty.
474- // Returning undefined before init prevents subscribers from seeing a false empty state.
475- if ( this . storageKeys . size > 0 ) {
476- return FROZEN_EMPTY_COLLECTION ;
477- }
479+
480+ // We never stored anything for this collection key.
481+ if ( snapshot === undefined ) {
478482 return undefined ;
479483 }
480484
485+ // The collection is empty (it holds our shared empty object). But "empty" is ambiguous
486+ // during startup: we can't tell an actually-empty collection apart from one whose data
487+ // hasn't loaded yet. Once any key exists, we know setAllKeys has run and loaded everything,
488+ // so an empty collection really is empty. Before that, return undefined so subscribers
489+ // don't briefly see a collection as empty when it just hasn't loaded.
490+ if ( snapshot === FROZEN_EMPTY_COLLECTION ) {
491+ return this . storageKeys . size > 0 ? FROZEN_EMPTY_COLLECTION : undefined ;
492+ }
493+
481494 return snapshot ;
482495 }
483496}
0 commit comments