diff --git a/blog/2020-05-16-web-support.md b/blog/2020-05-16-web-support.md index 599bf8357e..0f5be8529c 100644 --- a/blog/2020-05-16-web-support.md +++ b/blog/2020-05-16-web-support.md @@ -34,7 +34,7 @@ Example: ```js const linking = { - prefixes: ['https://mychat.com/', 'mychat://'], + prefixes: ['https://example.com/', 'example://'], config: { screens: { Home: '', diff --git a/blog/2024-03-25-introducing-static-api.md b/blog/2024-03-25-introducing-static-api.md index fc950b677b..014d7567e8 100644 --- a/blog/2024-03-25-introducing-static-api.md +++ b/blog/2024-03-25-introducing-static-api.md @@ -138,7 +138,7 @@ There are 2 improvements to deep linking API: return ( + ## Introduction React Navigation comes with many navigators out of the box. We've got Stack, Native Stack, Drawer, and Bottom Tabs, but there were no Native Bottom Tabs until today! diff --git a/versioned_docs/version-1.x/deep-linking.md b/versioned_docs/version-1.x/deep-linking.md index 4e3b7e229a..bbbffeb5ba 100644 --- a/versioned_docs/version-1.x/deep-linking.md +++ b/versioned_docs/version-1.x/deep-linking.md @@ -4,7 +4,7 @@ title: Deep linking sidebar_label: Deep linking --- -In this guide we will set up our app to handle external URIs. Let's suppose that we want a URI like `mychat://chat/Eric` to open our app and link straight into a chat screen for some user named "Eric". +In this guide we will set up our app to handle external URIs. Let's suppose that we want a URI like `example://chat/Eric` to open our app and link straight into a chat screen for some user named "Eric". ## Configuration @@ -36,7 +36,7 @@ You need to specify a scheme for your app. You can register for a scheme in your ```json { "expo": { - "scheme": "mychat" + "scheme": "example" } } ``` @@ -90,14 +90,14 @@ Next, let's configure our navigation container to extract the path from the app' ```js const SimpleApp = StackNavigator({...})); -const prefix = 'mychat://'; +const prefix = 'example://'; const MainApp = () => ; ``` ### iOS -Let's configure the native iOS app to open based on the `mychat://` URI scheme. +Let's configure the native iOS app to open based on the `example://` URI scheme. In `SimpleApp/ios/SimpleApp/AppDelegate.m`: @@ -116,7 +116,7 @@ In `SimpleApp/ios/SimpleApp/AppDelegate.m`: In Xcode, open the project at `SimpleApp/ios/SimpleApp.xcodeproj`. Select the project in sidebar and navigate to the info tab. Scroll down to "URL Types" and add one. In the new URL type, set the identifier and the URL scheme to your desired URL scheme. -![Xcode project info URL types with mychat added](/assets/deep-linking/xcode-linking.png) +![Xcode project info URL types with example added](/assets/deep-linking/xcode-linking.png) Now you can press play in Xcode, or re-build on the command line: @@ -127,10 +127,10 @@ react-native run-ios To test the URI on the simulator, run the following: ``` -xcrun simctl openurl booted mychat://chat/Eric +xcrun simctl openurl booted example://chat/Eric ``` -To test the URI on a real device, open Safari and type `mychat://chat/Eric`. +To test the URI on a real device, open Safari and type `example://chat/Eric`. ### Android @@ -153,7 +153,7 @@ In `SimpleApp/android/app/src/main/AndroidManifest.xml`, do these followings adj - + ``` @@ -167,7 +167,7 @@ react-native run-android To test the intent handling in Android, run the following: ``` -adb shell am start -W -a android.intent.action.VIEW -d "mychat://chat/Eric" com.simpleapp +adb shell am start -W -a android.intent.action.VIEW -d "example://chat/Eric" com.simpleapp ``` ## Disable deep linking diff --git a/versioned_docs/version-2.x/deep-linking.md b/versioned_docs/version-2.x/deep-linking.md index e0e0f6f385..1659d84a7a 100644 --- a/versioned_docs/version-2.x/deep-linking.md +++ b/versioned_docs/version-2.x/deep-linking.md @@ -4,7 +4,7 @@ title: Deep linking sidebar_label: Deep linking --- -In this guide we will set up our app to handle external URIs. Let's suppose that we want a URI like `mychat://chat/Eric` to open our app and link straight into a chat screen for some user named "Eric". +In this guide we will set up our app to handle external URIs. Let's suppose that we want a URI like `example://chat/Eric` to open our app and link straight into a chat screen for some user named "Eric". ## Configuration @@ -36,7 +36,7 @@ You need to specify a scheme for your app. You can register for a scheme in your ```json { "expo": { - "scheme": "mychat" + "scheme": "example" } } ``` @@ -90,14 +90,14 @@ Next, let's configure our navigation container to extract the path from the app' ```js const SimpleApp = createStackNavigator({...}); -const prefix = 'mychat://'; +const prefix = 'example://'; const MainApp = () => ; ``` ### iOS -Let's configure the native iOS app to open based on the `mychat://` URI scheme. +Let's configure the native iOS app to open based on the `example://` URI scheme. In `SimpleApp/ios/SimpleApp/AppDelegate.m`: @@ -116,7 +116,7 @@ In `SimpleApp/ios/SimpleApp/AppDelegate.m`: In Xcode, open the project at `SimpleApp/ios/SimpleApp.xcodeproj`. Select the project in sidebar and navigate to the info tab. Scroll down to "URL Types" and add one. In the new URL type, set the identifier and the URL scheme to your desired URL scheme. -![Xcode project info URL types with mychat added](/assets/deep-linking/xcode-linking.png) +![Xcode project info URL types with example added](/assets/deep-linking/xcode-linking.png) Now you can press play in Xcode, or re-build on the command line: @@ -127,10 +127,10 @@ react-native run-ios To test the URI on the simulator, run the following: ``` -xcrun simctl openurl booted mychat://chat/Eric +xcrun simctl openurl booted example://chat/Eric ``` -To test the URI on a real device, open Safari and type `mychat://chat/Eric`. +To test the URI on a real device, open Safari and type `example://chat/Eric`. ### Android @@ -153,7 +153,7 @@ In `SimpleApp/android/app/src/main/AndroidManifest.xml`, do these followings adj - + ``` @@ -167,7 +167,7 @@ react-native run-android To test the intent handling in Android, run the following: ``` -adb shell am start -W -a android.intent.action.VIEW -d "mychat://chat/Eric" com.simpleapp +adb shell am start -W -a android.intent.action.VIEW -d "example://chat/Eric" com.simpleapp ``` ## Disable deep linking diff --git a/versioned_docs/version-3.x/deep-linking.md b/versioned_docs/version-3.x/deep-linking.md index 7adc9312c4..cce7c6f89b 100644 --- a/versioned_docs/version-3.x/deep-linking.md +++ b/versioned_docs/version-3.x/deep-linking.md @@ -4,7 +4,7 @@ title: Deep linking sidebar_label: Deep linking --- -In this guide we will set up our app to handle external URIs. Let's suppose that we want a URI like `mychat://chat/Eric` to open our app and link straight into a chat screen for some user named "Eric". +In this guide we will set up our app to handle external URIs. Let's suppose that we want a URI like `example://chat/Eric` to open our app and link straight into a chat screen for some user named "Eric". ## Configuration @@ -71,7 +71,7 @@ You need to specify a scheme for your app. You can register for a scheme in your ```json { "expo": { - "scheme": "mychat" + "scheme": "example" } } ``` @@ -125,14 +125,14 @@ Next, let's configure our navigation container to extract the path from the app' ```js const SimpleApp = createAppContainer(createStackNavigator({...})); -const prefix = 'mychat://'; +const prefix = 'example://'; const MainApp = () => ; ``` ### iOS -Let's configure the native iOS app to open based on the `mychat://` URI scheme. +Let's configure the native iOS app to open based on the `example://` URI scheme. In `SimpleApp/ios/SimpleApp/AppDelegate.m`: @@ -151,7 +151,7 @@ In `SimpleApp/ios/SimpleApp/AppDelegate.m`: In Xcode, open the project at `SimpleApp/ios/SimpleApp.xcodeproj`. Select the project in sidebar and navigate to the info tab. Scroll down to "URL Types" and add one. In the new URL type, set the identifier and the URL scheme to your desired URL scheme. -![Xcode project info URL types with mychat added](/assets/deep-linking/xcode-linking.png) +![Xcode project info URL types with example added](/assets/deep-linking/xcode-linking.png) Now you can press play in Xcode, or re-build on the command line: @@ -162,10 +162,10 @@ react-native run-ios To test the URI on the simulator, run the following: ``` -xcrun simctl openurl booted mychat://chat/Eric +xcrun simctl openurl booted example://chat/Eric ``` -To test the URI on a real device, open Safari and type `mychat://chat/Eric`. +To test the URI on a real device, open Safari and type `example://chat/Eric`. ### Android @@ -188,7 +188,7 @@ In `SimpleApp/android/app/src/main/AndroidManifest.xml`, do these followings adj - + ``` @@ -202,7 +202,7 @@ react-native run-android To test the intent handling in Android, run the following: ``` -adb shell am start -W -a android.intent.action.VIEW -d "mychat://chat/Eric" com.simpleapp +adb shell am start -W -a android.intent.action.VIEW -d "example://chat/Eric" com.simpleapp ``` ## Disable deep linking diff --git a/versioned_docs/version-4.x/deep-linking.md b/versioned_docs/version-4.x/deep-linking.md index 60536603b4..4576a34ca3 100644 --- a/versioned_docs/version-4.x/deep-linking.md +++ b/versioned_docs/version-4.x/deep-linking.md @@ -4,7 +4,7 @@ title: Deep linking sidebar_label: Deep linking --- -In this guide we will set up our app to handle external URIs. Let's suppose that we want a URI like `mychat://chat/Eric` to open our app and link straight into a chat screen for some user named "Eric". +In this guide we will set up our app to handle external URIs. Let's suppose that we want a URI like `example://chat/Eric` to open our app and link straight into a chat screen for some user named "Eric". ## Configuration @@ -66,12 +66,12 @@ const FriendsScreen = createStackNavigator({ ## Set up with Expo projects -First, you will want to specify a URL scheme for your app. This corresponds to the string before `://` in a URL, so if your scheme is `mychat` then a link to your app would be `mychat://`. The scheme only applies to standalone apps and you need to re-build the standalone app for the change to take effect. In the Expo client app you can deep link using `exp://ADDRESS:PORT` where `ADDRESS` is often `127.0.0.1` and `PORT` is often `19000` - the URL is printed when you run `expo start`. If you want to test with your custom scheme you will need to run `expo build:ios -t simulator` or `expo build:android` and install the resulting binaries in your emulators. You can register for a scheme in your `app.json` by adding a string under the scheme key: +First, you will want to specify a URL scheme for your app. This corresponds to the string before `://` in a URL, so if your scheme is `example` then a link to your app would be `example://`. The scheme only applies to standalone apps and you need to re-build the standalone app for the change to take effect. In the Expo client app you can deep link using `exp://ADDRESS:PORT` where `ADDRESS` is often `127.0.0.1` and `PORT` is often `19000` - the URL is printed when you run `expo start`. If you want to test with your custom scheme you will need to run `expo build:ios -t simulator` or `expo build:android` and install the resulting binaries in your emulators. You can register for a scheme in your `app.json` by adding a string under the scheme key: ```json { "expo": { - "scheme": "mychat" + "scheme": "example" } } ``` @@ -134,14 +134,14 @@ Next, let's configure our navigation container to extract the path from the app' ```js const SimpleApp = createAppContainer(createStackNavigator({...})); -const prefix = 'mychat://'; +const prefix = 'example://'; const MainApp = () => ; ``` ### iOS -Let's configure the native iOS app to open based on the `mychat://` URI scheme. +Let's configure the native iOS app to open based on the `example://` URI scheme. In `SimpleApp/ios/SimpleApp/AppDelegate.m`: @@ -159,7 +159,7 @@ In `SimpleApp/ios/SimpleApp/AppDelegate.m`: In Xcode, open the project at `SimpleApp/ios/SimpleApp.xcodeproj`. Select the project in sidebar and navigate to the info tab. Scroll down to "URL Types" and add one. In the new URL type, set the identifier and the URL scheme to your desired URL scheme. -![Xcode project info URL types with mychat added](/assets/deep-linking/xcode-linking.png) +![Xcode project info URL types with example added](/assets/deep-linking/xcode-linking.png) Now you can press play in Xcode, or re-build on the command line: @@ -170,10 +170,10 @@ react-native run-ios To test the URI on the simulator, run the following: ```bash -xcrun simctl openurl booted mychat://chat/Eric +xcrun simctl openurl booted example://chat/Eric ``` -To test the URI on a real device, open Safari and type `mychat://chat/Eric`. +To test the URI on a real device, open Safari and type `example://chat/Eric`. ### Android @@ -196,7 +196,7 @@ In `SimpleApp/android/app/src/main/AndroidManifest.xml`, do these followings adj - + ``` @@ -210,7 +210,7 @@ react-native run-android To test the intent handling in Android, run the following: ```bash -adb shell am start -W -a android.intent.action.VIEW -d "mychat://chat/Eric" com.simpleapp +adb shell am start -W -a android.intent.action.VIEW -d "example://chat/Eric" com.simpleapp ``` ## Disable deep linking diff --git a/versioned_docs/version-5.x/configuring-links.md b/versioned_docs/version-5.x/configuring-links.md index 4542bece96..01ed72fd7b 100644 --- a/versioned_docs/version-5.x/configuring-links.md +++ b/versioned_docs/version-5.x/configuring-links.md @@ -106,7 +106,7 @@ const config = { }; const linking = { - prefixes: ['https://mychat.com/', 'mychat://'], + prefixes: ['https://example.com/', 'example://'], config, }; @@ -616,7 +616,7 @@ Example: ```js const linking = { - prefixes: ['https://mychat.com/', 'mychat://'], + prefixes: ['https://example.com/', 'example://'], config: { screens: { Chat: 'feed/:sort', diff --git a/versioned_docs/version-5.x/deep-linking.md b/versioned_docs/version-5.x/deep-linking.md index 3bbc811567..bbc447119a 100755 --- a/versioned_docs/version-5.x/deep-linking.md +++ b/versioned_docs/version-5.x/deep-linking.md @@ -15,12 +15,12 @@ Below, we'll go through required configurations for each platform so that the de ## Set up with Expo projects -First, you will want to specify a URL scheme for your app. This corresponds to the string before `://` in a URL, so if your scheme is `mychat` then a link to your app would be `mychat://`. The scheme only applies to standalone apps and you need to re-build the standalone app for the change to take effect. In the Expo client app you can deep link using `exp://ADDRESS:PORT` where `ADDRESS` is often `127.0.0.1` and `PORT` is often `19000` - the URL is printed when you run `expo start`. If you want to test with your custom scheme you will need to run `expo build:ios -t simulator` or `expo build:android` and install the resulting binaries in your emulators. You can register for a scheme in your `app.json` by adding a string under the scheme key: +First, you will want to specify a URL scheme for your app. This corresponds to the string before `://` in a URL, so if your scheme is `example` then a link to your app would be `example://`. The scheme only applies to standalone apps and you need to re-build the standalone app for the change to take effect. In the Expo client app you can deep link using `exp://ADDRESS:PORT` where `ADDRESS` is often `127.0.0.1` and `PORT` is often `19000` - the URL is printed when you run `expo start`. If you want to test with your custom scheme you will need to run `expo build:ios -t simulator` or `expo build:android` and install the resulting binaries in your emulators. You can register for a scheme in your `app.json` by adding a string under the scheme key: ```json { "expo": { - "scheme": "mychat" + "scheme": "example" } } ``` @@ -143,7 +143,7 @@ Read the [Expo linking guide](https://docs.expo.io/versions/latest/guides/linkin ### iOS -Let's configure the native iOS app to open based on the `mychat://` URI scheme. +Let's configure the native iOS app to open based on the `example://` URI scheme. You'll need to link `RCTLinking` to your project by following the steps described here. To be able to listen to incoming app links, you'll need to add the following lines to `SimpleApp/ios/SimpleApp/AppDelegate.m`. @@ -192,11 +192,11 @@ If your app is using Universal Links, you'll need to add the following code as w Now you need to add the scheme to your project configuration. -The easiest way to do this is with the `uri-scheme` package: `npx uri-scheme add mychat --ios`. +The easiest way to do this is with the `uri-scheme` package: `npx uri-scheme add example --ios`. If you want to do it manually, open the project at `SimpleApp/ios/SimpleApp.xcodeproj` in Xcode. Select the project in sidebar and navigate to the info tab. Scroll down to "URL Types" and add one. In the new URL type, set the identifier and the URL scheme to your desired URL scheme. -![Xcode project info URL types with mychat added](/assets/deep-linking/xcode-linking.png) +![Xcode project info URL types with example added](/assets/deep-linking/xcode-linking.png) Now you can press play in Xcode, or re-build on the command line: @@ -207,22 +207,22 @@ npx react-native run-ios To test the URI on the simulator, run the following: ```bash -npx uri-scheme open mychat://chat/jane --ios +npx uri-scheme open example://chat/jane --ios ``` or use `xcrun` directly: ```bash -xcrun simctl openurl booted mychat://chat/jane +xcrun simctl openurl booted example://chat/jane ``` -To test the URI on a real device, open Safari and type `mychat://chat/jane`. +To test the URI on a real device, open Safari and type `example://chat/jane`. ### Android To configure the external linking in Android, you can create a new intent in the manifest. -The easiest way to do this is with the `uri-scheme` package: `npx uri-scheme add mychat --android`. +The easiest way to do this is with the `uri-scheme` package: `npx uri-scheme add example --android`. If you want to add it manually, open up `SimpleApp/android/app/src/main/AndroidManifest.xml`, and make the following adjustments: @@ -241,7 +241,7 @@ If you want to add it manually, open up `SimpleApp/android/app/src/main/AndroidM - + ``` @@ -255,13 +255,13 @@ react-native run-android To test the intent handling in Android, run the following: ```bash -npx uri-scheme open mychat://chat/jane --android +npx uri-scheme open example://chat/jane --android ``` or use `adb` directly: ```bash -adb shell am start -W -a android.intent.action.VIEW -d "mychat://chat/jane" com.simpleapp +adb shell am start -W -a android.intent.action.VIEW -d "example://chat/jane" com.simpleapp ``` ## Hybrid React Native and native iOS Applications (skip for React-Native-only projects) diff --git a/versioned_docs/version-5.x/navigation-container.md b/versioned_docs/version-5.x/navigation-container.md index d58bef4935..885477f1d1 100644 --- a/versioned_docs/version-5.x/navigation-container.md +++ b/versioned_docs/version-5.x/navigation-container.md @@ -199,7 +199,7 @@ import { NavigationContainer } from '@react-navigation/native'; function App() { const linking = { - prefixes: ['https://mychat.com/', 'mychat://'], + prefixes: ['https://example.com/', 'example://'], config: { screens: { Home: 'feed/:sort', @@ -230,7 +230,7 @@ Example: ```js !url.includes('+expo-auth-session'), }; ``` @@ -149,7 +149,7 @@ const config = { }; const linking = { - prefixes: ['https://mychat.com/', 'mychat://'], + prefixes: ['https://example.com/', 'example://'], config, }; @@ -679,7 +679,7 @@ Example: ```js const linking = { - prefixes: ['https://mychat.com/', 'mychat://'], + prefixes: ['https://example.com/', 'example://'], config: { screens: { Chat: 'feed/:sort', diff --git a/versioned_docs/version-6.x/deep-linking.md b/versioned_docs/version-6.x/deep-linking.md index ef0f3ee0e7..e112f3f7f8 100755 --- a/versioned_docs/version-6.x/deep-linking.md +++ b/versioned_docs/version-6.x/deep-linking.md @@ -17,12 +17,12 @@ Below, we'll go through required configurations so that the deep link integratio ## Setup with Expo projects -First, you will want to specify a URL scheme for your app. This corresponds to the string before `://` in a URL, so if your scheme is `mychat` then a link to your app would be `mychat://`. You can register for a scheme in your `app.json` by adding a string under the scheme key: +First, you will want to specify a URL scheme for your app. This corresponds to the string before `://` in a URL, so if your scheme is `example` then a link to your app would be `example://`. You can register for a scheme in your `app.json` by adding a string under the scheme key: ```json { "expo": { - "scheme": "mychat" + "scheme": "example" } } ``` @@ -69,7 +69,7 @@ const linking = { ### Setup on iOS -Let's configure the native iOS app to open based on the `mychat://` URI scheme. +Let's configure the native iOS app to open based on the `example://` URI scheme. You'll need to link `RCTLinking` to your project by following the steps described here. To be able to listen to incoming app links, you'll need to add the following lines to `AppDelegate.m` in your project: @@ -104,12 +104,12 @@ Now you need to add the scheme to your project configuration. The easiest way to do this is with the `uri-scheme` package by running the following: ```bash -npx uri-scheme add mychat --ios +npx uri-scheme add example --ios ``` If you want to do it manually, open the project (e.g. `SimpleApp/ios/SimpleApp.xcworkspace`) in Xcode. Select the project in sidebar and navigate to the info tab. Scroll down to "URL Types" and add one. In the new URL type, set the identifier and the URL scheme to your desired URL scheme. -![Xcode project info URL types with mychat added](/assets/deep-linking/xcode-linking.png) +![Xcode project info URL types with example added](/assets/deep-linking/xcode-linking.png) To make sure Universal Links work in your app, you also need to setup [Associated Domains](https://developer.apple.com/documentation/Xcode/supporting-associated-domains) on your server. @@ -129,7 +129,7 @@ If you're using React Navigation within a hybrid app - an iOS app that has both To configure the external linking in Android, you can create a new intent in the manifest. -The easiest way to do this is with the `uri-scheme` package: `npx uri-scheme add mychat --android`. +The easiest way to do this is with the `uri-scheme` package: `npx uri-scheme add example --android`. If you want to add it manually, open up `SimpleApp/android/app/src/main/AndroidManifest.xml`, and make the following adjustments: @@ -148,7 +148,7 @@ If you want to add it manually, open up `SimpleApp/android/app/src/main/AndroidM - + ``` @@ -172,7 +172,7 @@ After adding them, it should look like this: - + @@ -218,7 +218,7 @@ npx uri-scheme open [your deep link] --[ios|android] For example: ```bash -npx uri-scheme open "mychat://chat/jane" --ios +npx uri-scheme open "example://chat/jane" --ios ``` Or if using Expo client: @@ -238,7 +238,7 @@ xcrun simctl openurl booted [your deep link] For example: ```bash -xcrun simctl openurl booted "mychat://chat/jane" +xcrun simctl openurl booted "example://chat/jane" ``` ### Testing with `adb` on Android @@ -252,7 +252,7 @@ adb shell am start -W -a android.intent.action.VIEW -d [your deep link] [your an For example: ```bash -adb shell am start -W -a android.intent.action.VIEW -d "mychat://chat/jane" com.simpleapp +adb shell am start -W -a android.intent.action.VIEW -d "example://chat/jane" com.simpleapp ``` Or if using Expo client: diff --git a/versioned_docs/version-6.x/navigation-container.md b/versioned_docs/version-6.x/navigation-container.md index f5708d8a60..5d48860e80 100644 --- a/versioned_docs/version-6.x/navigation-container.md +++ b/versioned_docs/version-6.x/navigation-container.md @@ -237,7 +237,7 @@ import { NavigationContainer } from '@react-navigation/native'; function App() { const linking = { - prefixes: ['https://mychat.com/', 'mychat://'], + prefixes: ['https://example.com/', 'example://'], config: { screens: { Home: 'feed/:sort', @@ -268,7 +268,7 @@ Example: ```js -To do this, we need a couple of things: - -1. Define two hooks: `useIsSignedIn` and `useIsSignedOut`, which return a boolean value indicating whether the user is signed in or not. -2. Use the `useIsSignedIn` and `useIsSignedOut` along with the [`if`](static-configuration.md#if) property to define the screens that are available based on the condition. - -This tells React Navigation to show specific screens based on the signed in status. When the signed in status changes, React Navigation will automatically show the appropriate screen. - -## Define the hooks - -To implement the `useIsSignedIn` and `useIsSignedOut` hooks, we can start by creating a context to store the authentication state. Let's call it `SignInContext`: - -```js -import * as React from 'react'; - -const SignInContext = React.createContext(); -``` - -Then we can implement the `useIsSignedIn` and `useIsSignedOut` hooks as follows: - -```js -function useIsSignedIn() { - const isSignedIn = React.useContext(SignInContext); - return isSignedIn; -} - -function useIsSignedOut() { - const isSignedIn = React.useContext(SignInContext); - return !isSignedIn; -} -``` - -We'll discuss how to expose the context value later. - -```js name="Customizing tabs appearance" snack +```js name="Authentication flow" snack import * as React from 'react'; import { View } from 'react-native'; import { createStaticNavigation } from '@react-navigation/native'; @@ -79,40 +46,19 @@ const useIsSignedIn = () => { }; const useIsSignedOut = () => { - return false; + return !useIsSignedIn(); }; -const signedInStack = createNativeStackNavigator({ - screens: { - Home: HomeScreen, - Profile: ProfileScreen, - Settings: SettingsScreen, - }, -}); - -const signedOutStack = createNativeStackNavigator({ - screens: { - SignIn: SignInScreen, - SignUp: SignUpScreen, - }, -}); - // codeblock-focus-start const RootStack = createNativeStackNavigator({ screens: { - LoggedIn: { + Home: { if: useIsSignedIn, - screen: signedInStack, - options: { - headerShown: false, - }, + screen: HomeScreen, }, - LoggedOut: { + SignIn: { if: useIsSignedOut, - screen: signedOutStack, - options: { - headerShown: false, - }, + screen: SignInScreen, }, }, }); @@ -128,29 +74,59 @@ function HomeScreen() { return ; } -function ProfileScreen() { +function SignInScreen() { return ; } +``` -function SettingsScreen() { - return ; -} +Here, for each screen, we have defined a condition using the `if` property which takes a hook. The hook returns a boolean value indicating whether the user is signed in or not. If the hook returns `true`, the screen will be available, otherwise it won't. -function SignInScreen() { - return ; +This means: + +- When `useIsSignedIn` returns `true`, React Navigation will only use the `Home` screen, since it's the only screen matching the condition. +- Similarly, when `useIsSignedOut` returns `true`, React Navigation will use the `SignIn` screen. + +This makes it impossible to navigate to the `Home` when the user is not signed in, and to `SignIn` when the user is signed in. + +When the values returned by `useIsSignedin` and `useIsSignedOut` change, the screens matching the condition will change: + +- Let's say, initially `useIsSignedOut` returns `true`. This means that `SignIn` screens is shown. +- After the user signs in, the return value of `useIsSignedIn` will change to `true` and `useIsSignedOut` will change to `false`, which means: + - React Navigation will see that the `SignIn` screen is no longer matches the condition, so it will remove the screen. + - Then it'll show the `Home` screen automatically because that's the first screen available when `useIsSignedIn` returns `true`. + +The order of the screens matters when there are multiple screens matching the condition. For example, if there are two screens matching `useIsSignedIn`, the first screen will be shown when the condition is `true`. + +## Define the hooks + +To implement the `useIsSignedIn` and `useIsSignedOut` hooks, we can start by creating a context to store the authentication state. Let's call it `SignInContext`: + +```js +import * as React from 'react'; + +const SignInContext = React.createContext(); +``` + +Then we can implement the `useIsSignedIn` and `useIsSignedOut` hooks as follows: + +```js +function useIsSignedIn() { + const isSignedIn = React.useContext(SignInContext); + return isSignedIn; } -function SignUpScreen() { - return ; +function useIsSignedOut() { + return !useIsSignedIn(); } ``` +We'll discuss how to provide the context value later. + - -For example: + -```js name="Customizing tabs appearance" snack +```js name="Authentication flow" snack import * as React from 'react'; import { View } from 'react-native'; import { NavigationContainer } from '@react-navigation/native'; @@ -158,29 +134,17 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack'; const Stack = createNativeStackNavigator(); -const getIsSignedIn = () => { - // custom logic - return true; -}; - export default function App() { - const isSignedIn = getIsSignedIn(); + const isSignedIn = true; return ( // codeblock-focus-start {isSignedIn ? ( - <> - - - - + ) : ( - <> - - - + )} // codeblock-focus-end @@ -192,39 +156,35 @@ function HomeScreen() { return ; } -function ProfileScreen() { - return ; -} - -function SettingsScreen() { - return ; -} - function SignInScreen() { return ; } - -function SignUpScreen() { - return ; -} ``` -When we define screens like this, when `isSignedIn` is `true`, React Navigation will only see the `Home`, `Profile` and `Settings` screens, and when it's `false`, React Navigation will see the `SignIn` and `SignUp` screens. This makes it impossible to navigate to the `Home`, `Profile` and `Settings` screens when the user is not signed in, and to `SignIn` and `SignUp` screens when the user is signed in. +Here, we have conditionally defined the screens based on the value of `isSignedIn`. -This pattern has been in use by other routing libraries such as React Router for a long time, and is commonly known as "Protected routes". Here, our screens which need the user to be signed in are "protected" and cannot be navigated to by other means if the user is not signed in. +This means: -The magic happens when the value of the `isSignedIn` variable changes. Let's say, initially `isSignedIn` is `false`. This means, either `SignIn` or `SignUp` screens are shown. After the user signs in, the value of `isSignedIn` will change to `true`. React Navigation will see that the `SignIn` and `SignUp` screens are no longer defined and so it will remove them. Then it'll show the `Home` screen automatically because that's the first screen defined when `isSignedIn` is `true`. +- When `isSignedIn` is `true`, React Navigation will only see the `Home` screen, since it's the only screen defined based on the condition. +- Similarly, when `isSignedIn` is `false`, React Navigation will only see the `SignIn` screen. -The example shows stack navigator, but you can use the same approach with any navigator. +This makes it impossible to navigate to the `Home` when the user is not signed in, and to `SignIn` when the user is signed in. -By conditionally defining different screens based on a variable, we can implement auth flow in a simple way that doesn't require additional logic to make sure that the correct screen is shown. +When the value of `isSignedin` changes, the screens defined based on the condition will change: + +- Let's say, initially `isSignedin` is `false`. This means that `SignIn` screens is shown. +- After the user signs in, the value of `isSignedin` will change to `true`, which means: + - React Navigation will see that the `SignIn` screen is no longer defined, so it will remove the screen. + - Then it'll show the `Home` screen automatically because that's the first screen defined when `isSignedin` returns `true`. + +The order of the screens matters when there are multiple screens matching the condition. For example, if there are two screens defined based on `isSignedin`, the first screen will be shown when the condition is `true`. -## Define our screens +## Add more screens -In our navigator, we can conditionally define appropriate screens. For our case, let's say we have 3 screens: +For our case, let's say we have 3 screens: - `SplashScreen` - This will show a splash or loading screen when we're restoring the token. - `SignIn` - This is the screen we show if the user isn't signed in already (we couldn't find a token). @@ -255,10 +215,46 @@ const RootStack = createNativeStackNavigator({ const Navigation = createStaticNavigation(RootStack); ``` + + + +```js +const Stack = createNativeStackNavigator(); + +export default function App() { + const isSignedIn = true; + + return ( + + + {isSignedIn ? ( + + ) : ( + + )} + + + ); +} +``` + + + + Notice how we have only defined the `Home` and `SignIn` screens here, and not the `SplashScreen`. The `SplashScreen` should be rendered before we render any navigators so that we don't render incorrect screens before we know whether the user is signed in or not. When we use this in our component, it'd look something like this: + + + ```js if (isLoading) { // We haven't finished checking for the token yet @@ -274,43 +270,6 @@ return ( ); ``` -In the above snippet, `isLoading` means that we're still checking if we have a token. This can usually be done by checking if we have a token in `SecureStore` and validating the token. After we get the token and if it's valid, we need to set the `userToken`. We also have another state called `isSignout` to have a different animation on sign out. - -Next, we're exposing the sign in status via the `SignInContext` so that it's available to the `useIsSignedIn` and `useIsSignedOut` hooks. - -In the above example, we have one screen for each case. But you could also define multiple screens. For example, you probably want to define password reset, signup, etc screens as well when the user isn't signed in. Similarly for the screens accessible after sign in, you probably have more than one screen. We can use [`groups`](static-configuration.md#groups) to define multiple screens: - -```js -const RootStack = createNativeStackNavigator({ - screens: { - // Common screens - }, - groups: { - SignedIn: { - if: useIsSignedIn, - screens: { - Home: HomeScreen, - Profile: ProfileScreen, - }, - }, - SignedOut: { - if: useIsSignedOut, - screens: { - SignIn: SignInScreen, - SignUp: SignUpScreen, - ResetPassword: ResetPasswordScreen, - }, - }, - }, -}); -``` - -:::tip - -If you have both your login-related screens and rest of the screens in Stack navigators, we recommend to use a single Stack navigator and place the conditional inside instead of using 2 different navigators. This makes it possible to have a proper transition animation during login/logout. - -::: - @@ -320,11 +279,12 @@ if (isLoading) { return ; } +const isSignedIn = userToken != null; + return ( - {userToken == null ? ( - // No token found, user isn't signed in + {isSignedIn ? ( ) : ( - // User is signed in )} @@ -345,51 +304,49 @@ return ( -In the above snippet, `isLoading` means that we're still checking if we have a token. This can usually be done by checking if we have a token in `SecureStore` and validating the token. After we get the token and if it's valid, we need to set the `userToken`. We also have another state called `isSignout` to have a different animation on sign out. - -The main thing to notice is that we're conditionally defining screens based on these state variables: +In the above snippet, `isLoading` means that we're still checking if we have a token. This can usually be done by checking if we have a token in `SecureStore` and validating the token. -- `SignIn` screen is only defined if `userToken` is `null` (user is not signed in) -- `Home` screen is only defined if `userToken` is non-null (user is signed in) +Next, we're exposing the sign in status via the `SignInContext` so that it's available to the `useIsSignedIn` and `useIsSignedOut` hooks. -Here, we're conditionally defining one screen for each case. But you could also define multiple screens. For example, you probably want to define password reset, signup, etc screens as well when the user isn't signed in. Similarly, for the screens accessible after signing in, you probably have more than one screen. We can use `React.Fragment` to define multiple screens: +In the above example, we have one screen for each case. But you could also define multiple screens. For example, you probably want to define password reset, signup, etc screens as well when the user isn't signed in. Similarly for the screens accessible after sign in, you probably have more than one screen. -```js -const SignInContext = React.createContext(); - -function useIsSignedIn() { - const isSignedIn = React.useContext(SignInContext); - return isSignedIn; -} - -function useIsSignedOut() { - const isSignedIn = React.useContext(SignInContext); - return !isSignedIn; -} - -/* content */ - -export default function App() { - /* content */ +We can use [`groups`](static-configuration.md#groups) to define multiple screens: - const isSignedIn = userToken != null; - - return ( - - - - ); -} +```js +const RootStack = createNativeStackNavigator({ + screens: { + // Common screens + }, + groups: { + SignedIn: { + if: useIsSignedIn, + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, + }, + SignedOut: { + if: useIsSignedOut, + screens: { + SignIn: SignInScreen, + SignUp: SignUpScreen, + ResetPassword: ResetPasswordScreen, + }, + }, + }, +}); ``` +We can use [`React.Fragment`](https://react.dev/reference/react/Fragment) or [`Group`](group.md) to define multiple screens: + ```js -state.userToken == null ? ( +isSignedIn ? ( <> @@ -405,7 +362,7 @@ state.userToken == null ? ( :::tip -If you have both your login-related screens and rest of the screens in two different Stack navigators, we recommend to use a single Stack navigator and place the conditional inside instead of using 2 different navigators. This makes it possible to have a proper transition animation during login/logout. +Instead of having your login-related screens and rest of the screens in two different Stack navigators and render them conditionally, we recommend to use a single Stack navigator and place the conditional inside. This makes it possible to have a proper transition animation during login/logout. ::: @@ -422,8 +379,8 @@ The following is just an example of how you might implement the logic for authen From the previous snippet, we can see that we need 3 state variables: -- `isLoading` - We set this to `true` when we're trying to check if we already have a token saved in `SecureStore` -- `isSignout` - We set this to `true` when user is signing out, otherwise set it to `false` +- `isLoading` - We set this to `true` when we're trying to check if we already have a token saved in `SecureStore`. +- `isSignout` - We set this to `true` when user is signing out, otherwise set it to `false`. This can be used to customize the animation when signing out. - `userToken` - The token for the user. If it's non-null, we assume the user is logged in, otherwise not. So we need to: @@ -472,8 +429,7 @@ function useIsSignedIn() { } function useIsSignedOut() { - const isSignedIn = React.useContext(SignInContext); - return !isSignedIn; + return !useIsSignedIn(); } function SplashScreen() { @@ -619,11 +575,11 @@ const RootStack = createNativeStackNavigator({ screen: HomeScreen, }, SignIn: { + if: useIsSignedOut, screen: SignInScreen, options: { title: 'Sign in', }, - if: useIsSignedOut, }, }, }); @@ -975,6 +931,10 @@ If you have a bunch of shared screens, you can also use [`navigationKey` with a +The examples above show stack navigator, but you can use the same approach with any navigator. + +By specifying a condition for our screens, we can implement auth flow in a simple way that doesn't require additional logic to make sure that the correct screen is shown. + ## Don't manually navigate when conditionally rendering screens It's important to note that when using such a setup, you **don't manually navigate** to the `Home` screen by calling `navigation.navigate('Home')` or any other method. **React Navigation will automatically navigate to the correct screen** when `isSignedIn` changes - `Home` screen when `isSignedIn` becomes `true`, and to `SignIn` screen when `isSignedIn` becomes `false`. You'll get an error if you attempt to navigate manually. diff --git a/versioned_docs/version-7.x/combine-static-with-dynamic.md b/versioned_docs/version-7.x/combine-static-with-dynamic.md index 6b9c738e35..bfb8c80b61 100644 --- a/versioned_docs/version-7.x/combine-static-with-dynamic.md +++ b/versioned_docs/version-7.x/combine-static-with-dynamic.md @@ -198,7 +198,7 @@ import { createPathConfigForStaticNavigation } from '@react-navigation/native'; const feedScreens = createPathConfigForStaticNavigation(FeedTabs); const linking = { - prefixes: ['https://mychat.com/', 'mychat://'], + prefixes: ['https://example.com/', 'example://'], config: { screens: { Home: '', diff --git a/versioned_docs/version-7.x/configuring-links.md b/versioned_docs/version-7.x/configuring-links.md index 1f68858f90..7b98cbee23 100644 --- a/versioned_docs/version-7.x/configuring-links.md +++ b/versioned_docs/version-7.x/configuring-links.md @@ -92,13 +92,13 @@ You can also pass a [`fallback`](navigation-container.md#fallback) prop that con ## Prefixes -The `prefixes` option can be used to specify custom schemes (e.g. `mychat://`) as well as host & domain names (e.g. `https://mychat.com`) if you have configured [Universal Links](https://developer.apple.com/ios/universal-links/) or [Android App Links](https://developer.android.com/training/app-links). +The `prefixes` option can be used to specify custom schemes (e.g. `example://`) as well as host & domain names (e.g. `https://example.com`) if you have configured [Universal Links](https://developer.apple.com/ios/universal-links/) or [Android App Links](https://developer.android.com/training/app-links). For example: ```js const linking = { - prefixes: ['mychat://', 'https://mychat.com/'], + prefixes: ['example://', 'https://example.com/'], }; ``` @@ -106,11 +106,11 @@ Note that the `prefixes` option is not supported on Web. The host & domain names ### Multiple subdomains​ -To match all subdomains of an associated domain, you can specify a wildcard by prefixing `*`. before the beginning of a specific domain. Note that an entry for `*.mychat.com` does not match `mychat.com` because of the period after the asterisk. To enable matching for both `*.mychat.com` and `mychat.com`, you need to provide a separate prefix entry for each. +To match all subdomains of an associated domain, you can specify a wildcard by prefixing `*`. before the beginning of a specific domain. Note that an entry for `*.example.com` does not match `example.com` because of the period after the asterisk. To enable matching for both `*.example.com` and `example.com`, you need to provide a separate prefix entry for each. ```js const linking = { - prefixes: ['mychat://', 'https://mychat.com/', 'https://*.mychat.com/'], + prefixes: ['example://', 'https://example.com/', 'https://*.example.com/'], }; ``` @@ -122,7 +122,7 @@ To achieve this, you can use the `filter` option: ```js const linking = { - prefixes: ['mychat://', 'https://mychat.com/'], + prefixes: ['example://', 'https://example.com/'], // highlight-next-line filter: (url) => !url.includes('+expo-auth-session'), }; @@ -132,11 +132,11 @@ This is not supported on Web as we always need to handle the URL of the page. ## Apps under subpaths -If your app is hosted under a subpath, you can specify the subpath at the top-level of the `config`. For example, if your app is hosted at `https://mychat.com/app`, you can specify the `path` as `app`: +If your app is hosted under a subpath, you can specify the subpath at the top-level of the `config`. For example, if your app is hosted at `https://example.com/app`, you can specify the `path` as `app`: ```js const linking = { - prefixes: ['mychat://', 'https://mychat.com/'], + prefixes: ['example://', 'https://example.com/'], config: { // highlight-next-line path: 'app', @@ -322,7 +322,7 @@ const config = { }; const linking = { - prefixes: ['https://mychat.com/', 'mychat://'], + prefixes: ['https://example.com/', 'example://'], config, }; @@ -1357,7 +1357,7 @@ Example: ```js const linking = { - prefixes: ['https://mychat.com/', 'mychat://'], + prefixes: ['https://example.com/', 'example://'], getStateFromPath: (path, options) => { // Return a state object here // You can also reuse the default logic by importing `getStateFromPath` from `@react-navigation/native` diff --git a/versioned_docs/version-7.x/custom-navigators.md b/versioned_docs/version-7.x/custom-navigators.md index c8567fa848..4e1e9cbdb5 100755 --- a/versioned_docs/version-7.x/custom-navigators.md +++ b/versioned_docs/version-7.x/custom-navigators.md @@ -56,27 +56,9 @@ import { TabActions, } from '@react-navigation/native'; -function TabNavigator({ - id, - initialRouteName, - children, - layout, - screenListeners, - screenOptions, - screenLayout, - tabBarStyle, - contentStyle, -}) { +function TabNavigator({ tabBarStyle, contentStyle, ...rest }) { const { state, navigation, descriptors, NavigationContent } = - useNavigationBuilder(TabRouter, { - id, - initialRouteName, - children, - layout, - screenListeners, - screenOptions, - screenLayout, - }); + useNavigationBuilder(TabRouter, rest); return ( @@ -210,7 +192,7 @@ import { useNavigationBuilder, } from '@react-navigation/native'; -// Props accepted by the view +// Additional props accepted by the view type TabNavigationConfig = { tabBarStyle: StyleProp; contentStyle: StyleProp; @@ -222,7 +204,6 @@ type TabNavigationOptions = { }; // Map of event name and the type of data (in event.data) -// // canPreventDefault: true adds the defaultPrevented property to the // emitted events. type TabNavigationEventMap = { @@ -242,18 +223,7 @@ type Props = DefaultNavigatorOptions< TabRouterOptions & TabNavigationConfig; -function TabNavigator({ - id, - initialRouteName, - children, - layout, - screenListeners, - screenOptions, - screenLayout, - backBehavior, - tabBarStyle, - contentStyle, -}: Props) { +function TabNavigator({ tabBarStyle, contentStyle, ...rest }: Props) { const { state, navigation, descriptors, NavigationContent } = useNavigationBuilder< TabNavigationState, @@ -261,16 +231,7 @@ function TabNavigator({ TabActionHelpers, TabNavigationOptions, TabNavigationEventMap - >(TabRouter, { - id, - initialRouteName, - children, - layout, - screenListeners, - screenOptions, - screenLayout, - backBehavior, - }); + >(TabRouter, rest); return ( @@ -321,6 +282,7 @@ function TabNavigator({ ); } +// The factory function with generic types for type-inference export function createMyNavigator< const ParamList extends ParamListBase, const NavigatorID extends string | undefined = undefined, diff --git a/versioned_docs/version-7.x/deep-linking.md b/versioned_docs/version-7.x/deep-linking.md index 0ecb6de11c..6b6389861f 100755 --- a/versioned_docs/version-7.x/deep-linking.md +++ b/versioned_docs/version-7.x/deep-linking.md @@ -20,12 +20,12 @@ Below, we'll go through required configurations so that the deep link integratio ## Setup with Expo projects -First, you will want to specify a URL scheme for your app. This corresponds to the string before `://` in a URL, so if your scheme is `mychat` then a link to your app would be `mychat://`. You can register for a scheme in your `app.json` by adding a string under the scheme key: +First, you will want to specify a URL scheme for your app. This corresponds to the string before `://` in a URL, so if your scheme is `example` then a link to your app would be `example://`. You can register for a scheme in your `app.json` by adding a string under the scheme key: ```json { "expo": { - "scheme": "mychat" + "scheme": "example" } } ``` @@ -97,7 +97,7 @@ const linking = { ### Setup on iOS -Let's configure the native iOS app to open based on the `mychat://` URI scheme. +Let's configure the native iOS app to open based on the `example://` URI scheme. You'll need to link `RCTLinking` to your project by following the steps described here. To be able to listen to incoming app links, you'll need to add the following lines to `AppDelegate.m` in your project: @@ -132,12 +132,12 @@ Now you need to add the scheme to your project configuration. The easiest way to do this is with the `uri-scheme` package by running the following: ```bash -npx uri-scheme add mychat --ios +npx uri-scheme add example --ios ``` If you want to do it manually, open the project (e.g. `SimpleApp/ios/SimpleApp.xcworkspace`) in Xcode. Select the project in sidebar and navigate to the info tab. Scroll down to "URL Types" and add one. In the new URL type, set the identifier and the URL scheme to your desired URL scheme. -![Xcode project info URL types with mychat added](/assets/deep-linking/xcode-linking.png) +![Xcode project info URL types with example added](/assets/deep-linking/xcode-linking.png) To make sure Universal Links work in your app, you also need to setup [Associated Domains](https://developer.apple.com/documentation/Xcode/supporting-associated-domains) on your server. @@ -157,7 +157,7 @@ If you're using React Navigation within a hybrid app - an iOS app that has both To configure the external linking in Android, you can create a new intent in the manifest. -The easiest way to do this is with the `uri-scheme` package: `npx uri-scheme add mychat --android`. +The easiest way to do this is with the `uri-scheme` package: `npx uri-scheme add example --android`. If you want to add it manually, open up `SimpleApp/android/app/src/main/AndroidManifest.xml`, and make the following adjustments: @@ -176,7 +176,7 @@ If you want to add it manually, open up `SimpleApp/android/app/src/main/AndroidM - + ``` @@ -200,7 +200,7 @@ After adding them, it should look like this: - + @@ -246,7 +246,7 @@ npx uri-scheme open [your deep link] --[ios|android] For example: ```bash -npx uri-scheme open "mychat://chat/jane" --ios +npx uri-scheme open "example://chat/jane" --ios ``` Or if using Expo client: @@ -266,7 +266,7 @@ xcrun simctl openurl booted [your deep link] For example: ```bash -xcrun simctl openurl booted "mychat://chat/jane" +xcrun simctl openurl booted "example://chat/jane" ``` ### Testing with `adb` on Android @@ -280,7 +280,7 @@ adb shell am start -W -a android.intent.action.VIEW -d [your deep link] [your an For example: ```bash -adb shell am start -W -a android.intent.action.VIEW -d "mychat://chat/jane" com.simpleapp +adb shell am start -W -a android.intent.action.VIEW -d "example://chat/jane" com.simpleapp ``` Or if using Expo client: diff --git a/versioned_docs/version-7.x/getting-started.md b/versioned_docs/version-7.x/getting-started.md index 9e747fe04b..c4d614845c 100755 --- a/versioned_docs/version-7.x/getting-started.md +++ b/versioned_docs/version-7.x/getting-started.md @@ -15,10 +15,10 @@ If you're already familiar with JavaScript, React and React Native, then you'll Here are some resources to help you out: -1. [React Native](https://reactnative.dev/docs/getting-started) -2. [Main Concepts of React](https://react.dev/learn) -3. [React Hooks](https://react.dev/reference/react) -4. [React Context](https://react.dev/learn/passing-data-deeply-with-context) (Advanced) +1. [Main Concepts of React](https://react.dev/learn) +2. [Getting started with React Native](https://reactnative.dev/docs/getting-started) +3. [React Hooks](https://react.dev/reference/react/hooks) +4. [React Context](https://react.dev/learn/passing-data-deeply-with-context) ## Minimum requirements diff --git a/versioned_docs/version-7.x/native-stack-navigator.md b/versioned_docs/version-7.x/native-stack-navigator.md index 8ec238a587..57991096c0 100755 --- a/versioned_docs/version-7.x/native-stack-navigator.md +++ b/versioned_docs/version-7.x/native-stack-navigator.md @@ -174,7 +174,7 @@ This will have no effect on the first screen in the stack. #### `headerBackTitle` -Title string used by the back button on iOS. Defaults to the previous scene's title. +Title string used by the back button on iOS. Defaults to the previous scene's title, "Back" or arrow icon depending on the available space. See `headerBackButtonDisplayMode` to read about limitations and customize the behavior. Use `headerBackButtonDisplayMode: "minimal"` to hide it. @@ -195,7 +195,6 @@ Supported values: The space-aware behavior is disabled when: - The iOS version is 13 or lower -- Custom back title is set (e.g. with `headerBackTitle`) - Custom font family or size is set (e.g. with `headerBackTitleStyle`) - Back button menu is disabled (e.g. with `headerBackButtonMenuEnabled`) @@ -727,12 +726,26 @@ Only supported on Android and iOS. #### `statusBarBackgroundColor` +:::warning + +This option is deprecated and will be removed in a future release (for apps targeting Android SDK 35 or above edge-to-edge mode is enabled by default +and it is expected that the edge-to-edge will be enforced in future SDKs, see [here](https://developer.android.com/about/versions/15/behavior-changes-15#ux) for more information). + +::: + Sets the background color of the status bar (similar to the `StatusBar` component). Only supported on Android. #### `statusBarTranslucent` +:::warning + +This option is deprecated and will be removed in a future release (for apps targeting Android SDK 35 or above edge-to-edge mode is enabled by default +and it is expected that the edge-to-edge will be enforced in future SDKs, see [here](https://developer.android.com/about/versions/15/behavior-changes-15#ux) for more information). + +::: + Sets the translucency of the status bar (similar to the `StatusBar` component). Defaults to `false`. Only supported on Android. @@ -1261,6 +1274,13 @@ Only supported on iOS. #### `navigationBarColor` +:::warning + +This option is deprecated and will be removed in a future release (for apps targeting Android SDK 35 or above edge-to-edge mode is enabled by default +and it is expected that the edge-to-edge will be enforced in future SDKs, see [here](https://developer.android.com/about/versions/15/behavior-changes-15#ux) for more information). + +::: + Sets the navigation bar color. Defaults to initial status bar color. Only supported on Android. diff --git a/versioned_docs/version-7.x/navigation-container.md b/versioned_docs/version-7.x/navigation-container.md index 27c46df6ee..7fed8c5242 100644 --- a/versioned_docs/version-7.x/navigation-container.md +++ b/versioned_docs/version-7.x/navigation-container.md @@ -463,7 +463,7 @@ const Navigation = createStaticNavigation(RootStack); function App() { const linking = { - prefixes: ['https://mychat.com/', 'mychat://'], + prefixes: ['https://example.com/', 'example://'], }; return ( @@ -484,7 +484,7 @@ import { NavigationContainer } from '@react-navigation/native'; function App() { const linking = { - prefixes: ['https://mychat.com/', 'mychat://'], + prefixes: ['https://example.com/', 'example://'], config: { screens: { Home: 'feed/:sort', @@ -525,7 +525,7 @@ Example: Loading...} /> @@ -538,7 +538,7 @@ Example: listener(url); @@ -701,7 +701,7 @@ import messaging from '@react-native-firebase/messaging'; /jest/setup.js"] + "transformIgnorePatterns": [ + "node_modules/(?!(@react-native|react-native|@react-navigation)/)" + ], ++ "setupFilesAfterEnv": ["/jest/setup.js"] } ``` -Make sure that the path to the file in `setupFilesAfterEnv` is correct. Jest will run these files before running your tests, so it's the best place to put your global mocks. +Jest will run the files specified in `setupFilesAfterEnv` before running your tests, so it's a good place to put your global mocks.
Mocking `react-native-screens` @@ -761,3 +783,175 @@ In the above test, we: In a production app, we recommend using a library like [React Query](https://tanstack.com/query/) to handle data fetching and caching. The above example is for demonstration purposes only. ::: + +### Re-usable components + +To make it easier to test components that don't depend on the navigation structure, we can create a light-weight test navigator: + +```js title="TestStackNavigator.js" +import { useNavigationBuilder, StackRouter } from '@react-navigation/native'; + +function TestStackNavigator(props) { + const { state, descriptors, NavigationContent } = useNavigationBuilder( + StackRouter, + props + ); + + return ( + + {state.routes.map((route, index) => { + return ( + + {descriptors[route.key].render()} + + ); + })} + + ); +} + +export function createTestStackNavigator(config) { + return createNavigatorFactory(TestStackNavigator)(config); +} +``` + +This lets us test React Navigation specific logic such as `useFocusEffect` without needing to set up a full navigator. + +We can use this test navigator in our tests like this: + + + + +```js title="MyComponent.test.js" +import { act, render, screen } from '@testing-library/react-native'; +import { createStaticNavigation } from '@react-navigation/native'; +import { createTestStackNavigator } from './TestStackNavigator'; +import { MyComponent } from './MyComponent'; + +test('does not show modal when not focused', () => { + const TestStack = createTestStackNavigator({ + screens: { + A: MyComponent, + B: () => null, + }, + }); + + const Navigation = createStaticNavigation(TestStack); + + render( + + ); + + expect(screen.queryByText('Modal')).not.toBeVisible(); +}); + +test('shows modal when focused', () => { + const TestStack = createTestStackNavigator({ + screens: { + A: MyComponent, + B: () => null, + }, + }); + + const Navigation = createStaticNavigation(TestStack); + + render( + + ); + + expect(screen.getByText('Modal')).toBeVisible(); +}); +``` + + + + +```js title="MyComponent.test.js" +import { act, render, screen } from '@testing-library/react-native'; +import { NavigationContainer } from '@react-navigation/native'; +import { createTestStackNavigator } from './TestStackNavigator'; +import { MyComponent } from './MyComponent'; + +test('does not show modal when not focused', () => { + const Stack = createTestStackNavigator(); + + const TestStack = () => ( + + + null} /> + + ); + + render( + + + + ); + + expect(screen.queryByText('Modal')).not.toBeVisible(); +}); + +test('shows modal when focused', () => { + const Stack = createTestStackNavigator(); + + const TestStack = () => ( + + + null} /> + + ); + + render( + + + + ); + + expect(screen.getByText('Modal')).toBeVisible(); +}); +``` + + + + +Here we create a test stack navigator using the `createTestStackNavigator` function. We then render the `MyComponent` component within the test navigator and assert that the modal is shown or hidden based on the focus state. + +The `initialState` prop is used to set the initial state of the navigator, i.e. which screens are rendered in the stack and which one is focused. See [navigation state](navigation-state.md) for more information on the structure of the state object. + +You can also pass a [`ref`](navigation-container.md#ref) to programmatically navigate in your tests. + +The test navigator is a simplified version of the stack navigator, but it's still a real navigator and behaves like one. This means that you can use it to test any other navigation logic. + +See [Custom navigators](custom-navigators.md) for more information on how to write custom navigators if you want adjust the behavior of the test navigator or add more functionality. + +## Best practices + +Generally, we recommend avoiding mocking React Navigation. Mocking can help you isolate the component you're testing, but when testing components with navigation logic, mocking means that your tests don't test for the navigation logic. + +- Mocking APIs such as `useFocusEffect` means you're not testing the focus logic in your component. +- Mocking `navigation` prop or `useNavigation` means that the `navigation` object may not have the same shape as the real one. +- Asserting `navigation.navigate` calls means you only test that the function was called, not that the call was correct based on the navigation structure. +- etc. + +Avoiding mocks means additional work when writing tests, but it also means: + +- Refactors that don't change the logic won't break the tests, e.g. changing `navigation` prop to `useNavigation`, using a different navigation action that does the same thing, etc. +- Library upgrades or refactor that actually change the behavior will correctly break the tests, surfacing actual regressions. + +Tests should break when there's a regression, not due to a refactor. Otherwise it leads to additional work to fix the tests, making it harder to know when a regression is introduced. diff --git a/versioned_docs/version-7.x/typescript.md b/versioned_docs/version-7.x/typescript.md index 77755c0a14..27267aa0b3 100755 --- a/versioned_docs/version-7.x/typescript.md +++ b/versioned_docs/version-7.x/typescript.md @@ -9,11 +9,10 @@ import TabItem from '@theme/TabItem'; React Navigation can be configured to type-check screens and their params, as well as various other APIs using TypeScript. This provides better intelliSense and type safety when working with React Navigation. -:::note - -React Navigation is designed to work with [`strict`](https://www.typescriptlang.org/tsconfig/#strict) mode in TypeScript. If you are not using `strict` mode, some things might not work as expected. +First, make sure you have the following configuration in your `tsconfig.json` under `compilerOptions`: -::: +- `strict: true` or `strictNullChecks: true` - Necessary for intelliSense and type inference to work correctly. +- `moduleResolution: "bundler"` - Necessary to resolve the types correctly and match the behavior of [Metro](https://metrobundler.dev/) and other bundlers. diff --git a/versioned_docs/version-7.x/upgrading-from-6.x.md b/versioned_docs/version-7.x/upgrading-from-6.x.md index 20822dc78a..fd5c971ced 100755 --- a/versioned_docs/version-7.x/upgrading-from-6.x.md +++ b/versioned_docs/version-7.x/upgrading-from-6.x.md @@ -336,7 +336,7 @@ The `popToTopOnBlur` option provides an alternative approach - it pops the scree See [Bottom Tab Navigator](bottom-tab-navigator.md#poptotoponblur) and [Drawer Navigator](drawer-navigator.md#poptotoponblur) docs for usage. -It's still possible to achieve the old behavior of `unmountOnBlur` by using the useIsFocused hook in the screen: +It's still possible to achieve the old behavior of `unmountOnBlur` by using the [`useIsFocused`](use-is-focused.md) hook in the screen: ```js const isFocused = useIsFocused(); @@ -346,7 +346,45 @@ if (!isFocused) { } ``` -This could also be combined with the new [layout props](#new-layout-props) to specify it at the screen configuration level. +This could also be combined with the new [layout props](#new-layout-props) to specify it at the screen configuration level to make the migration easier. + +To achieve this, define a component that uses the `useIsFocused` hook to conditionally render its children: + +```js +function UnmountOnBlur({ children }) { + const isFocused = useIsFocused(); + + if (!isFocused) { + return null; + } + + return children; +} +``` + +Then use the component in `layout` prop of the screen: + +```diff lang=js + {children}} + options={{ +- unmountOnBlur: true, + }} +/> +``` + +Or `screenLayout` prop of the navigator: + +```diff lang=js + {children}} + screenOptions={{ +- unmountOnBlur: true, + }} +> +``` #### The `tabBarTestID` option is renamed to `tabBarButtonTestID` in Bottom Tab Navigator and Material Top Tab Navigator @@ -494,17 +532,36 @@ Previously, the UI elements in React Navigation such as the header on platforms #### React Native Tab View now has a new API to specify various options -The API for the `TabView` and `TabBar` component in `react-native-tab-view` has been revamped. Previously, the `TabBar` took the following props: +The API for the `TabView` and `TabBar` component in `react-native-tab-view` has been revamped. + +Some of props accepted by the `TabBar` have now been replaced with `commonOptions` and `options` props on `TabView`: -- `getLabelText` -- `getAccessible` -- `getAccessibilityLabel` -- `getTestID` -- `renderIcon` -- `renderLabel` -- `renderBadge` +- `getLabelText` -> `labelText` +- `getAccessible` -> `accessible` +- `getAccessibilityLabel` -> `accessibilityLabel` +- `getTestID` -> `testID` +- `renderIcon` -> `icon` +- `renderLabel` -> `label` +- `renderBadge` -> `badge` +- `labelStyle` +- `sceneContainerStyle` -> `sceneStyle` + +To keep the same behavior when updating your existing code, move these props to `commonOptions` prop on `TabView`: + +```diff lang=js + ( +- ++ + )} ++ commonOptions={{ ++ label: renderLabel, ++ labelStyle, ++ }} +/> +``` -These props have been replaced with `commonOptions` and `options` props on `TabView`: +The options can also be customized individually for each tab by passing an object to the `options` prop with the `route.key` as the key and the options as the value: ```js