import React, { useState, useEffect, useRef } from 'react' import { BackHandler } from 'react-native' import { PartialState, NavigationState, NavigationContainerRef, } from '@react-navigation/native' export const RootNavigation = { navigate(name: string) { name // eslint-disable-line no-unused-expressions }, goBack() {}, // eslint-disable-line @typescript-eslint/no-empty-function resetRoot(state?: PartialState | NavigationState) {}, // eslint-disable-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function getRootState(): NavigationState { return {} as any }, } export const setRootNavigation = ( ref: React.RefObject, ) => { for (const method in RootNavigation) { RootNavigation[method] = (...args: any) => { if (ref.current) { return ref.current[method](...args) } } } } /** * Gets the current screen from any navigation state. */ export function getActiveRouteName( state: NavigationState | PartialState, ) { const route = state.routes[state.index] // Found the active route -- return the name if (!route.state) return route.name // Recursive call to deal with nested routers return getActiveRouteName(route.state) } /** * Hook that handles Android back button presses and forwards those on to * the navigation or allows exiting the app. */ export function useBackButtonHandler( ref: React.RefObject, canExit: (routeName: string) => boolean, ) { const canExitRef = useRef(canExit) useEffect(() => { canExitRef.current = canExit }, [canExit]) useEffect(() => { // We'll fire this when the back button is pressed on Android. const onBackPress = () => { const navigation = ref.current if (navigation == null) { return false } // grab the current route const routeName = getActiveRouteName(navigation.getRootState()) // are we allowed to exit? if (canExitRef.current(routeName)) { // let the system know we've not handled this event return false } // we can't exit, so let's turn this into a back action if (navigation.canGoBack()) { navigation.goBack() return true } return false } // Subscribe when we come to life BackHandler.addEventListener('hardwareBackPress', onBackPress) // Unsubscribe when we're done return () => BackHandler.removeEventListener('hardwareBackPress', onBackPress) }, [ref]) } /** * Custom hook for persisting navigation state. */ export function useNavigationPersistence(storage: any, persistenceKey: string) { const [initialNavigationState, setInitialNavigationState] = useState() const [isRestoringNavigationState, setIsRestoringNavigationState] = useState( true, ) const routeNameRef = useRef() const onNavigationStateChange = (state) => { const previousRouteName = routeNameRef.current const currentRouteName = getActiveRouteName(state) if (previousRouteName !== currentRouteName) { // track screens. __DEV__ && console.tron.log(currentRouteName) } // Save the current route name for later comparision routeNameRef.current = currentRouteName // Persist state to storage storage.save(persistenceKey, state) } const restoreState = async () => { try { const state = await storage.load(persistenceKey) if (state) setInitialNavigationState(state) } finally { setIsRestoringNavigationState(false) } } useEffect(() => { if (isRestoringNavigationState) restoreState() }, [isRestoringNavigationState]) return { onNavigationStateChange, restoreState, initialNavigationState } }