All files / apps/host/src/navigation index.tsx

90% Statements 27/30
75% Branches 6/8
66.66% Functions 6/9
90% Lines 27/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99                                      1x 11x 11x   11x 11x 11x     11x   11x 5x     11x           11x 11x   11x   11x 9x                 11x 5x 5x     5x       11x 6x 6x 6x     11x 1x             10x                         1x              
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { ActivityIndicator, StyleSheet, View } from 'react-native';
 
import { NavigationContainerRef } from '@react-navigation/native';
 
import { CoreNavigation } from '@repo/core/navigation/CoreNavigation';
 
import { useAuth } from '@/contexts/AuthContext';
import { DeepLinkProvider } from '@/contexts/DeepLinkContext';
 
import { linkingConfig, useNavigateDeepLinks } from '@/hooks/deeplink';
 
import type { AppStackParamList } from '@/types/navigation';
 
import { AuthNavigator } from './AuthStackNavigation';
import { Navigation as AppNavigator } from './Navigation';
 
type TimeoutHandle = number | { cancel: () => void };
 
export const Navigation = () => {
  const { user, loading } = useAuth();
  const prevUserRef = useRef(user);
 
  const navigationRef = useRef<NavigationContainerRef<AppStackParamList>>(null);
  const isNavigationReadyRef = useRef(false);
  const timeoutRefsRef = useRef<TimeoutHandle[]>([]);
 
  // Derive isLoggedOut during render to avoid one-render delay from state update
  const isLoggedOut = !!prevUserRef.current && !user;
 
  useEffect(() => {
    prevUserRef.current = user;
  }, [user]);
 
  const { processPendingDeepLink, setPendingDeepLink, navigateFromDeepLink } = useNavigateDeepLinks(
    navigationRef,
    isNavigationReadyRef,
    timeoutRefsRef,
  );
 
  const contextValueRef = useRef({ setPendingDeepLink, navigateFromDeepLink });
  contextValueRef.current = { setPendingDeepLink, navigateFromDeepLink };
 
  const [isNavigationReadyState, setIsNavigationReadyState] = useState(false);
 
  const contextValue = useMemo(
    () => ({
      setPendingDeepLink: (url: string) => contextValueRef.current.setPendingDeepLink(url),
      navigateFromDeepLink: (url: string) => contextValueRef.current.navigateFromDeepLink(url),
      navigationRef,
      isNavigationReady: isNavigationReadyState,
    }),
    [isNavigationReadyState],
  );
 
  useEffect(() => {
    return () => {
      timeoutRefsRef.current.forEach(handle => {
        typeof handle === 'number' ? cancelAnimationFrame(handle) : handle.cancel();
      });
      timeoutRefsRef.current = [];
    };
  }, []);
 
  const handleNavigationReady = useCallback(() => {
    isNavigationReadyRef.current = true;
    setIsNavigationReadyState(true);
    processPendingDeepLink();
  }, [processPendingDeepLink]);
 
  if (loading) {
    return (
      <View style={styles.loadingContainer} testID="loading-indicator">
        <ActivityIndicator size="large" />
      </View>
    );
  }
 
  return (
    <DeepLinkProvider value={contextValue}>
      <CoreNavigation
        navigationRef={navigationRef}
        linking={linkingConfig}
        onReady={handleNavigationReady}
      >
        {user ? <AppNavigator /> : <AuthNavigator isLoggedOut={isLoggedOut} />}
      </CoreNavigation>
    </DeepLinkProvider>
  );
};
 
const styles = StyleSheet.create({
  loadingContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});