@@ -380,6 +380,9 @@ const BottomNavigationBar = <Route extends BaseRoute>({
380380   * Animation for the background color ripple, used to determine it's scale and opacity. 
381381   */ 
382382  const  rippleAnim  =  useAnimatedValue ( MIN_RIPPLE_SCALE ) ; 
383+   const  animationInProgressRef  =  React . useRef ( false ) ; 
384+   const  targetIndexRef  =  React . useRef ( navigationState . index ) ; 
385+   const  isInitialMount  =  React . useRef ( true ) ; 
383386
384387  /** 
385388   * Layout of the navigation bar. The width is used to determine the size and position of the ripple. 
@@ -412,6 +415,21 @@ const BottomNavigationBar = <Route extends BaseRoute>({
412415
413416  const  animateToIndex  =  React . useCallback ( 
414417    ( index : number )  =>  { 
418+       // If already animating to this index, do nothing 
419+       if  ( animationInProgressRef . current  &&  targetIndexRef . current  ===  index )  { 
420+         return ; 
421+       } 
422+ 
423+       // If animating to a different index, cancel current animation and restart 
424+       // This prevents half-states when rapidly switching tabs 
425+       if  ( animationInProgressRef . current )  { 
426+         rippleAnim . stopAnimation ( ) ; 
427+         indexAnim . stopAnimation ( ) ; 
428+         tabsAnims . forEach ( tab  =>  tab . stopAnimation ( ) ) ; 
429+       } 
430+ 
431+       targetIndexRef . current  =  index ; 
432+       animationInProgressRef . current  =  true ; 
415433      // Reset the ripple to avoid glitch if it's currently animating 
416434      rippleAnim . setValue ( MIN_RIPPLE_SCALE ) ; 
417435
@@ -429,13 +447,40 @@ const BottomNavigationBar = <Route extends BaseRoute>({
429447            easing : animationEasing , 
430448          } ) 
431449        ) , 
432-       ] ) . start ( ( )  =>  { 
433-         // Workaround a bug in native animations where this is reset after first animation 
434-         tabsAnims . map ( ( tab ,  i )  =>  tab . setValue ( i  ===  index  ? 1  : 0 ) ) ; 
450+       ] ) . start ( ( result )  =>  { 
451+         animationInProgressRef . current  =  false ; 
435452
436-         // Update the index to change bar's background color and then hide the ripple 
453+         if  ( targetIndexRef . current  !==  index )  { 
454+           return ; 
455+         } 
456+ 
457+         // Explicitly finish the animation values to avoid RN 0.80 dropping the completion frame 
458+         tabsAnims . forEach ( ( tab ,  i )  =>  { 
459+           tab . stopAnimation ( ) ; 
460+           tab . setValue ( i  ===  index  ? 1  : 0 ) ; 
461+         } ) ; 
462+ 
463+         indexAnim . stopAnimation ( ) ; 
437464        indexAnim . setValue ( index ) ; 
465+ 
466+         rippleAnim . stopAnimation ( ) ; 
438467        rippleAnim . setValue ( MIN_RIPPLE_SCALE ) ; 
468+ 
469+         // Safety fallback: if animation was interrupted, ensure completion on next frame 
470+         if  ( result ?. finished  ===  false )  { 
471+           requestAnimationFrame ( ( )  =>  { 
472+             if  ( targetIndexRef . current  !==  index )  { 
473+               return ; 
474+             } 
475+ 
476+             tabsAnims . forEach ( ( tab ,  i )  =>  { 
477+               tab . stopAnimation ( ) ; 
478+               tab . setValue ( i  ===  index  ? 1  : 0 ) ; 
479+             } ) ; 
480+             indexAnim . setValue ( index ) ; 
481+             rippleAnim . setValue ( MIN_RIPPLE_SCALE ) ; 
482+           } ) ; 
483+         } 
439484      } ) ; 
440485    } , 
441486    [ 
@@ -450,19 +495,18 @@ const BottomNavigationBar = <Route extends BaseRoute>({
450495    ] 
451496  ) ; 
452497
453-   React . useEffect ( ( )  =>  { 
454-     // Workaround for native animated bug in react-native@^0.57 
455-     // Context: https://github.com/callstack/react-native-paper/pull/637 
456-     animateToIndex ( navigationState . index ) ; 
457-     // eslint-disable-next-line react-hooks/exhaustive-deps 
458-   } ,  [ ] ) ; 
459- 
460498  useIsKeyboardShown ( { 
461499    onShow : handleKeyboardShow , 
462500    onHide : handleKeyboardHide , 
463501  } ) ; 
464502
465503  React . useEffect ( ( )  =>  { 
504+     if  ( isInitialMount . current )  { 
505+       // Skip animation on initial mount - values are already correctly initialized 
506+       // Animating on mount causes RN 0.80+ native driver to render incomplete frames 
507+       isInitialMount . current  =  false ; 
508+       return ; 
509+     } 
466510    animateToIndex ( navigationState . index ) ; 
467511  } ,  [ navigationState . index ,  animateToIndex ] ) ; 
468512
@@ -676,7 +720,10 @@ const BottomNavigationBar = <Route extends BaseRoute>({
676720                  inputRange : [ 0 ,  1 ] , 
677721                  outputRange : [ 0.5 ,  1 ] , 
678722                } ) 
679-               : 0 ; 
723+               : active . interpolate ( { 
724+                   inputRange : [ 0 ,  1 ] , 
725+                   outputRange : [ 0 ,  1 ] , 
726+                 } ) ; 
680727
681728            const  badge  =  getBadge ( {  route } ) ; 
682729
@@ -740,7 +787,7 @@ const BottomNavigationBar = <Route extends BaseRoute>({
740787                      } , 
741788                    ] } 
742789                  > 
743-                     { isV3  &&  focused   &&   ( 
790+                     { isV3  &&  ( 
744791                      < Animated . View 
745792                        style = { [ 
746793                          styles . outline , 
0 commit comments