[go: up one dir, main page]

Skip to content

Commit

Permalink
feat(navigation): Upgrade React Navigation to V6 (infinitered#1758 by @…
Browse files Browse the repository at this point in the history
  • Loading branch information
harrisrobin authored Aug 10, 2021
1 parent 2efdf7e commit 5dd10ee
Show file tree
Hide file tree
Showing 11 changed files with 322 additions and 267 deletions.
33 changes: 13 additions & 20 deletions boilerplate/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,11 @@
*/
import "./i18n"
import "./utils/ignore-warnings"
import React, { useState, useEffect, useRef } from "react"
import { NavigationContainerRef } from "@react-navigation/native"
import React, { useState, useEffect } from "react"
import { SafeAreaProvider, initialWindowMetrics } from "react-native-safe-area-context"
import { initFonts } from "./theme/fonts" // expo
import * as storage from "./utils/storage"
import {
useBackButtonHandler,
AppNavigator,
canExit,
setAppNavigation,
useNavigationPersistence,
} from "./navigators"
import { useBackButtonHandler, AppNavigator, canExit, useNavigationPersistence } from "./navigators"
import { RootStore, RootStoreProvider, setupRootStore } from "./models"
import { ToggleStorybook } from "../storybook/toggle-storybook"

Expand All @@ -38,15 +31,14 @@ export const NAVIGATION_PERSISTENCE_KEY = "NAVIGATION_STATE"
* This is the root component of our app.
*/
function App() {
const navigationRef = useRef<NavigationContainerRef>(null)
const [rootStore, setRootStore] = useState<RootStore | undefined>(undefined)

setAppNavigation(navigationRef)
useBackButtonHandler(navigationRef, canExit)
const { initialNavigationState, onNavigationStateChange } = useNavigationPersistence(
storage,
NAVIGATION_PERSISTENCE_KEY,
)
useBackButtonHandler(canExit)
const {
initialNavigationState,
onNavigationStateChange,
isRestored: isNavigationStateRestored,
} = useNavigationPersistence(storage, NAVIGATION_PERSISTENCE_KEY)

// Kick off initial async loading actions, like loading fonts and RootStore
useEffect(() => {
Expand All @@ -58,17 +50,18 @@ function App() {

// Before we show the app, we have to wait for our state to be ready.
// In the meantime, don't render anything. This will be the background
// color set in native by rootView's background color. You can replace
// with your own loading component if you wish.
if (!rootStore) return null
// color set in native by rootView's background color.
// In iOS: application:didFinishLaunchingWithOptions:
// In Android: https://stackoverflow.com/a/45838109/204044
// You can replace with your own loading component if you wish.
if (!rootStore || !isNavigationStateRestored) return null

// otherwise, we're ready to render the app
return (
<ToggleStorybook>
<RootStoreProvider value={rootStore}>
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
<AppNavigator
ref={navigationRef}
initialState={initialNavigationState}
onStateChange={onNavigationStateChange}
/>
Expand Down
24 changes: 15 additions & 9 deletions boilerplate/app/navigators/app-navigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
* and a "main" flow which the user will use once logged in.
*/
import React from "react"
import { NavigationContainer, NavigationContainerRef } from "@react-navigation/native"
import { createStackNavigator } from "@react-navigation/stack"
import { useColorScheme } from "react-native"
import { NavigationContainer, DefaultTheme, DarkTheme } from "@react-navigation/native"
import { createNativeStackNavigator } from "@react-navigation/native-stack"
import { WelcomeScreen, DemoScreen, DemoListScreen } from "../screens"
import { navigationRef } from "./navigation-utilities"

/**
* This type allows TypeScript to know what routes are defined in this navigator
Expand All @@ -28,7 +30,7 @@ export type NavigatorParamList = {
}

// Documentation: https://reactnavigation.org/docs/stack-navigator/
const Stack = createStackNavigator<NavigatorParamList>()
const Stack = createNativeStackNavigator<NavigatorParamList>()

const AppStack = () => {
return (
Expand All @@ -45,16 +47,20 @@ const AppStack = () => {
)
}

export const AppNavigator = React.forwardRef<
NavigationContainerRef,
Partial<React.ComponentProps<typeof NavigationContainer>>
>((props, ref) => {
interface NavigationProps extends Partial<React.ComponentProps<typeof NavigationContainer>> {}

export const AppNavigator = (props: NavigationProps) => {
const colorScheme = useColorScheme()
return (
<NavigationContainer {...props} ref={ref}>
<NavigationContainer
ref={navigationRef}
theme={colorScheme === "dark" ? DarkTheme : DefaultTheme}
{...props}
>
<AppStack />
</NavigationContainer>
)
})
}

AppNavigator.displayName = "AppNavigator"

Expand Down
88 changes: 48 additions & 40 deletions boilerplate/app/navigators/navigation-utilities.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,12 @@
import React, { useState, useEffect, useRef } from "react"
import { useState, useEffect, useRef } from "react"
import { BackHandler } from "react-native"
import { PartialState, NavigationState, NavigationContainerRef } from "@react-navigation/native"

export const AppNavigation = {
navigate(name: string) {
name // eslint-disable-line no-unused-expressions
},
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 any
},
}
import {
PartialState,
NavigationState,
createNavigationContainerRef,
} from "@react-navigation/native"

export const setAppNavigation = (ref: React.RefObject<NavigationContainerRef>) => {
for (const method in AppNavigation) {
AppNavigation[method] = (...args: any) => {
if (ref.current) {
return ref.current[method](...args)
}
}
}
}
export const navigationRef = createNavigationContainerRef()

/**
* Gets the current screen from any navigation state.
Expand All @@ -40,10 +25,7 @@ export function getActiveRouteName(state: NavigationState | PartialState<Navigat
* 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<NavigationContainerRef>,
canExit: (routeName: string) => boolean,
) {
export function useBackButtonHandler(canExit: (routeName: string) => boolean) {
const canExitRef = useRef(canExit)

useEffect(() => {
Expand All @@ -53,14 +35,12 @@ export function useBackButtonHandler(
useEffect(() => {
// We'll fire this when the back button is pressed on Android.
const onBackPress = () => {
const navigation = ref.current

if (navigation == null) {
if (!navigationRef.isReady()) {
return false
}

// grab the current route
const routeName = getActiveRouteName(navigation.getRootState())
const routeName = getActiveRouteName(navigationRef.getRootState())

// are we allowed to exit?
if (canExitRef.current(routeName)) {
Expand All @@ -69,9 +49,8 @@ export function useBackButtonHandler(
}

// we can't exit, so let's turn this into a back action
if (navigation.canGoBack()) {
navigation.goBack()

if (navigationRef.canGoBack()) {
navigationRef.goBack()
return true
}

Expand All @@ -83,17 +62,23 @@ export function useBackButtonHandler(

// 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()
// This feature is particularly useful in development mode.
// It is selectively enabled in development mode with
// the following approach. If you'd like to use navigation persistence
// in production, remove the __DEV__ and set the state to true
const [isRestored, setIsRestored] = useState(!__DEV__)

const routeNameRef = useRef<string | undefined>()

const onNavigationStateChange = (state) => {
const previousRouteName = routeNameRef.current
const currentRouteName = getActiveRouteName(state)
Expand All @@ -115,13 +100,36 @@ export function useNavigationPersistence(storage: any, persistenceKey: string) {
const state = await storage.load(persistenceKey)
if (state) setInitialNavigationState(state)
} finally {
setIsRestoringNavigationState(false)
setIsRestored(true)
}
}

useEffect(() => {
if (isRestoringNavigationState) restoreState()
}, [isRestoringNavigationState])
if (!isRestored) restoreState()
}, [isRestored])

return { onNavigationStateChange, restoreState, isRestored, initialNavigationState }
}

/**
* use this to navigate to navigate without the navigation
* prop. If you have access to the navigation prop, do not use this.
* More info: https://reactnavigation.org/docs/navigating-without-navigation-prop/
*/
export function navigate(name: any, params?: any) {
if (navigationRef.isReady()) {
navigationRef.navigate(name as never, params as never)
}
}

return { onNavigationStateChange, restoreState, initialNavigationState }
export function goBack() {
if (navigationRef.isReady() && navigationRef.canGoBack()) {
navigationRef.goBack()
}
}

export function resetRoot(params = { index: 0, routes: [] }) {
if (navigationRef.isReady()) {
navigationRef.resetRoot(params)
}
}
85 changes: 44 additions & 41 deletions boilerplate/app/screens/demo/demo-list-screen.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { useEffect } from "react"
import React, { useEffect, FC } from "react"
import { FlatList, TextStyle, View, ViewStyle, ImageStyle } from "react-native"
import { useNavigation } from "@react-navigation/native"
import { StackScreenProps } from "@react-navigation/stack"
import { observer } from "mobx-react-lite"
import { Header, Screen, Text, AutoImage as Image, GradientBackground } from "../../components"
import { color, spacing } from "../../theme"
import { useStores } from "../../models"
import { NavigatorParamList } from "../../navigators"

const FULL: ViewStyle = {
flex: 1,
Expand Down Expand Up @@ -41,46 +42,48 @@ const FLAT_LIST: ViewStyle = {
paddingHorizontal: spacing[4],
}

export const DemoListScreen = observer(function DemoListScreen() {
const navigation = useNavigation()
const goBack = () => navigation.goBack()
export const DemoListScreen: FC<StackScreenProps<NavigatorParamList, "demoList">> = observer(
({ navigation }) => {
const goBack = () => navigation.goBack()

const { characterStore } = useStores()
const { characters } = characterStore
const { characterStore } = useStores()
const { characters } = characterStore

useEffect(() => {
async function fetchData() {
await characterStore.getCharacters()
}
useEffect(() => {
async function fetchData() {
await characterStore.getCharacters()
}

fetchData()
}, [])
fetchData()
}, [])

return (
<View testID="DemoListScreen" style={FULL}>
<GradientBackground colors={["#422443", "#281b34"]} />
<Screen style={CONTAINER} preset="fixed" backgroundColor={color.transparent}>
<Header
headerTx="demoListScreen.title"
leftIcon="back"
onLeftPress={goBack}
style={HEADER}
titleStyle={HEADER_TITLE}
/>
<FlatList
contentContainerStyle={FLAT_LIST}
data={[...characters]}
keyExtractor={(item) => String(item.id)}
renderItem={({ item }) => (
<View style={LIST_CONTAINER}>
<Image source={{ uri: item.image }} style={IMAGE} />
<Text style={LIST_TEXT}>
{item.name} ({item.status})
</Text>
</View>
)}
/>
</Screen>
</View>
)
},
)

return (
<View testID="DemoListScreen" style={FULL}>
<GradientBackground colors={["#422443", "#281b34"]} />
<Screen style={CONTAINER} preset="fixed" backgroundColor={color.transparent}>
<Header
headerTx="demoListScreen.title"
leftIcon="back"
onLeftPress={goBack}
style={HEADER}
titleStyle={HEADER_TITLE}
/>
<FlatList
contentContainerStyle={FLAT_LIST}
data={[...characters]}
keyExtractor={(item) => String(item.id)}
renderItem={({ item }) => (
<View style={LIST_CONTAINER}>
<Image source={{ uri: item.image }} style={IMAGE} />
<Text style={LIST_TEXT}>
{item.name} ({item.status})
</Text>
</View>
)}
/>
</Screen>
</View>
)
})
Loading

0 comments on commit 5dd10ee

Please sign in to comment.