@gocodingnow/rn-better-boilerplate
Version:
React Native Better Boilerplate
138 lines (114 loc) • 3.65 kB
text/typescript
import { useState, useEffect, useRef, RefObject, useCallback } from 'react'
import { BackHandler } from 'react-native'
import {
PartialState,
NavigationState,
NavigationContainerRef,
} from '@react-navigation/native'
export const RootNavigation = {
navigate(name: string) {
name
},
goBack() {}, // eslint-disable-line @typescript-eslint/no-empty-function
resetRoot(state?: PartialState<NavigationState> | NavigationState) {}, // eslint-disable-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
getRootState(): NavigationState {
return {} as never
},
}
export const setRootNavigation = (
ref: RefObject<NavigationContainerRef<never>>,
) => {
for (const method in RootNavigation) {
RootNavigation[method] = (...args) => {
if (ref.current) {
return ref.current[method](...args)
}
}
}
}
/**
* Gets the current screen from any navigation state.
*/
export function getActiveRouteName(
state: NavigationState | PartialState<NavigationState>,
) {
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: RefObject<NavigationContainerRef<any>>,
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 currentRouteName = getActiveRouteName(state)
// Save the current route name for later comparison
routeNameRef.current = currentRouteName
// Persist state to storage
storage.save(persistenceKey, state)
}
const restoreState = useCallback(async () => {
try {
const state = await storage.load(persistenceKey)
if (state) {
setInitialNavigationState(state)
}
} finally {
setIsRestoringNavigationState(false)
}
}, [persistenceKey, storage])
useEffect(() => {
if (isRestoringNavigationState) {
restoreState()
}
}, [isRestoringNavigationState, restoreState])
return { onNavigationStateChange, restoreState, initialNavigationState }
}