@@ -536,6 +536,222 @@ describe('useOnyx', () => {
536536 expect ( result . current [ 0 ] ) . not . toBe ( firstResult ) ;
537537 expect ( result . current [ 0 ] ) . toBe ( 10 ) ;
538538 } ) ;
539+
540+ it ( 'should recompute selector when dependencies change even if input data stays the same' , async ( ) => {
541+ const testCollection = {
542+ [ `${ ONYXKEYS . COLLECTION . TEST_KEY } 1` ] : { id : '1' , value : 'item1' } ,
543+ [ `${ ONYXKEYS . COLLECTION . TEST_KEY } 2` ] : { id : '2' , value : 'item2' } ,
544+ [ `${ ONYXKEYS . COLLECTION . TEST_KEY } 3` ] : { id : '3' , value : 'item3' } ,
545+ } ;
546+
547+ await act ( async ( ) => Onyx . mergeCollection ( ONYXKEYS . COLLECTION . TEST_KEY , testCollection as GenericCollection ) ) ;
548+
549+ let filterIds = [ '1' ] ;
550+ let selectorCallCount = 0 ;
551+
552+ const { result, rerender} = renderHook ( ( ) =>
553+ useOnyx (
554+ ONYXKEYS . COLLECTION . TEST_KEY ,
555+ {
556+ selector : ( collection ) => {
557+ selectorCallCount ++ ;
558+ return filterIds . map ( ( id ) => ( collection as OnyxCollection < GenericCollection > ) ?. [ `${ ONYXKEYS . COLLECTION . TEST_KEY } ${ id } ` ] ) . filter ( Boolean ) ;
559+ } ,
560+ } ,
561+ [ filterIds ] ,
562+ ) ,
563+ ) ;
564+
565+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
566+
567+ // Record count after initial stabilization
568+ const initialCallCount = selectorCallCount ;
569+ const initialResult = result . current [ 0 ] ;
570+
571+ // Should return item with id '1'
572+ expect ( initialResult ) . toEqual ( [ { id : '1' , value : 'item1' } ] ) ;
573+
574+ // Change dependencies without changing underlying data
575+ await act ( async ( ) => {
576+ filterIds = [ '1' , '2' ] ;
577+ rerender ( ONYXKEYS . COLLECTION . TEST_KEY ) ;
578+ } ) ;
579+
580+ // Selector should recompute and return items with id '1' and '2'
581+ expect ( result . current [ 0 ] ) . toEqual ( [
582+ { id : '1' , value : 'item1' } ,
583+ { id : '2' , value : 'item2' } ,
584+ ] ) ;
585+ expect ( selectorCallCount ) . toBeGreaterThan ( initialCallCount ) ;
586+
587+ // Record count after first dependency change
588+ const firstChangeCallCount = selectorCallCount ;
589+
590+ // Change dependencies again
591+ await act ( async ( ) => {
592+ filterIds = [ '2' , '3' ] ;
593+ rerender ( ONYXKEYS . COLLECTION . TEST_KEY ) ;
594+ } ) ;
595+
596+ // Selector should recompute and return items with id '2' and '3'
597+ expect ( result . current [ 0 ] ) . toEqual ( [
598+ { id : '2' , value : 'item2' } ,
599+ { id : '3' , value : 'item3' } ,
600+ ] ) ;
601+ expect ( selectorCallCount ) . toBeGreaterThan ( firstChangeCallCount ) ;
602+ } ) ;
603+
604+ it ( 'should handle complex dependency scenarios with multiple values' , async ( ) => {
605+ type TestItem = { id : string ; category : string ; priority : number } ;
606+ const testData = {
607+ [ `${ ONYXKEYS . COLLECTION . TEST_KEY } item1` ] : { id : 'item1' , category : 'A' , priority : 1 } ,
608+ [ `${ ONYXKEYS . COLLECTION . TEST_KEY } item2` ] : { id : 'item2' , category : 'B' , priority : 2 } ,
609+ [ `${ ONYXKEYS . COLLECTION . TEST_KEY } item3` ] : { id : 'item3' , category : 'A' , priority : 3 } ,
610+ [ `${ ONYXKEYS . COLLECTION . TEST_KEY } item4` ] : { id : 'item4' , category : 'B' , priority : 4 } ,
611+ } ;
612+
613+ await act ( async ( ) => Onyx . mergeCollection ( ONYXKEYS . COLLECTION . TEST_KEY , testData as GenericCollection ) ) ;
614+
615+ let categoryFilter = 'A' ;
616+ let sortAscending = true ;
617+
618+ const { result, rerender} = renderHook ( ( ) =>
619+ useOnyx (
620+ ONYXKEYS . COLLECTION . TEST_KEY ,
621+ {
622+ selector : ( collection ) => {
623+ const typedCollection = collection as OnyxCollection < TestItem > ;
624+ if ( ! typedCollection ) return [ ] ;
625+
626+ const filtered = Object . values ( typedCollection ) . filter ( ( item ) => item ?. category === categoryFilter ) ;
627+
628+ return filtered . sort ( ( a , b ) => ( sortAscending ? ( a ?. priority ?? 0 ) - ( b ?. priority ?? 0 ) : ( b ?. priority ?? 0 ) - ( a ?. priority ?? 0 ) ) ) ;
629+ } ,
630+ } ,
631+ [ categoryFilter , sortAscending ] ,
632+ ) ,
633+ ) ;
634+
635+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
636+
637+ // Should return category A items sorted ascending
638+ expect ( result . current [ 0 ] ) . toEqual ( [
639+ { id : 'item1' , category : 'A' , priority : 1 } ,
640+ { id : 'item3' , category : 'A' , priority : 3 } ,
641+ ] ) ;
642+
643+ // Change sort order only
644+ await act ( async ( ) => {
645+ sortAscending = false ;
646+ rerender ( ONYXKEYS . COLLECTION . TEST_KEY ) ;
647+ } ) ;
648+
649+ // Should return category A items sorted descending
650+ expect ( result . current [ 0 ] ) . toEqual ( [
651+ { id : 'item3' , category : 'A' , priority : 3 } ,
652+ { id : 'item1' , category : 'A' , priority : 1 } ,
653+ ] ) ;
654+
655+ // Change category filter
656+ await act ( async ( ) => {
657+ categoryFilter = 'B' ;
658+ rerender ( ONYXKEYS . COLLECTION . TEST_KEY ) ;
659+ } ) ;
660+
661+ // Should return category B items sorted descending
662+ expect ( result . current [ 0 ] ) . toEqual ( [
663+ { id : 'item4' , category : 'B' , priority : 4 } ,
664+ { id : 'item2' , category : 'B' , priority : 2 } ,
665+ ] ) ;
666+ } ) ;
667+
668+ it ( 'should not trigger unnecessary recomputations when dependencies remain the same' , async ( ) => {
669+ await act ( async ( ) => Onyx . set ( ONYXKEYS . TEST_KEY , { value : 'test' } ) ) ;
670+
671+ const dependencies = [ 'constant' ] ;
672+ let selectorCallCount = 0 ;
673+
674+ const { result, rerender} = renderHook ( ( ) =>
675+ useOnyx (
676+ ONYXKEYS . TEST_KEY ,
677+ {
678+ selector : ( data ) => {
679+ selectorCallCount ++ ;
680+ return `${ dependencies . join ( ',' ) } :${ ( data as { value ?: string } ) ?. value } ` ;
681+ } ,
682+ } ,
683+ dependencies ,
684+ ) ,
685+ ) ;
686+
687+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
688+
689+ expect ( result . current [ 0 ] ) . toBe ( 'constant:test' ) ;
690+ expect ( selectorCallCount ) . toBe ( 1 ) ;
691+
692+ // Force rerender without changing dependencies
693+ await act ( async ( ) => {
694+ rerender ( ONYXKEYS . COLLECTION . TEST_KEY ) ;
695+ } ) ;
696+
697+ // Selector should not recompute since dependencies haven't changed
698+ expect ( result . current [ 0 ] ) . toBe ( 'constant:test' ) ;
699+ expect ( selectorCallCount ) . toBe ( 1 ) ;
700+
701+ // Update underlying data
702+ await act ( async ( ) => Onyx . merge ( ONYXKEYS . TEST_KEY , { value : 'updated' } ) ) ;
703+
704+ // Selector should recompute due to data change
705+ expect ( result . current [ 0 ] ) . toBe ( 'constant:updated' ) ;
706+ expect ( selectorCallCount ) . toBe ( 2 ) ;
707+ } ) ;
708+
709+ it ( 'should handle dependencies with deep equality changes' , async ( ) => {
710+ await act ( async ( ) => Onyx . set ( ONYXKEYS . TEST_KEY , { items : [ 'a' , 'b' , 'c' ] } ) ) ;
711+
712+ let config = { includeItems : [ 'a' , 'b' ] } ;
713+ let selectorCallCount = 0 ;
714+
715+ const { result, rerender} = renderHook ( ( ) =>
716+ useOnyx (
717+ ONYXKEYS . TEST_KEY ,
718+ {
719+ selector : ( data ) => {
720+ selectorCallCount ++ ;
721+ const typedData = data as { items ?: string [ ] } ;
722+ if ( ! typedData ?. items ) return [ ] ;
723+ return typedData . items . filter ( ( item : string ) => config . includeItems . includes ( item ) ) ;
724+ } ,
725+ } ,
726+ [ config ] ,
727+ ) ,
728+ ) ;
729+
730+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
731+
732+ expect ( result . current [ 0 ] ) . toEqual ( [ 'a' , 'b' ] ) ;
733+ expect ( selectorCallCount ) . toBe ( 1 ) ;
734+
735+ // Change config to new object with same content
736+ await act ( async ( ) => {
737+ config = { includeItems : [ 'a' , 'b' ] } ;
738+ rerender ( ONYXKEYS . COLLECTION . TEST_KEY ) ;
739+ } ) ;
740+
741+ // Should not recompute since deep equality shows no change
742+ expect ( result . current [ 0 ] ) . toEqual ( [ 'a' , 'b' ] ) ;
743+ expect ( selectorCallCount ) . toBe ( 1 ) ;
744+
745+ // Change config content
746+ await act ( async ( ) => {
747+ config = { includeItems : [ 'b' , 'c' ] } ;
748+ rerender ( ONYXKEYS . COLLECTION . TEST_KEY ) ;
749+ } ) ;
750+
751+ // Should recompute due to content change
752+ expect ( result . current [ 0 ] ) . toEqual ( [ 'b' , 'c' ] ) ;
753+ expect ( selectorCallCount ) . toBe ( 2 ) ;
754+ } ) ;
539755 } ) ;
540756
541757 describe ( 'allowStaleData' , ( ) => {
0 commit comments