diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2618dc1431f..123190557ce 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,5 @@ + diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index e890855f305..d8e6daa5fa2 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -1,9 +1,6 @@ name: autofix.ci on: - push: - branches: - - main pull_request: branches: - main @@ -28,4 +25,4 @@ jobs: run: yarn prettier --write . - name: Autofix - uses: autofix-ci/action@8106fde54b877517c9af2c3d68918ddeaa7bed64 + uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27 diff --git a/blog/2020-05-16-web-support.md b/blog/2020-05-16-web-support.md index 599bf8357e9..0f5be8529cc 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 fc950b677bd..014d7567e8b 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! + +Both Android and iOS have predefined native components for handling bottom navigation. For iOS it's SwiftUI's `TabView` component and for Android it's `BottomNavigationView`. The native approach gives us an appropriate appearance no matter the platform we are running on. Native Bottom Tabs is a navigator that wraps the native `TabView` and `BottomNavigationView` - so you can use them with React Navigation. + +Let's dive into the details of this navigator. + +Note: Native Bottom Tabs navigator is a standalone package, not released as part of React Navigation. + +## Overview + +You still might be wondering the difference between `@react-navigation/bottom-tabs` and `react-native-bottom-tabs`. + +Let's go over the main differences: + +- JS Bottom Tabs recreate the UI as closely as possible while **Native Bottom Tabs use native platform primitives** to create the tabs. This makes your tab navigation indistinguishable from Native Apps as they use the same components under the hood. +- Native Bottom Tabs **adapt to interfaces of a given platform** for example: tvOS and visionOS show tabs as a sidebar on iPadOS they appear at the top, while JS Bottom Tabs are always at the bottom. + +### Distinctive features of Native Bottom Tabs + +#### Multi-platform support + +Native Bottom tabs adapt to the appearance of multiple platforms. You always get natively-looking tabs! + +Native Tabs on iOS + +Bottom Navigation on iOS, with native blur. + +Native Tabs on Android + +Bottom Navigation on Android, following Material Design 3 styling. + +Native Tabs on iPadOS + +On iPadOS tabs appear at the top with a button allowing you to go into the sidebar mode. + +Native Tabs on visionOS + +On visionOS, the tabs appear on the left side, attached outside of the window. + +Native Tabs on tvOS + +On tvOS tabs appear on the top, making navigation with the TV remote a breeze. + +Native Tabs on macOS + +On macOS, tabs appear on the left side, following the design of the Finder app. + +#### Automatic scroll to the top + +iOS TabView automatically scrolls to the top when ScrollView is embedded inside of it. + +#### Automatic PiP avoidance + +The operating system recognizes navigation in your app making the Picture in Picture window automatically avoid bottom navigation. + +#### Platform-specific styling + +For iOS bottom navigation has a built-in blur making your app stand out. For Android, you can choose between Material 2 and Material 3 and leverage Material You system styling. + +#### Sidebar + +TabView can turn in to a side bar on tvOS, iPadOS and macOS. The `sidebarAdaptable` prop controls this. + +## Getting started + +To get started follow the installation instructions in the `react-native-bottom-tabs` [documentation](https://callstackincubator.github.io/react-native-bottom-tabs/docs/getting-started/quick-start.html). + +Native Bottom Tabs Navigation resembles JavaScript Tabs API as closely as possible. Making your migration straightforward. + +As mentioned before, Native Bottom Tabs use native primitives to create the tabs. This approach also has some downsides: Native components enforce certain constraints that we need to follow. + +There are a few differences between the APIs worth noting. One of the biggest is how native tabs handle images. In JavaScript tabs, you can render React components as icons, in native tabs unfortunately it’s not possible. Instead, you have to provide one of the following options: + +```tsx + require('person.png'), + // SVG is also supported + tabBarIcon: () => require('person.svg'), + // or + tabBarIcon: () => ({ sfSymbol: 'person' }), + // You can also pass a URL + tabBarIcon: () => ({ uri: 'https://example.com/icon.png' }), + }} +/> +``` + +So if you need full customizability like providing custom tab bar icons, and advanced styling that goes beyond what’s possible with native components you should use JavaScript bottom tabs. + +On top of that, the scope of this library doesn’t include the web so for that platform, you should use JavaScript Tabs. + +To get started you can import `createNativeBottomTabNavigator` from `@bottom-tabs/react-navigation` and use it the same way as JavaScript Bottom Tabs. + +### Example usage + +```tsx +import { createNativeBottomTabNavigator } from '@bottom-tabs/react-navigation'; + +const Tabs = createNativeBottomTabNavigator(); + +function NativeBottomTabs() { + return ( + + ({ uri: 'https://example.com/icon.png' }), + }} + /> + ({ uri: 'https://example.com/icon.png' }), + }} + /> + + ); +} +``` + +Native Tabs + +You can check out the project [here](https://github.com/callstackincubator/react-native-bottom-tabs). + +Thanks for reading! diff --git a/blog/authors.yml b/blog/authors.yml index ec3a850bf0d..a184d71c1bb 100644 --- a/blog/authors.yml +++ b/blog/authors.yml @@ -39,3 +39,11 @@ dawid: socials: x: trensik github: Trancever + +oskar: + name: Oskar Kwaśniewski + image_url: https://avatars.githubusercontent.com/u/52801365 + title: Callstack + socials: + x: o_kwasniewski + github: okwasniewsk diff --git a/package.json b/package.json index 4989ead8672..8c4108eb882 100755 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "deploy": "DEPLOYMENT_BRANCH=gh-pages docusaurus deploy", "crowdin-upload": "crowdin upload sources --auto-update -b main", "crowdin-download": "crowdin download -b main", - "fetch-sponsors": "node scripts/fetch-sponsors.js" + "fetch-sponsors": "node scripts/fetch-sponsors.js && prettier --write src/data/sponsors.js" }, "dependencies": { "@docusaurus/core": "3.6.1", diff --git a/src/css/custom.css b/src/css/custom.css index 19d5ba9ba0e..33d5487453c 100755 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -81,8 +81,12 @@ --ifm-footer-padding-horizontal: var(--ifm-spacing-horizontal); --ifm-footer-padding-vertical: var(--ifm-spacing-vertical); - --ifm-tabs-padding-vertical: 0.5rem; + --ifm-tabs-padding-vertical: 0.375rem; + + --ifm-alert-shadow: none; --ifm-alert-border-left-width: 0; + + --codeblock-background-color: #f6f8fa; } :root[data-theme='dark'] { @@ -133,6 +137,8 @@ --ifm-home-color-border: #f7f7ff; --docusaurus-highlighted-code-line-bg: rgba(255, 255, 255, 0.07); + + --codeblock-background-color: #282a35; } h1 { @@ -388,7 +394,7 @@ p { } .tabs__item { - border-bottom: 1px solid var(--ifm-toc-border-color); + border-bottom: 2px solid var(--ifm-toc-border-color); border-bottom-left-radius: 0; border-bottom-right-radius: 0; } @@ -396,7 +402,29 @@ p { .tabs__item--active, .tabs__item--active:hover { border-bottom-color: var(--ifm-tabs-color-active-border); - background-color: var(--ifm-menu-color-background-active); +} + +.tabs-container:has( + > .margin-top--md > [role='tabpanel'] > .theme-code-block:only-child + ):not(:has(> .margin-top--md > [role='tabpanel'] > :nth-child(2))) { + background-color: var(--codeblock-background-color); + border-radius: var(--ifm-code-border-radius); + + & > .margin-top--md { + margin-top: 0 !important; + } + + & > .tabs { + box-shadow: inset 0 -2px 0 var(--ifm-toc-border-color); + } + + & > .tabs > .tabs__item { + border-top-right-radius: 0; + } + + & > .tabs > .tabs__item:not(:first-child) { + border-top-left-radius: 0; + } } .col:has(.table-of-contents) { @@ -475,6 +503,10 @@ samp { display: none; } +.theme-code-block { + box-shadow: none !important; +} + .theme-code-block:has(+ .snack-sample-link) { margin-bottom: 0; border-bottom-left-radius: 0; @@ -488,17 +520,15 @@ samp { margin-top: 0; margin-bottom: var(--ifm-leading); padding: calc(var(--ifm-pre-padding) / 2) var(--ifm-pre-padding); - background-color: #f6f8fa; + background-color: var(--codeblock-background-color); border-top-width: 1px; border-top-style: solid; border-top-color: var(--ifm-color-gray-200); border-bottom-left-radius: var(--ifm-pre-border-radius); border-bottom-right-radius: var(--ifm-pre-border-radius); - box-shadow: var(--ifm-global-shadow-lw); } [data-theme='dark'] .theme-code-block + .snack-sample-link { - background-color: #282a35; border-top-color: rgba(255, 255, 255, 0.07); } diff --git a/src/data/sponsors.js b/src/data/sponsors.js index ece5c987929..84d0f91f24a 100644 --- a/src/data/sponsors.js +++ b/src/data/sponsors.js @@ -1,37 +1,9 @@ export default [ { avatarUrl: - 'https://avatars.githubusercontent.com/u/9664363?u=a4a9e93dc4305c91ced38b83d4c08186f7254b04&v=4', - username: 'EvanBacon', - name: 'Evan Bacon', - }, - { - avatarUrl: 'https://avatars.githubusercontent.com/u/306134?v=4', - username: 'wcandillon', - name: 'William Candillon', - }, - { - avatarUrl: 'https://avatars.githubusercontent.com/u/476779?v=4', - username: 'Expensify', - name: 'Expensify, Inc', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/916690?u=66482eb2c5bb755553afbcfa219dcacc42fc487a&v=4', - username: 'benevbright', - name: 'Bright Lee', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/980234?u=59bb4c6ac0a23b53225bb2235b69c72a960ba83f&v=4', - username: 'simoncar', - name: 'Simoncar', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/1057756?u=15c3cdff1c715ac27bbc63ccb8f0a1c27eeb3784&v=4', - username: 'zhigang1992', - name: 'Zhigang Fang', + 'https://avatars.githubusercontent.com/u/360412?u=15e7b90eb91a3d2b410f7f47461862cb793398ff&v=4', + username: 'jyc', + name: null, }, { avatarUrl: @@ -41,27 +13,14 @@ export default [ }, { avatarUrl: - 'https://avatars.githubusercontent.com/u/1566403?u=3df07e2ae72e89a3a3509ba6c2f927115b5f38aa&v=4', - username: 'vonovak', - name: 'Vojtech Novak', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/1629785?u=12eb94da6070d00fc924761ce06e3a428d01b7e9&v=4', + 'https://avatars.githubusercontent.com/u/1629785?u=f91613c118bb1fcf442a71008dff1cd5f9b30411&v=4', username: 'JonnyBurger', name: 'Jonny Burger', }, { - avatarUrl: - 'https://avatars.githubusercontent.com/u/1764217?u=f36737d852ffcc50b87e809474faa27eb2ce130a&v=4', - username: 'markholland', - name: 'Mark Holland', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/3584560?u=f54bd481e956c6b3fe88a15f466ff9a3973e4b35&v=4', - username: 'hetmann', - name: 'Hetmann W. Iohan', + avatarUrl: 'https://avatars.githubusercontent.com/u/2443340?v=4', + username: 'toyokumo', + name: 'TOYOKUMO', }, { avatarUrl: @@ -69,29 +28,6 @@ export default [ username: 'itsrifat', name: 'Moinul Hossain', }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/4376835?u=0cf5324a78dd4140ef71943048dedac328be68b9&v=4', - username: 'lnmunhoz', - name: 'Lucas N. Munhoz', - }, - { - avatarUrl: 'https://avatars.githubusercontent.com/u/5605177?v=4', - username: 'Razorholt', - name: null, - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/5967956?u=f7f5ed6b6b399c2953fd0e3be0512c378e9f76c4&v=4', - username: 'codinsonn', - name: 'Thorr Stevens', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/6457344?u=47e100289441b7f4681a7809202ff683886e4f5e&v=4', - username: 'ryo-rm', - name: 'ryo kishida', - }, { avatarUrl: 'https://avatars.githubusercontent.com/u/6936373?u=4edd14e6636c45d10ac6a3eecb4b3ffa6cc2bf5c&v=4', @@ -105,89 +41,38 @@ export default [ name: 'Radek Czemerys', }, { - avatarUrl: 'https://avatars.githubusercontent.com/u/12504344?v=4', - username: 'expo', - name: 'Expo', - }, - { - avatarUrl: 'https://avatars.githubusercontent.com/u/13601619?v=4', - username: 'itiden', - name: 'Itiden', + avatarUrl: + 'https://avatars.githubusercontent.com/u/7910545?u=95ae2c2a40b5f6f63f05fce29ee7c01622019c76&v=4', + username: 'UdaySravanK', + name: 'Uday Sravan K', }, { avatarUrl: - 'https://avatars.githubusercontent.com/u/14099522?u=965e74751a9db0f40a30ace9cd0cb1f82eaf1412&v=4', - username: 'aCanalez', - name: 'Alexi Canales', + 'https://avatars.githubusercontent.com/u/9664363?u=46ba6d5fbd29729df2950b845c9ca2cd085a1c2b&v=4', + username: 'EvanBacon', + name: 'Evan Bacon', }, { avatarUrl: - 'https://avatars.githubusercontent.com/u/15199031?u=46da50e88594eb284cf249485f202d5d43d474d1&v=4', + 'https://avatars.githubusercontent.com/u/15199031?u=5a82dcb32237282ff576c0446567a1e2fe49b868&v=4', username: 'mrousavy', name: 'Marc Rousavy', }, { avatarUrl: - 'https://avatars.githubusercontent.com/u/17621507?u=0ee7f26191d430f4fc0672cef92c2759d948bbb5&v=4', - username: 'dsznajder', - name: 'Damian Sznajder', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/21980965?u=5a571092a83cb71508c60a9c86ab2520fde8a68e&v=4', - username: 'jarvisluong', - name: 'Jarvis Luong', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/26326015?u=33a1afbe11e8f6b962c6267606d59c2b2ef94716&v=4', - username: 'bang9', - name: 'Hyungu Kang | Airen', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/27461460?u=b5860875e26d33fd70fd210f4ea74f81cdf9d99b&v=4', - username: 'hyochan', - name: 'Hyo', - }, - { - avatarUrl: 'https://avatars.githubusercontent.com/u/30735054?v=4', - username: 'endearhq', - name: 'Endear', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/33361399?u=6180514361e35ae42e2401431555c82cc63adda9&v=4', - username: 'oliverloops', - name: 'Oliver Lopez ', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/34658847?u=17fd3603d012d068c060039bcf009095055290a1&v=4', - username: 'RatebSeirawan', - name: 'Rateb Seirawan', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/36824170?u=6f56fa2998ffba6b5a3908c79e2ef9331bad502a&v=4', - username: 'luism3861', - name: 'Luis Medina Huerta', - }, - { - avatarUrl: - 'https://avatars.githubusercontent.com/u/45317893?u=253e6eabcb23a5361e93a064914c9b8d136ac888&v=4', - username: 'GandresCoello18', - name: 'Andres Coello', + 'https://avatars.githubusercontent.com/u/23122214?u=7b3231a177e5d661d55b2cee88ea69e1713eb695&v=4', + username: 'sayurimizuguchi', + name: 'Sayuri Mizuguchi', }, { avatarUrl: - 'https://avatars.githubusercontent.com/u/46625943?u=63c9ed9017c34900df8b5ae2ed455ec4c82ef8aa&v=4', - username: 'bowen9284', - name: 'Matt Bowen', + 'https://avatars.githubusercontent.com/u/79333934?u=d18e4c6a4e063534d6fd7e77e0d51c367c91cfa0&v=4', + username: 'finanzguru', + name: 'Finanzguru', }, { - avatarUrl: 'https://avatars.githubusercontent.com/u/49920282?v=4', - username: 'reactrondev', - name: 'Reactron', + avatarUrl: 'https://avatars.githubusercontent.com/u/140319444?v=4', + username: 'jupli-apps', + name: 'Jupli', }, ]; diff --git a/src/pages/home/Sponsors/index.js b/src/pages/home/Sponsors/index.js index 40c3bff4d8d..4bac27a7f4d 100644 --- a/src/pages/home/Sponsors/index.js +++ b/src/pages/home/Sponsors/index.js @@ -1,5 +1,3 @@ -import React from 'react'; - import sponsors from '../../../data/sponsors'; import styles from './styles.module.css'; @@ -8,15 +6,14 @@ export default function Sponsors() {

- React Navigation is built by Expo,{' '} - Software Mansion, and{' '} - Callstack, with contributions - from the{' '} + React Navigation relies on the support from the community. Thanks to{' '} + Software Mansion,{' '} + Callstack,{' '} + Expo, and our amazing{' '} - community + contributors {' '} - and{' '} - sponsors: + & sponsors:

{sponsors.map((sponsor) => ( diff --git a/static/assets/7.x/native-bottom-tabs-android.mp4 b/static/assets/7.x/native-bottom-tabs-android.mp4 new file mode 100644 index 00000000000..4c65cf2105c Binary files /dev/null and b/static/assets/7.x/native-bottom-tabs-android.mp4 differ diff --git a/static/assets/7.x/native-bottom-tabs-ios-minimize.mp4 b/static/assets/7.x/native-bottom-tabs-ios-minimize.mp4 new file mode 100644 index 00000000000..2460652d08b Binary files /dev/null and b/static/assets/7.x/native-bottom-tabs-ios-minimize.mp4 differ diff --git a/static/assets/7.x/native-bottom-tabs-ios-search.mp4 b/static/assets/7.x/native-bottom-tabs-ios-search.mp4 new file mode 100644 index 00000000000..e3892bde2f3 Binary files /dev/null and b/static/assets/7.x/native-bottom-tabs-ios-search.mp4 differ diff --git a/static/assets/7.x/native-bottom-tabs-ios.mp4 b/static/assets/7.x/native-bottom-tabs-ios.mp4 new file mode 100644 index 00000000000..29f928e9779 Binary files /dev/null and b/static/assets/7.x/native-bottom-tabs-ios.mp4 differ diff --git a/static/assets/7.x/native-stack/formSheet-sheetAllowedDetents.mp4 b/static/assets/7.x/native-stack/formSheet-sheetAllowedDetents.mp4 new file mode 100644 index 00000000000..25059f143c9 Binary files /dev/null and b/static/assets/7.x/native-stack/formSheet-sheetAllowedDetents.mp4 differ diff --git a/static/assets/7.x/native-stack/formSheet-sheetCornerRadius.mp4 b/static/assets/7.x/native-stack/formSheet-sheetCornerRadius.mp4 new file mode 100644 index 00000000000..6ebe5929e0e Binary files /dev/null and b/static/assets/7.x/native-stack/formSheet-sheetCornerRadius.mp4 differ diff --git a/static/assets/7.x/native-stack/formSheet-sheetElevation.mp4 b/static/assets/7.x/native-stack/formSheet-sheetElevation.mp4 new file mode 100644 index 00000000000..a9d80dcde30 Binary files /dev/null and b/static/assets/7.x/native-stack/formSheet-sheetElevation.mp4 differ diff --git a/static/assets/7.x/native-stack/formSheet-sheetExpandsWhenScrolledToEdge.mp4 b/static/assets/7.x/native-stack/formSheet-sheetExpandsWhenScrolledToEdge.mp4 new file mode 100644 index 00000000000..2da3d6a74fa Binary files /dev/null and b/static/assets/7.x/native-stack/formSheet-sheetExpandsWhenScrolledToEdge.mp4 differ diff --git a/static/assets/7.x/native-stack/formSheet-sheetGrabberVisible.mp4 b/static/assets/7.x/native-stack/formSheet-sheetGrabberVisible.mp4 new file mode 100644 index 00000000000..4d0c331cf23 Binary files /dev/null and b/static/assets/7.x/native-stack/formSheet-sheetGrabberVisible.mp4 differ diff --git a/static/assets/7.x/native-stack/formSheet-sheetInitialDetentIndex.mp4 b/static/assets/7.x/native-stack/formSheet-sheetInitialDetentIndex.mp4 new file mode 100644 index 00000000000..2484f912240 Binary files /dev/null and b/static/assets/7.x/native-stack/formSheet-sheetInitialDetentIndex.mp4 differ diff --git a/static/assets/7.x/native-stack/formSheet-sheetLargestUndimmedDetentIndex.mp4 b/static/assets/7.x/native-stack/formSheet-sheetLargestUndimmedDetentIndex.mp4 new file mode 100644 index 00000000000..f7eb0cb7fee Binary files /dev/null and b/static/assets/7.x/native-stack/formSheet-sheetLargestUndimmedDetentIndex.mp4 differ diff --git a/static/assets/7.x/native-stack/presentation-formSheet-android.mp4 b/static/assets/7.x/native-stack/presentation-formSheet-android.mp4 new file mode 100644 index 00000000000..966bff508c7 Binary files /dev/null and b/static/assets/7.x/native-stack/presentation-formSheet-android.mp4 differ diff --git a/static/assets/7.x/native-stack/presentation-formSheet-ios.mp4 b/static/assets/7.x/native-stack/presentation-formSheet-ios.mp4 new file mode 100644 index 00000000000..89fca9b830e Binary files /dev/null and b/static/assets/7.x/native-stack/presentation-formSheet-ios.mp4 differ diff --git a/static/assets/7.x/native-stack/presentation-formSheet.mp4 b/static/assets/7.x/native-stack/presentation-formSheet.mp4 deleted file mode 100644 index 52de749077c..00000000000 Binary files a/static/assets/7.x/native-stack/presentation-formSheet.mp4 and /dev/null differ diff --git a/static/assets/blog/native-bottom-tabs/android.png b/static/assets/blog/native-bottom-tabs/android.png new file mode 100644 index 00000000000..8ac1fcb0bc1 Binary files /dev/null and b/static/assets/blog/native-bottom-tabs/android.png differ diff --git a/static/assets/blog/native-bottom-tabs/ios.png b/static/assets/blog/native-bottom-tabs/ios.png new file mode 100644 index 00000000000..78792f1b4bc Binary files /dev/null and b/static/assets/blog/native-bottom-tabs/ios.png differ diff --git a/static/assets/blog/native-bottom-tabs/ipados.png b/static/assets/blog/native-bottom-tabs/ipados.png new file mode 100644 index 00000000000..5804af44bfc Binary files /dev/null and b/static/assets/blog/native-bottom-tabs/ipados.png differ diff --git a/static/assets/blog/native-bottom-tabs/macos.png b/static/assets/blog/native-bottom-tabs/macos.png new file mode 100644 index 00000000000..cb704aa3c7e Binary files /dev/null and b/static/assets/blog/native-bottom-tabs/macos.png differ diff --git a/static/assets/blog/native-bottom-tabs/result.png b/static/assets/blog/native-bottom-tabs/result.png new file mode 100644 index 00000000000..dd62cdbc222 Binary files /dev/null and b/static/assets/blog/native-bottom-tabs/result.png differ diff --git a/static/assets/blog/native-bottom-tabs/tvos.png b/static/assets/blog/native-bottom-tabs/tvos.png new file mode 100644 index 00000000000..45e3840b5d8 Binary files /dev/null and b/static/assets/blog/native-bottom-tabs/tvos.png differ diff --git a/static/assets/blog/native-bottom-tabs/visionos.png b/static/assets/blog/native-bottom-tabs/visionos.png new file mode 100644 index 00000000000..4660c2769bb Binary files /dev/null and b/static/assets/blog/native-bottom-tabs/visionos.png differ diff --git a/static/assets/deep-linking/xcode-linking.png b/static/assets/deep-linking/xcode-linking.png old mode 100755 new mode 100644 index 9f5170bf9a0..2cea12f4854 Binary files a/static/assets/deep-linking/xcode-linking.png and b/static/assets/deep-linking/xcode-linking.png differ diff --git a/static/assets/header-items/header-items-menu.png b/static/assets/header-items/header-items-menu.png new file mode 100644 index 00000000000..c900fdb84f7 Binary files /dev/null and b/static/assets/header-items/header-items-menu.png differ diff --git a/static/assets/header-items/header-items.png b/static/assets/header-items/header-items.png new file mode 100644 index 00000000000..cb7430b9128 Binary files /dev/null and b/static/assets/header-items/header-items.png differ diff --git a/versioned_docs/version-1.x/deep-linking.md b/versioned_docs/version-1.x/deep-linking.md index 4e3b7e229aa..bbbffeb5ba2 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 e0e0f6f3854..1659d84a7ac 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 7adc9312c4d..cce7c6f89bf 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/contributing.md b/versioned_docs/version-4.x/contributing.md index faf347fc790..5472a1c0acd 100644 --- a/versioned_docs/version-4.x/contributing.md +++ b/versioned_docs/version-4.x/contributing.md @@ -32,7 +32,7 @@ And here are a few helpful resources to aid in getting started: You can't write code without writing the occasional bug. Especially as React Navigation is moving quickly, bugs happen. When you think you've found one here's what to do: 1. Search the existing issues for one like what you're seeing. If you see one, add a 👍 reaction (please no +1 comments). Read through the comments and see if you can provide any more valuable information to the thread -2. If there are no other issues like yours then create a new one. Be sure to follow the [issue template](https://github.com/react-navigation/react-navigation/blob/4.x/.github/ISSUE_TEMPLATE.md). +2. If there are no other issues like yours then create a new one. Be sure to follow the [issue template](https://github.com/react-navigation/react-navigation/blob/v4.1.1/.github/ISSUE_TEMPLATE/bug_report.md). Creating a high quality reproduction is critical. Without it we likely can't fix the bug and, in an ideal situation, you'll find out that it's not actually a bug of the library but simply done incorrectly in your project. Instant bug fix! @@ -93,7 +93,7 @@ The libdef (located at `flow/react-navigation.js`) will need to be updated such ### Issue Template -Before submitting an issue, please take a look at the [issue template](https://github.com/react-navigation/react-navigation/blob/4.x/.github/ISSUE_TEMPLATE.md) and follow it. This is in place to help everyone better understand the issue you're having and reduce the back and forth to get the necessary information. +Before submitting an issue, please take a look at the [issue template](https://github.com/react-navigation/react-navigation/blob/v4.1.1/.github/ISSUE_TEMPLATE/bug_report.md) and follow it. This is in place to help everyone better understand the issue you're having and reduce the back and forth to get the necessary information. Yes, it takes time and effort to complete the issue template. But that's the only way to ask high quality questions that actually get responses. @@ -101,7 +101,7 @@ Would you rather take 1 minute to create an incomplete issue report and wait mon ### Pull Request Template -Much like the issue template, the [pull request template](https://github.com/react-navigation/react-navigation/blob/4.x/.github/PULL_REQUEST_TEMPLATE.md) lays out instructions to ensure your pull request gets reviewed in a timely manner and reduces the back and forth. Make sure to look it over before you start writing any code. +Much like the issue template, the [pull request template](https://github.com/react-navigation/react-navigation/blob/v4.1.1/.github/PULL_REQUEST_TEMPLATE.md) lays out instructions to ensure your pull request gets reviewed in a timely manner and reduces the back and forth. Make sure to look it over before you start writing any code. ### Forking the Repository diff --git a/versioned_docs/version-4.x/deep-linking.md b/versioned_docs/version-4.x/deep-linking.md index 60536603b43..4576a34ca32 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-4.x/stack-navigator.md b/versioned_docs/version-4.x/stack-navigator.md index b7983ddfd96..930d90dff2a 100644 --- a/versioned_docs/version-4.x/stack-navigator.md +++ b/versioned_docs/version-4.x/stack-navigator.md @@ -69,8 +69,8 @@ Visual options: - Prevents last inactive screen from being detached so that it stays visible underneath the active screen - Make the screens slide in from the bottom on iOS which is a common iOS pattern. - `headerMode` - Specifies how the header should be rendered: - - `float` - Render a single header that stays at the top and animates as screens are changed. This is a common pattern on iOS. - - `screen` - Each screen has a header attached to it and the header fades in and out together with the screen. This is a common pattern on Android. + - `float` - The header is rendered above the screen and animates independently of the screen. This is default on iOS for non-modals. + - `screen` - The header is rendered as part of the screen and animates together with the screen. This is default on other platforms. - `none` - No header will be rendered. - `keyboardHandlingEnabled` - If `false`, the on screen keyboard will NOT automatically dismiss when navigating to a new screen. Defaults to `true`. diff --git a/versioned_docs/version-5.x/configuring-links.md b/versioned_docs/version-5.x/configuring-links.md index 4542bece964..01ed72fd7b3 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/contributing.md b/versioned_docs/version-5.x/contributing.md index aa31a3cb0b6..f90dd750538 100755 --- a/versioned_docs/version-5.x/contributing.md +++ b/versioned_docs/version-5.x/contributing.md @@ -15,23 +15,9 @@ Here are some of the ways to contribute to the project: - [Bug Fixes](#bug-fixes) - [Suggesting a Feature](#suggesting-a-feature) - [Big Pull Requests](#big-pull-requests) -- [Information](#information) - - [Issue Template](#issue-template) - - [Pull Request Template](#pull-request-template) - - [Forking the Repository](#forking-the-repository) - - [Code Review Guidelines](#code-review-guidelines) - - [Run the Example App](#run-the-example-app) - - [Run Tests](#run-tests) And here are a few helpful resources to aid in getting started: -- [Contributing](#contributing) - - [Reporting Bugs](#reporting-bugs) - - [Improving the Documentation](#improving-the-documentation) - - [Responding to Issues](#responding-to-issues) - - [Bug Fixes](#bug-fixes) - - [Suggesting a Feature](#suggesting-a-feature) - - [Big Pull Requests](#big-pull-requests) - [Information](#information) - [Issue Template](#issue-template) - [Pull Request Template](#pull-request-template) @@ -47,7 +33,7 @@ And here are a few helpful resources to aid in getting started: You can't write code without writing the occasional bug. Especially as React Navigation is moving quickly, bugs happen. When you think you've found one here's what to do: 1. Search the existing issues for one like what you're seeing. If you see one, add a 👍 reaction (please no +1 comments). Read through the comments and see if you can provide any more valuable information to the thread -2. If there are no other issues like yours then create a new one. Be sure to follow the [issue template](https://github.com/react-navigation/react-navigation/blob/main/.github/ISSUE_TEMPLATE.md). +2. If there are no other issues like yours then create a new one. Be sure to follow the [issue template](https://github.com/react-navigation/react-navigation/blob/%40react-navigation/core%405.14.4/.github/ISSUE_TEMPLATE/bug-report.md). Creating a high quality reproduction is critical. Without it we likely can't fix the bug and, in an ideal situation, you'll find out that it's not actually a bug of the library but simply done incorrectly in your project. Instant bug fix! @@ -96,7 +82,7 @@ The reason we want to do this is to save everyone time. Maybe that feature alrea ### Issue Template -Before submitting an issue, please take a look at the [issue template](https://github.com/react-navigation/react-navigation/blob/main/.github/ISSUE_TEMPLATE.md) and follow it. This is in place to help everyone better understand the issue you're having and reduce the back and forth to get the necessary information. +Before submitting an issue, please take a look at the [issue template](https://github.com/react-navigation/react-navigation/blob/%40react-navigation/core%405.14.4/.github/ISSUE_TEMPLATE/bug-report.md) and follow it. This is in place to help everyone better understand the issue you're having and reduce the back and forth to get the necessary information. Yes, it takes time and effort to complete the issue template. But that's the only way to ask high quality questions that actually get responses. @@ -104,7 +90,7 @@ Would you rather take 1 minute to create an incomplete issue report and wait mon ### Pull Request Template -Much like the issue template, the [pull request template](https://github.com/react-navigation/react-navigation/blob/main/.github/PULL_REQUEST.md) lays out instructions to ensure your pull request gets reviewed in a timely manner and reduces the back and forth. Make sure to look it over before you start writing any code. +Much like the issue template, the [pull request template](https://github.com/react-navigation/react-navigation/blob/%40react-navigation/core%405.14.4/.github/PULL_REQUEST.md) lays out instructions to ensure your pull request gets reviewed in a timely manner and reduces the back and forth. Make sure to look it over before you start writing any code. ### Forking the Repository diff --git a/versioned_docs/version-5.x/deep-linking.md b/versioned_docs/version-5.x/deep-linking.md index 3bbc8115677..bbc447119a3 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 d58bef49356..885477f1d12 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/contributing.md b/versioned_docs/version-6.x/contributing.md index aa31a3cb0b6..461dde2dcc7 100755 --- a/versioned_docs/version-6.x/contributing.md +++ b/versioned_docs/version-6.x/contributing.md @@ -15,23 +15,9 @@ Here are some of the ways to contribute to the project: - [Bug Fixes](#bug-fixes) - [Suggesting a Feature](#suggesting-a-feature) - [Big Pull Requests](#big-pull-requests) -- [Information](#information) - - [Issue Template](#issue-template) - - [Pull Request Template](#pull-request-template) - - [Forking the Repository](#forking-the-repository) - - [Code Review Guidelines](#code-review-guidelines) - - [Run the Example App](#run-the-example-app) - - [Run Tests](#run-tests) And here are a few helpful resources to aid in getting started: -- [Contributing](#contributing) - - [Reporting Bugs](#reporting-bugs) - - [Improving the Documentation](#improving-the-documentation) - - [Responding to Issues](#responding-to-issues) - - [Bug Fixes](#bug-fixes) - - [Suggesting a Feature](#suggesting-a-feature) - - [Big Pull Requests](#big-pull-requests) - [Information](#information) - [Issue Template](#issue-template) - [Pull Request Template](#pull-request-template) @@ -47,7 +33,7 @@ And here are a few helpful resources to aid in getting started: You can't write code without writing the occasional bug. Especially as React Navigation is moving quickly, bugs happen. When you think you've found one here's what to do: 1. Search the existing issues for one like what you're seeing. If you see one, add a 👍 reaction (please no +1 comments). Read through the comments and see if you can provide any more valuable information to the thread -2. If there are no other issues like yours then create a new one. Be sure to follow the [issue template](https://github.com/react-navigation/react-navigation/blob/main/.github/ISSUE_TEMPLATE.md). +2. If there are no other issues like yours then create a new one. Be sure to follow the [issue template](https://github.com/react-navigation/react-navigation/blob/6.x/.github/ISSUE_TEMPLATE/bug-report.yml). Creating a high quality reproduction is critical. Without it we likely can't fix the bug and, in an ideal situation, you'll find out that it's not actually a bug of the library but simply done incorrectly in your project. Instant bug fix! @@ -96,7 +82,7 @@ The reason we want to do this is to save everyone time. Maybe that feature alrea ### Issue Template -Before submitting an issue, please take a look at the [issue template](https://github.com/react-navigation/react-navigation/blob/main/.github/ISSUE_TEMPLATE.md) and follow it. This is in place to help everyone better understand the issue you're having and reduce the back and forth to get the necessary information. +Before submitting an issue, please take a look at the [issue template](https://github.com/react-navigation/react-navigation/blob/6.x/.github/ISSUE_TEMPLATE/bug-report.yml) and follow it. This is in place to help everyone better understand the issue you're having and reduce the back and forth to get the necessary information. Yes, it takes time and effort to complete the issue template. But that's the only way to ask high quality questions that actually get responses. @@ -104,7 +90,7 @@ Would you rather take 1 minute to create an incomplete issue report and wait mon ### Pull Request Template -Much like the issue template, the [pull request template](https://github.com/react-navigation/react-navigation/blob/main/.github/PULL_REQUEST.md) lays out instructions to ensure your pull request gets reviewed in a timely manner and reduces the back and forth. Make sure to look it over before you start writing any code. +Much like the issue template, the [pull request template](https://github.com/react-navigation/react-navigation/blob/6.x/.github/PULL_REQUEST_TEMPLATE.md) lays out instructions to ensure your pull request gets reviewed in a timely manner and reduces the back and forth. Make sure to look it over before you start writing any code. ### Forking the Repository diff --git a/versioned_docs/version-6.x/deep-linking.md b/versioned_docs/version-6.x/deep-linking.md index ef0f3ee0e72..e112f3f7f81 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/native-stack-navigator.md b/versioned_docs/version-6.x/native-stack-navigator.md index 3aa0c89c91b..4b286b0ac7c 100755 --- a/versioned_docs/version-6.x/native-stack-navigator.md +++ b/versioned_docs/version-6.x/native-stack-navigator.md @@ -457,15 +457,17 @@ Only supported on Android and iOS. #### `statusBarStyle` -Sets the status bar color (similar to the `StatusBar` component). Defaults to `auto`. +Sets the status bar color (similar to the `StatusBar` component). Supported values: -- `"auto"` +- `"auto"` (iOS only) - `"inverted"` (iOS only) - `"dark"` - `"light"` +Defaults to `auto` on iOS and `light` on Android. + Requires setting `View controller-based status bar appearance -> YES` (or removing the config) in your `Info.plist` file. Only supported on Android and iOS. diff --git a/versioned_docs/version-6.x/navigation-container.md b/versioned_docs/version-6.x/navigation-container.md index f5708d8a60f..5d48860e803 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 , - StackScreenProps + StackScreenProps >; ``` -The `CompositeScreenProps` type takes 2 parameters, first parameter is the type of props for the primary navigation (type for the navigator that owns this screen, in our case the tab navigator which contains the `Profile` screen) and second parameter is the type of props for secondary navigation (type for a parent navigator). The primary type should always have the screen's route name as its second parameter. +The `CompositeScreenProps` type takes 2 parameters: + +- The first parameter is the type for the navigator that owns this screen, in our case the tab navigator which contains the `Profile` screen +- The second parameter is the type of props for a parent navigator, in our case the stack navigator which contains the `Account` screen -For multiple parent navigators, this secondary type should be nested: +For multiple parent navigators, this second parameter can nest another `CompositeScreenProps`: ```ts type ProfileScreenProps = CompositeScreenProps< BottomTabScreenProps, CompositeScreenProps< - StackScreenProps, - DrawerScreenProps + StackScreenProps, + DrawerScreenProps > >; ``` @@ -207,7 +212,7 @@ import type { StackNavigationProp } from '@react-navigation/stack'; type ProfileScreenNavigationProp = CompositeNavigationProp< BottomTabNavigationProp, - StackNavigationProp + StackNavigationProp >; ``` @@ -358,7 +363,7 @@ export type HomeTabParamList = { export type HomeTabScreenProps = CompositeScreenProps< BottomTabScreenProps, - RootStackScreenProps + RootStackScreenProps >; declare global { diff --git a/versioned_docs/version-7.x/auth-flow.md b/versioned_docs/version-7.x/auth-flow.md index 427e0390a2c..c3d4e71772a 100755 --- a/versioned_docs/version-7.x/auth-flow.md +++ b/versioned_docs/version-7.x/auth-flow.md @@ -30,45 +30,12 @@ We want the following behavior from our authentication flow: ## How it will work -We can configure different screens to be available based on some condition. For example, if the user is signed in, we can define `Home`, `Profile`, `Settings` etc. If the user is not signed in, we can define `SignIn` and `SignUp` screens. +We can configure different screens to be available based on some condition. For example, if the user is signed in, we want `Home` to be available. If the user is not signed in, we want `SignIn` to be available. -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're 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. +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. -The main thing to notice is that we're conditionally defining screens based on these state variables: - -- `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 */ +We can use [`groups`](static-configuration.md#groups) to define multiple screens: -export default function App() { - /* content */ - - 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,72 @@ 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. + +## Handling deep links after auth + +When using deep links, you may want to handle the case where the user opens a deep link that requires authentication. + +Example scenario: + +- User opens a deep link to `myapp://profile` but is not signed in. +- The app shows the `SignIn` screen. +- After the user signs in, you want to navigate them to the `Profile` screen. + +To achieve this, you can set [`UNSTABLE_routeNamesChangeBehavior`](navigator.md#route-names-change-behavior) to `"lastUnhandled"`: + +:::warning + +This API is experimental and may change in a minor release. + +::: + + + + +```js +const RootStack = createNativeStackNavigator({ + // highlight-next-line + UNSTABLE_routeNamesChangeBehavior: 'lastUnhandled', + screens: { + Home: { + if: useIsSignedIn, + screen: HomeScreen, + }, + SignIn: { + if: useIsSignedOut, + screen: SignInScreen, + options: { + title: 'Sign in', + }, + }, + }, +}); +``` + + + + +```js + + {isSignedIn ? ( + + ) : ( + + )} + +``` + + + + +The `UNSTABLE_routeNamesChangeBehavior` option allows you to control how React Navigation handles navigation when the available screens change because of conditions such as authentication state. When `lastUnhandled` is specified, React Navigation will remember the last screen that couldn't be handled, and after the condition changes, it'll automatically navigate to that screen if it's now available. diff --git a/versioned_docs/version-7.x/bottom-tab-navigator.md b/versioned_docs/version-7.x/bottom-tab-navigator.md index 93aacfd13d4..574ffc16b34 100755 --- a/versioned_docs/version-7.x/bottom-tab-navigator.md +++ b/versioned_docs/version-7.x/bottom-tab-navigator.md @@ -152,6 +152,7 @@ It supports the following values: - `initialRoute` - return to initial screen passed in `initialRouteName` prop, if not passed, defaults to the first screen - `order` - return to screen defined before the focused screen - `history` - return to last visited screen in the navigator; if the same screen is visited multiple times, the older entries are dropped from the history +- `fullHistory` - return to last visited screen in the navigator; doesn't drop duplicate entries unlike `history` - this behavior is useful to match how web pages work - `none` - do not handle back button #### `detachInactiveScreens` @@ -325,7 +326,7 @@ function MyTabBar({ navigation }) { ### Options -The following [options](screen-options.md) can be used to configure the screens in the navigator. These can be specified under `screenOptions` prop of `Tab.navigator` or `options` prop of `Tab.Screen`. +The following [options](screen-options.md) can be used to configure the screens in the navigator. These can be specified under `screenOptions` prop of `Tab.Navigator` or `options` prop of `Tab.Screen`. #### `title` @@ -495,7 +496,7 @@ When the tab bar is positioned on the `left` or `right`, it is styled as a sideb ```js const Tabs = createBottomTabNavigator({ screenOptions: { - tabBarPosition: isLargeScreen ? 'left' ? 'bottom', + tabBarPosition: isLargeScreen ? 'left' : 'bottom', }, // ... @@ -586,7 +587,7 @@ Style object for the component wrapping the screen content. ### Header related options -You can find the list of header related options [here](elements.md#header). These [options](screen-options.md) can be specified under `screenOptions` prop of `Tab.navigator` or `options` prop of `Tab.Screen`. You don't have to be using `@react-navigation/elements` directly to use these options, they are just documented in that page. +You can find the list of header related options [here](elements.md#header). These [options](screen-options.md) can be specified under `screenOptions` prop of `Tab.Navigator` or `options` prop of `Tab.Screen`. You don't have to be using `@react-navigation/elements` directly to use these options, they are just documented in that page. In addition to those, the following options are also supported in bottom tabs: 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 6b9c738e350..bfb8c80b619 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/community-libraries.md b/versioned_docs/version-7.x/community-libraries.md new file mode 100755 index 00000000000..74b8fc2e3e0 --- /dev/null +++ b/versioned_docs/version-7.x/community-libraries.md @@ -0,0 +1,31 @@ +--- +id: community-libraries +title: Community libraries +sidebar_label: Community libraries +--- + +This guide lists various community libraries that can be used alongside React Navigation to enhance its functionality. + +:::note + +Please refer to the library's documentation to see which version of React Navigation it supports. + +::: + +## react-native-screen-transitions + +A library that provides customizable screen transition animations for React Navigation's [Native Stack Navigator](native-stack-navigator.md). + +[Repository](https://github.com/eds2002/react-native-screen-transitions) + +### react-navigation-header-buttons + +Helps you to render buttons in the navigation bar and handle the styling so you don't have to. It tries to mimic the appearance of native navbar buttons and attempts to offer a simple interface for you to interact with. + +[Repository](https://github.com/vonovak/react-navigation-header-buttons) + +### react-navigation-props-mapper + +Provides simple HOCs that map react-navigation props to your screen components directly - ie. instead of `const user = this.props.route.params.activeUser`, you'd write `const user = this.props.activeUser`. + +[Repository](https://github.com/vonovak/react-navigation-props-mapper) diff --git a/versioned_docs/version-7.x/community-navigators.md b/versioned_docs/version-7.x/community-navigators.md new file mode 100755 index 00000000000..74c1a3278c8 --- /dev/null +++ b/versioned_docs/version-7.x/community-navigators.md @@ -0,0 +1,31 @@ +--- +id: community-navigators +title: Community navigators +sidebar_label: Community navigators +--- + +This guide lists various community navigators for React Navigation. These navigators offer provide UI components for navigation with the familiar React Navigation API. + +If you're looking to build your own navigator, check out the [custom navigators guide](custom-navigators.md). + +:::note + +Please refer to the library's documentation to see which version of React Navigation it supports. + +::: + +## React Native Bottom Tabs + +This project aims to expose the native Bottom Tabs component to React Native. It exposes SwiftUI's TabView on iOS and the material design tab bar on Android. Using `react-native-bottom-tabs` can bring several benefits, including multi-platform support and a native-feeling tab bar. + +[Documentation](https://oss.callstack.com/react-native-bottom-tabs/) + +[Repository](https://github.com/callstackincubator/react-native-bottom-tabs) + +## BottomNavigation - React Native Paper + +The library provides React Navigation integration for its Material Bottom Tabs. Material Bottom Tabs is a material-design themed tab bar on the bottom of the screen that lets you switch between different routes with animation. + +[Documentation](https://callstack.github.io/react-native-paper/docs/guides/bottom-navigation/) + +[Repository](https://github.com/callstack/react-native-paper) diff --git a/versioned_docs/version-7.x/community-solutions.md b/versioned_docs/version-7.x/community-solutions.md new file mode 100755 index 00000000000..18b8f2c81c6 --- /dev/null +++ b/versioned_docs/version-7.x/community-solutions.md @@ -0,0 +1,35 @@ +--- +id: community-solutions +title: Community solutions +sidebar_label: Community solutions +--- + +This guide lists various community navigation solutions built on top of React Navigation that offer a different API or complement React Navigation in some way. + +:::note + +Please refer to the library's documentation to see which version of React Navigation it supports. + +::: + +## Expo Router + +Expo Router is a file-based router for React Native and web applications built by the Expo team. + +[Documentation](https://docs.expo.dev/router/introduction/) + +[Repository](https://github.com/expo/expo/tree/main/packages/expo-router) + +## Solito + +A wrapper around React Navigation and Next.js that lets you share navigation code across platforms. Also, it provides a set of patterns and examples for building cross-platform apps with React Native + Next.js. + +[Documentation](https://solito.dev/) + +[Repository](https://github.com/nandorojo/solito) + +## Navio + +Navio provides a different API for React Navigation. It's main goal is to improve DX by building the app layout in one place and using the power of TypeScript to provide route names autocompletion. + +[Repository](https://github.com/kanzitelli/rn-navio) diff --git a/versioned_docs/version-7.x/configuring-links.md b/versioned_docs/version-7.x/configuring-links.md index 1f68858f904..7b98cbee23c 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/contributing.md b/versioned_docs/version-7.x/contributing.md index aa31a3cb0b6..ec05c128ca0 100755 --- a/versioned_docs/version-7.x/contributing.md +++ b/versioned_docs/version-7.x/contributing.md @@ -15,23 +15,9 @@ Here are some of the ways to contribute to the project: - [Bug Fixes](#bug-fixes) - [Suggesting a Feature](#suggesting-a-feature) - [Big Pull Requests](#big-pull-requests) -- [Information](#information) - - [Issue Template](#issue-template) - - [Pull Request Template](#pull-request-template) - - [Forking the Repository](#forking-the-repository) - - [Code Review Guidelines](#code-review-guidelines) - - [Run the Example App](#run-the-example-app) - - [Run Tests](#run-tests) And here are a few helpful resources to aid in getting started: -- [Contributing](#contributing) - - [Reporting Bugs](#reporting-bugs) - - [Improving the Documentation](#improving-the-documentation) - - [Responding to Issues](#responding-to-issues) - - [Bug Fixes](#bug-fixes) - - [Suggesting a Feature](#suggesting-a-feature) - - [Big Pull Requests](#big-pull-requests) - [Information](#information) - [Issue Template](#issue-template) - [Pull Request Template](#pull-request-template) @@ -47,7 +33,7 @@ And here are a few helpful resources to aid in getting started: You can't write code without writing the occasional bug. Especially as React Navigation is moving quickly, bugs happen. When you think you've found one here's what to do: 1. Search the existing issues for one like what you're seeing. If you see one, add a 👍 reaction (please no +1 comments). Read through the comments and see if you can provide any more valuable information to the thread -2. If there are no other issues like yours then create a new one. Be sure to follow the [issue template](https://github.com/react-navigation/react-navigation/blob/main/.github/ISSUE_TEMPLATE.md). +2. If there are no other issues like yours then create a new one. Be sure to follow the [issue template](https://github.com/react-navigation/react-navigation/blob/main/.github/ISSUE_TEMPLATE/bug-report.yml). Creating a high quality reproduction is critical. Without it we likely can't fix the bug and, in an ideal situation, you'll find out that it's not actually a bug of the library but simply done incorrectly in your project. Instant bug fix! @@ -96,7 +82,7 @@ The reason we want to do this is to save everyone time. Maybe that feature alrea ### Issue Template -Before submitting an issue, please take a look at the [issue template](https://github.com/react-navigation/react-navigation/blob/main/.github/ISSUE_TEMPLATE.md) and follow it. This is in place to help everyone better understand the issue you're having and reduce the back and forth to get the necessary information. +Before submitting an issue, please take a look at the [issue template](https://github.com/react-navigation/react-navigation/blob/main/.github/ISSUE_TEMPLATE/bug-report.yml) and follow it. This is in place to help everyone better understand the issue you're having and reduce the back and forth to get the necessary information. Yes, it takes time and effort to complete the issue template. But that's the only way to ask high quality questions that actually get responses. @@ -104,7 +90,7 @@ Would you rather take 1 minute to create an incomplete issue report and wait mon ### Pull Request Template -Much like the issue template, the [pull request template](https://github.com/react-navigation/react-navigation/blob/main/.github/PULL_REQUEST.md) lays out instructions to ensure your pull request gets reviewed in a timely manner and reduces the back and forth. Make sure to look it over before you start writing any code. +Much like the issue template, the [pull request template](https://github.com/react-navigation/react-navigation/blob/main/.github/PULL_REQUEST_TEMPLATE.md) lays out instructions to ensure your pull request gets reviewed in a timely manner and reduces the back and forth. Make sure to look it over before you start writing any code. ### Forking the Repository diff --git a/versioned_docs/version-7.x/custom-navigators.md b/versioned_docs/version-7.x/custom-navigators.md index 3d3e05397d8..5fa42e00ff1 100755 --- a/versioned_docs/version-7.x/custom-navigators.md +++ b/versioned_docs/version-7.x/custom-navigators.md @@ -4,26 +4,60 @@ title: Custom navigators sidebar_label: Custom navigators --- -Navigators allow you to define your application's navigation structure. Navigators also render common elements such as headers and tab bars which you can configure. +In essence, a navigator is a React component that takes a set of screens and options, and renders them based on its [navigation state](navigation-state.md), generally with additional UI such as headers, tab bars, or drawers. -Under the hood, navigators are plain React components. +React Navigation provides a few built-in navigators, but they might not always fit your needs if you want a very custom behavior or UI. In such cases, you can build your own custom navigators using React Navigation's APIs. -## Built-in Navigators +A custom navigator behaves just like a built-in navigator, and can be used in the same way. This means you can define screens the same way, use [route](route-object.md) and [navigation](navigation-object.md) objects in your screens, and navigate between screens with familiar API. The navigator will also be able to handle deep linking, state persistence, and other features that built-in navigators support. -We include some commonly needed navigators such as: +## Overview -- [`createStackNavigator`](stack-navigator.md) - Renders one screen at a time and provides transitions between screens. When a new screen is opened it is placed on top of the stack. -- [`createDrawerNavigator`](drawer-navigator.md) - Provides a drawer that slides in from the left of the screen by default. -- [`createBottomTabNavigator`](bottom-tab-navigator.md) - Renders a tab bar that lets the user switch between several screens. -- [`createMaterialTopTabNavigator`](material-top-tab-navigator.md) - Renders tab view which lets the user switch between several screens using swipe gesture or the tab bar. +Under the hood, navigators are plain React components that use the [`useNavigationBuilder`](#usenavigationbuilder) hook. -## API for building custom navigators +The navigator component then uses this state to layout the screens appropriately with any additional UI based on the use case. This component is then wrapped in [`createNavigatorFactory`](#createnavigatorfactory) to create the API for the navigator. -A navigator bundles a router and a view which takes the [navigation state](navigation-state.md) and decides how to render it. We export a `useNavigationBuilder` hook to build custom navigators that integrate with rest of React Navigation. +A very basic example looks like this: + +```js +function MyStackNavigator(props) { + const { state, descriptors, NavigationContent } = useNavigationBuilder( + StackRouter, + props + ); + + const focusedRoute = state.routes[state.index]; + const descriptor = descriptors[focusedRoute.key]; + + return {descriptor.render()}; +} + +export const createMyStackNavigator = createNavigatorFactory(MyStackNavigator); +``` + +Now, we have an already working navigator, even though it doesn't do anything special yet. + +Let's break this down: + +- We define a `MyNavigator` component that contains our navigator logic. This is the component that's rendered when you render `` in your app with the `createMyStackNavigator` factory function. +- We use the `useNavigationBuilder` hook and pass it [`StackRouter`](custom-routers.md#built-in-routers), which would make our navigator behave like a stack navigator. Any other router such as `TabRouter`, `DrawerRouter`, or a custom router can be used here as well. +- The hook returns the [navigation state](navigation-state.md) in the `state` property. This is the current state of the navigator. There's also a `descriptors` object which contains the data and helpers for each screen in the navigator. +- We get the focused route from the state with `state.routes[state.index]` - as `state.index` is the index of the currently focused route in the `state.routes` array. +- Then we get the corresponding descriptor for the focused route with `descriptors[focusedRoute.key]` and call the `render()` method on it to get the React element for the screen. +- The content of the navigator is wrapped in `NavigationContent` to provide appropriate context and wrappers. + +With this, we have a basic stack navigator that renders only the focused screen. Unlike the built-in stack navigator, this doesn't keep unfocused screens rendered. But you can loop through `state.routes` and render all of the screens if you want to keep them mounted. You can also read `descriptor.options` to get the [options](screen-options.md) to handle the screen's title, header, and other options. + +This also doesn't have any additional UI apart from the screen content. There are no gestures or animations. So you're free to add any additional UI, gestures, animations etc. as needed. You can also layout the screens in any way you want, such as rendering them side-by-side or in a grid, instead of stacking them on top of each other like the built-in stack navigator does. + +You can see a more complete example of a custom navigator later in this document. + +## API Definition ### `useNavigationBuilder` -This hook allows a component to hook into React Navigation. It accepts the following arguments: +This hook contains the core logic of a navigator, and is responsible for storing and managing the [navigation state](navigation-state.md). It takes a [router](custom-routers.md) as an argument to know how to handle various navigation actions. It then returns the state and helper methods for the navigator component to use. + +It accepts the following arguments: - `createRouter` - A factory method which returns a router object (e.g. `StackRouter`, `TabRouter`). - `options` - Options for the hook and the router. The navigator should forward its props here so that user can provide props to configure the navigator. By default, the following options are accepted: @@ -56,32 +90,14 @@ 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 ( - {state.routes.map((route) => ( + {state.routes.map((route, index) => ( { @@ -158,6 +174,12 @@ export function createMyNavigator(config) { } ``` +:::note + +We can also do `export const createMyNavigator = createNavigatorFactory(MyNavigator)` directly instead of wrapping in another function. However, the wrapper function is necessary to have proper [TypeScript support](#type-checking-navigators) for the navigator. + +::: + Then it can be used like this: ```js @@ -210,7 +232,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 +244,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 = { @@ -232,28 +253,34 @@ type TabNavigationEventMap = { }; }; +// The type of the navigation object for each screen +type TabNavigationProp< + ParamList extends ParamListBase, + RouteName extends keyof ParamList = keyof ParamList, + NavigatorID extends string | undefined = undefined, +> = NavigationProp< + ParamList, + RouteName, + NavigatorID, + TabNavigationState, + TabNavigationOptions, + TabNavigationEventMap +> & + TabActionHelpers; + // The props accepted by the component is a combination of 3 things type Props = DefaultNavigatorOptions< ParamListBase, + string | undefined, TabNavigationState, TabNavigationOptions, - TabNavigationEventMap + TabNavigationEventMap, + TabNavigationProp > & 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 +288,7 @@ function TabNavigator({ TabActionHelpers, TabNavigationOptions, TabNavigationEventMap - >(TabRouter, { - id, - initialRouteName, - children, - layout, - screenListeners, - screenOptions, - screenLayout, - backBehavior, - }); + >(TabRouter, rest); return ( @@ -321,6 +339,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, @@ -419,3 +438,14 @@ const { state, descriptors, navigation, NavigationContent } = // ... ``` + +:::note + +Customizing built-in navigators like this is an advanced use case and generally not necessary. Consider alternatives such as: + +- [`layout`](navigator.md#layout) prop on navigators to add a wrapper around the navigator +- [`UNSTABLE_router`](navigator.md#router) prop on navigators to customize the router behavior + +Also refer to the navigator's documentation to see if any existing API meets your needs. + +::: diff --git a/versioned_docs/version-7.x/custom-routers.md b/versioned_docs/version-7.x/custom-routers.md index 584e13662a0..86b66d3a1f9 100755 --- a/versioned_docs/version-7.x/custom-routers.md +++ b/versioned_docs/version-7.x/custom-routers.md @@ -150,9 +150,10 @@ The library ships with a few standard routers: ## Customizing Routers -You can reuse a router and override the router functions as per your needs, such as customizing how existing actions are handled, adding additional actions etc. +There are two main ways to customize routers: -See [custom navigators](custom-navigators.md) for details on how to override the router with a custom router in an existing navigator. +- Override an existing router with the [`UNSTABLE_router`](navigator.md#router) prop on navigators +- Customized navigators with a custom router, see [extending navigators](custom-navigators.md#extending-navigators) ### Custom Navigation Actions diff --git a/versioned_docs/version-7.x/deep-linking.md b/versioned_docs/version-7.x/deep-linking.md index 0d955f7332a..2dcac6d9f96 100755 --- a/versioned_docs/version-7.x/deep-linking.md +++ b/versioned_docs/version-7.x/deep-linking.md @@ -18,14 +18,19 @@ While you don't need to use the `linking` prop from React Navigation, and can ha Below, we'll go through required configurations so that the deep link integration works. -## Setup with Expo projects +## Setting up deep links -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: + + + +### Configuring URL scheme + +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" } } ``` @@ -36,76 +41,108 @@ Next, install `expo-linking` which we'd need to get the deep link prefix: npx expo install expo-linking ``` -Then, let's configure React Navigation to use the `scheme` for parsing incoming deep links: - - - +Then you can use `Linking.createURL` to get the prefix for your app: ```js -import * as Linking from 'expo-linking'; +const linking = { + prefixes: [Linking.createURL('/'), +}; +``` -const prefix = Linking.createURL('/'); +See more details below at [Configuring React Navigation](#configuring-react-navigation). -/* content */ +
+Why use `Linking.createURL`? -function App() { - const linking = { - prefixes: [prefix], - }; +It is necessary to use `Linking.createURL` since the scheme differs between the [Expo Dev Client](https://docs.expo.dev/versions/latest/sdk/dev-client/) and standalone apps. - return ; -} -``` +The scheme specified in `app.json` only applies to standalone apps. 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`. The `Linking.createURL` function abstracts it out so that you don't need to specify them manually. - - +
+ +If you are using universal links, you need to add your domain to the prefixes as well: ```js -import * as Linking from 'expo-linking'; +const linking = { + prefixes: [Linking.createURL('/'), 'https://app.example.com'], +}; +``` -const prefix = Linking.createURL('/'); +### Universal Links on iOS -function App() { - const linking = { - prefixes: [prefix], - }; +To set up iOS universal Links in your Expo app, you need to configure your [app config](https://docs.expo.dev/workflow/configuration) to include the associated domains and entitlements: - return ( - Loading...}> - {/* content */} - - ); +```json +{ + "expo": { + "ios": { + "associatedDomains": ["applinks:app.example.com"], + "entitlements": { + "com.apple.developer.associated-domains": ["applinks:app.example.com"] + } + } + } } ``` -
-
+You will also need to setup [Associated Domains](https://developer.apple.com/documentation/Xcode/supporting-associated-domains) on your server. -The reason that is necessary to use `Linking.createURL` is that the scheme will differ depending on whether you're in the client app or in a standalone app. +See [Expo's documentation on iOS Universal Links](https://docs.expo.dev/linking/ios-universal-links/) for more details. -The scheme specified in `app.json` only applies to standalone apps. 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`. The `Linking.createURL` function abstracts it out so that you don't need to specify them manually. +### App Links on Android -If you are using universal links, you need to add your domain to the prefixes as well: +To set up Android App Links in your Expo app, you need to configure your [app config](https://docs.expo.dev/workflow/configuration) to include the `intentFilters`: -```js -const linking = { - prefixes: [Linking.createURL('/'), 'https://app.example.com'], -}; +```json +{ + "expo": { + "android": { + "intentFilters": [ + { + "action": "VIEW", + "autoVerify": true, + "data": [ + { + "scheme": "https", + "host": "app.example.com" + } + ], + "category": ["BROWSABLE", "DEFAULT"] + } + ] + } + } +} ``` -## Set up with bare React Native projects +You will also need to [declare the association](https://developer.android.com/training/app-links/verify-android-applinks#web-assoc) between your website and your intent filters by hosting a Digital Asset Links JSON file. + +See [Expo's documentation on Android App Links](https://docs.expo.dev/linking/android-app-links/) for more details. + +
+ ### 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 add the `LinkingIOS` folder into your header search paths as described [here](https://reactnative.dev/docs/linking-libraries-ios#step-3). Then you'll need to add the following lines to your or `AppDelegate.swift` or `AppDelegate.mm` file: -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: + + + +```swift +func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + return RCTLinkingManager.application(app, open: url, options: options) +} +``` + + + ```objc -// Add the header at the top of the file: #import -// Add this inside `@implementation AppDelegate` above `@end`: - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options @@ -114,10 +151,31 @@ You'll need to link `RCTLinking` to your project by following the steps describe } ``` + + + If your app is using [Universal Links](https://developer.apple.com/ios/universal-links/), you'll need to add the following code as well: + + + +```swift +func application( + _ application: UIApplication, + continue userActivity: NSUserActivity, + restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + return RCTLinkingManager.application( + application, + continue: userActivity, + restorationHandler: restorationHandler + ) + } +``` + + + + ```objc -// Add this inside `@implementation AppDelegate` above `@end`: - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler { @@ -127,17 +185,20 @@ If your app is using [Universal Links](https://developer.apple.com/ios/universal } ``` + + + 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 +218,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 +237,7 @@ If you want to add it manually, open up `SimpleApp/android/app/src/main/AndroidM - +
``` @@ -192,7 +253,7 @@ After adding them, it should look like this: - + @@ -200,21 +261,78 @@ After adding them, it should look like this: - + - + - + ``` Then, you need to [declare the association](https://developer.android.com/training/app-links/verify-android-applinks#web-assoc) between your website and your intent filters by hosting a Digital Asset Links JSON file. + + + +## Configuring React Navigation + +To handle deep links, you need to configure React Navigation to use the `scheme` for parsing incoming deep links: + + + + +```js +const linking = { + prefixes: [ + 'example://', // Or `Linking.createURL('/')` for Expo apps + ], +}; + +function App() { + return ; +} +``` + + + + +```js +const linking = { + prefixes: [ + 'example://', // Or `Linking.createURL('/')` for Expo apps + ], +}; + +function App() { + return ( + Loading...}> + {/* content */} + + ); +} +``` + + + + +If you are using universal links, you need to add your domain to the prefixes as well: + +```js +const linking = { + prefixes: [ + 'example://', // Or `Linking.createURL('/')` for Expo apps + 'https://app.example.com', + ], +}; +``` + +See [configuring links](configuring-links.md) to see further details on how to configure links in React Navigation. + ## Testing deep links Before testing deep links, make sure that you rebuild and install the app in your emulator/simulator/device. @@ -231,7 +349,7 @@ If you're testing on Android, run: npx react-native run-android ``` -If you're using Expo managed workflow and testing on Expo client, you don't need to rebuild the app. However, you will need to use the correct address and port that's printed when you run `expo start` ([see above](#setup-with-expo-projects)), e.g. `exp://127.0.0.1:19000/--/`. +If you're using Expo managed workflow and testing on Expo client, you don't need to rebuild the app. However, you will need to use the correct address and port that's printed when you run `expo start`, e.g. `exp://127.0.0.1:19000/--/`. If you want to test with your custom scheme in your Expo app, you will need rebuild your standalone app by running `expo build:ios -t simulator` or `expo build:android` and install the resulting binaries. @@ -246,7 +364,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 +384,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 +398,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: @@ -289,56 +407,55 @@ Or if using Expo client: adb shell am start -W -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/chat/jane" host.exp.exponent ``` -## Third-party integrations +## Integrating with other tools + +In addition to deep links and universal links with React Native's `Linking` API, you may also want to integrate other tools for handling incoming links, e.g. Push Notifications - so that tapping on a notification can open the app to a specific screen. -React Native's `Linking` isn't the only way to handle deep linking. You might also want to integrate other services such as [Firebase Dynamic Links](https://firebase.google.com/docs/dynamic-links), [Branch](https://help.branch.io/developers-hub/docs/react-native) etc. which provide their own API for getting notified of incoming links. +To achieve this, you'd need to override how React Navigation subscribes to incoming links. To do so, you can provide your own [`getInitialURL`](navigation-container.md#linkinggetinitialurl) and [`subscribe`](navigation-container.md#linkingsubscribe) functions. -To achieve this, you'd need to override how React Navigation subscribes to incoming links. To do so, you can provide your own [`getInitialURL`](navigation-container.md#linkinggetinitialurl) and [`subscribe`](navigation-container.md#linkingsubscribe) functions: +Here is an example integration with [expo-notifications](https://docs.expo.dev/versions/latest/sdk/notifications): -```js name="Third-party integrations" +```js name="Expo Notifications" const linking = { - prefixes: ['myapp://', 'https://myapp.com'], + prefixes: ['example://', 'https://app.example.com'], // Custom function to get the URL which was used to open the app async getInitialURL() { - // First, you would need to get the initial URL from your third-party integration - // The exact usage depend on the third-party SDK you use - // For example, to get the initial URL for Firebase Dynamic Links: - const { isAvailable } = utils().playServicesAvailability; - - if (isAvailable) { - const initialLink = await dynamicLinks().getInitialLink(); + // First, handle deep links + const url = await Linking.getInitialURL(); - if (initialLink) { - return initialLink.url; - } + if (url != null) { + return url; } - // As a fallback, you may want to do the default deep link handling - const url = await Linking.getInitialURL(); + // Handle URL from expo push notifications + const response = await Notifications.getLastNotificationResponseAsync(); - return url; + return response?.notification.request.content.data.url; }, // Custom function to subscribe to incoming links subscribe(listener) { - // Listen to incoming links from Firebase Dynamic Links - const unsubscribeFirebase = dynamicLinks().onLink(({ url }) => { - listener(url); - }); - - // Listen to incoming links from deep linking + // Listen to incoming links for deep links const linkingSubscription = Linking.addEventListener('url', ({ url }) => { listener(url); }); + // Listen to expo push notifications when user interacts with them + const pushNotificationSubscription = + Notifications.addNotificationResponseReceivedListener((response) => { + const url = response.notification.request.content.data.url; + + listener(url); + }); + return () => { // Clean up the event listeners - unsubscribeFirebase(); linkingSubscription.remove(); + pushNotificationSubscription.remove(); }; }, }; @@ -347,47 +464,44 @@ const linking = { -```js name="Third-party integrations" +```js name="Expo Notifications" const linking = { - prefixes: ['myapp://', 'https://myapp.com'], + prefixes: ['example://', 'https://app.example.com'], // Custom function to get the URL which was used to open the app async getInitialURL() { - // First, you would need to get the initial URL from your third-party integration - // The exact usage depend on the third-party SDK you use - // For example, to get the initial URL for Firebase Dynamic Links: - const { isAvailable } = utils().playServicesAvailability; - - if (isAvailable) { - const initialLink = await dynamicLinks().getInitialLink(); + // First, handle deep links + const url = await Linking.getInitialURL(); - if (initialLink) { - return initialLink.url; - } + if (url != null) { + return url; } - // As a fallback, you may want to do the default deep link handling - const url = await Linking.getInitialURL(); + // Handle URL from expo push notifications + const response = await Notifications.getLastNotificationResponseAsync(); - return url; + return response?.notification.request.content.data.url; }, // Custom function to subscribe to incoming links subscribe(listener) { - // Listen to incoming links from Firebase Dynamic Links - const unsubscribeFirebase = dynamicLinks().onLink(({ url }) => { - listener(url); - }); - - // Listen to incoming links from deep linking + // Listen to incoming links for deep links const linkingSubscription = Linking.addEventListener('url', ({ url }) => { listener(url); }); + // Listen to expo push notifications when user interacts with them + const pushNotificationSubscription = + Notifications.addNotificationResponseReceivedListener((response) => { + const url = response.notification.request.content.data.url; + + listener(url); + }); + return () => { // Clean up the event listeners - unsubscribeFirebase(); linkingSubscription.remove(); + pushNotificationSubscription.remove(); }; }, diff --git a/versioned_docs/version-7.x/drawer-layout.md b/versioned_docs/version-7.x/drawer-layout.md index 045b88280f2..0a5d8ddb7d1 100644 --- a/versioned_docs/version-7.x/drawer-layout.md +++ b/versioned_docs/version-7.x/drawer-layout.md @@ -20,56 +20,36 @@ To use this package, open a Terminal in the project root and run: npm install react-native-drawer-layout ``` -Then, you need to install and configure the libraries that are required by the drawer: +The library depends on [`react-native-gesture-handler`](https://docs.swmansion.com/react-native-gesture-handler/) for gestures and [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/) for animations. -1. First, install [`react-native-gesture-handler`](https://docs.swmansion.com/react-native-gesture-handler/) and [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/) (at least version 2 or 3). + + - If you have a Expo managed project, in your project directory, run: +If you have a Expo managed project, in your project directory, run: - ```bash - npx expo install react-native-gesture-handler react-native-reanimated - ``` - - If you have a bare React Native project, in your project directory, run: - - ```bash npm2yarn - npm install react-native-gesture-handler react-native-reanimated - ``` - -2. Configure the Reanimated Babel Plugin in your project following the [installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started). - -3. To finalize the installation of `react-native-gesture-handler`, we need to conditionally import it. To do this, create 2 files: - - ```js title="gesture-handler.native.js" - // Only import react-native-gesture-handler on native platforms - import 'react-native-gesture-handler'; - ``` - - ```js title="gesture-handler.js" - // Don't import react-native-gesture-handler on web - ``` - - Now, add the following at the **top** (make sure it's at the top and there's nothing else before it) of your entry file, such as `index.js` or `App.js`: - - ```js - import './gesture-handler'; - ``` +```bash +npx expo install react-native-gesture-handler react-native-reanimated react-native-worklets +``` - Since the drawer layout doesn't use `react-native-gesture-handler` on Web, this avoids unnecessarily increasing the bundle size. + + - :::warning +If you have a bare React Native project, in your project directory, run: - If you are building for Android or iOS, do not skip this step, or your app may crash in production even if it works fine in development. This is not applicable to other platforms. +```bash npm2yarn +npm install react-native-gesture-handler react-native-reanimated react-native-worklets +``` - ::: +After installation, configure the Reanimated Babel Plugin in your project following the [installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started). -4. If you're on a Mac and developing for iOS, you also need to install the pods (via [Cocoapods](https://cocoapods.org/)) to complete the linking. + + - ```bash - npx pod-install ios - ``` +If you're on a Mac and developing for iOS, you also need to install [pods](https://cocoapods.org/) to complete the linking. -We're done! Now you can build and run the app on your device/simulator. +```bash +npx pod-install ios +``` ## Quick start diff --git a/versioned_docs/version-7.x/drawer-navigator.md b/versioned_docs/version-7.x/drawer-navigator.md index 78fd52c3e05..9f04798ea15 100644 --- a/versioned_docs/version-7.x/drawer-navigator.md +++ b/versioned_docs/version-7.x/drawer-navigator.md @@ -20,54 +20,36 @@ To use this navigator, ensure that you have [`@react-navigation/native` and its npm install @react-navigation/drawer ``` -Then, you need to install and configure the libraries that are required by the drawer navigator: +The navigator depends on [`react-native-gesture-handler`](https://docs.swmansion.com/react-native-gesture-handler/) for gestures and [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/) for animations. -1. First, install [`react-native-gesture-handler`](https://docs.swmansion.com/react-native-gesture-handler/) and [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/) (at least version 2 or 3). + + - If you have a Expo managed project, in your project directory, run: +If you have a Expo managed project, in your project directory, run: - ```bash - npx expo install react-native-gesture-handler react-native-reanimated - ``` - - If you have a bare React Native project, in your project directory, run: - - ```bash npm2yarn - npm install react-native-gesture-handler react-native-reanimated - ``` - -2. Configure the Reanimated Babel Plugin in your project following the [installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started). - -3. To finalize the installation of `react-native-gesture-handler`, we need to conditionally import it. To do this, create 2 files: - - ```js title="gesture-handler.native.js" - // Only import react-native-gesture-handler on native platforms - import 'react-native-gesture-handler'; - ``` - - ```js title="gesture-handler.js" - // Don't import react-native-gesture-handler on web - ``` - - Now, add the following at the **top** (make sure it's at the top and there's nothing else before it) of your entry file, such as `index.js` or `App.js`: +```bash +npx expo install react-native-gesture-handler react-native-reanimated react-native-worklets +``` - ```js - import './gesture-handler'; - ``` + + - Since the drawer navigator doesn't use `react-native-gesture-handler` on Web, this avoids unnecessarily increasing the bundle size. +If you have a bare React Native project, in your project directory, run: - :::warning +```bash npm2yarn +npm install react-native-gesture-handler react-native-reanimated react-native-worklets +``` - If you are building for Android or iOS, do not skip this step, or your app may crash in production even if it works fine in development. This is not applicable to other platforms. +After installation, configure the Reanimated Babel Plugin in your project following the [installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started). - ::: + + -4. If you're on a Mac and developing for iOS, you also need to install the pods (via [Cocoapods](https://cocoapods.org/)) to complete the linking. +If you're on a Mac and developing for iOS, you also need to install [pods](https://cocoapods.org/) to complete the linking. - ```bash - npx pod-install ios - ``` +```bash +npx pod-install ios +``` ## Usage @@ -203,6 +185,7 @@ It supports the following values: - `initialRoute` - return to initial screen passed in `initialRouteName` prop, if not passed, defaults to the first screen - `order` - return to screen defined before the focused screen - `history` - return to last visited screen in the navigator; if the same screen is visited multiple times, the older entries are dropped from the history +- `fullHistory` - return to last visited screen in the navigator; doesn't drop duplicate entries unlike `history` - this behavior is useful to match how web pages work - `none` - do not handle back button #### `defaultStatus` @@ -304,7 +287,7 @@ To use the custom component, we need to pass it in the `drawerContent` prop: ### Options -The following [options](screen-options.md) can be used to configure the screens in the navigator. These can be specified under `screenOptions` prop of `Drawer.navigator` or `options` prop of `Drawer.Screen`. +The following [options](screen-options.md) can be used to configure the screens in the navigator. These can be specified under `screenOptions` prop of `Drawer.Navigator` or `options` prop of `Drawer.Screen`. #### `title` @@ -581,7 +564,7 @@ It only works when there is a stack navigator (e.g. [stack navigator](stack-navi ### Header related options -You can find the list of header related options [here](elements.md#header). These [options](screen-options.md) can be specified under `screenOptions` prop of `Drawer.navigator` or `options` prop of `Drawer.Screen`. You don't have to be using `@react-navigation/elements` directly to use these options, they are just documented in that page. +You can find the list of header related options [here](elements.md#header). These [options](screen-options.md) can be specified under `screenOptions` prop of `Drawer.Navigator` or `options` prop of `Drawer.Screen`. You don't have to be using `@react-navigation/elements` directly to use these options, they are just documented in that page. In addition to those, the following options are also supported in drawer: diff --git a/versioned_docs/version-7.x/getting-started.md b/versioned_docs/version-7.x/getting-started.md index 1d0971acf8a..2e7d1b8dc5b 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 @@ -42,17 +42,20 @@ Otherwise, you can follow the instructions below to install React Navigation int ## Installation -Install the required packages in your React Native project: +The `@react-navigation/native` package contains the core functionality of React Navigation. + +In your project directory, run: ```bash npm2yarn npm install @react-navigation/native ``` -React Navigation is made up of some core utilities and those are then used by navigators to create the navigation structure in your app. Don't worry too much about this for now, it'll become clear soon enough! To frontload the installation work, let's also install and configure dependencies used by most navigators, then we can move forward with starting to write some code. +### Installing dependencies -The libraries we will install now are [`react-native-screens`](https://github.com/software-mansion/react-native-screens) and [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context). If you already have these libraries installed and at the latest version, you are done here! Otherwise, read on. +Let's also install and configure dependencies used by most navigators. The libraries we will install now are [`react-native-screens`](https://github.com/software-mansion/react-native-screens) and [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context). -### Installing dependencies into an Expo managed project + + In your project directory, run: @@ -60,11 +63,10 @@ In your project directory, run: npx expo install react-native-screens react-native-safe-area-context ``` -This will install versions of these libraries that are compatible. - -You can now continue to ["Hello React Navigation"](hello-react-navigation.md) to start writing some code. +This will install versions of these libraries that are compatible with your Expo SDK version. -### Installing dependencies into a bare React Native project + + In your project directory, run: @@ -72,50 +74,61 @@ In your project directory, run: npm install react-native-screens react-native-safe-area-context ``` -:::note - -You might get warnings related to peer dependencies after installation. They are usually caused by incorrect version ranges specified in some packages. You can safely ignore most warnings as long as your app builds. - -::: - If you're on a Mac and developing for iOS, you need to install the pods (via [Cocoapods](https://cocoapods.org/)) to complete the linking. ```bash npx pod-install ios ``` -`react-native-screens` package requires one additional configuration step to properly -work on Android devices. Edit `MainActivity.kt` or `MainActivity.java` file which is located under `android/app/src/main/java//`. +#### Configuring `react-native-screens` on Android -Add the highlighted code to the body of `MainActivity` class: +[`react-native-screens`](https://github.com/software-mansion/react-native-screens) requires one additional configuration to properly work on Android. + +Edit `MainActivity.kt` or `MainActivity.java` file under `android/app/src/main/java//`, and add the highlighted code to the body of `MainActivity` class: ```kotlin +// highlight-start +import android.os.Bundle +import com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory +// highlight-end + +// ... + class MainActivity: ReactActivity() { // ... - // highlight-start override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(null) + // highlight-start + supportFragmentManager.fragmentFactory = RNScreensFragmentFactory() + super.onCreate(savedInstanceState) + // highlight-end } - // highlight-end // ... } ``` - - + + ```java +// highlight-start +import android.os.Bundle; +import com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory; +// highlight-end + +// ... + public class MainActivity extends ReactActivity { // ... - // highlight-start @Override protected void onCreate(Bundle savedInstanceState) { - super.onCreate(null); + // highlight-start + getSupportFragmentManager().setFragmentFactory(new RNScreensFragmentFactory()); + super.onCreate(savedInstanceState); + // highlight-end } - // highlight-end // ... } ``` @@ -123,19 +136,25 @@ public class MainActivity extends ReactActivity { -and make sure to add the following import statement at the top of this file below your package statement: +This change is required to avoid crashes related to View state being not persisted consistently across Activity restarts. -```java -import android.os.Bundle; -``` +#### Opting-out of predictive back on Android -This change is required to avoid crashes related to View state being not persisted consistently across Activity restarts. +React Navigation doesn't yet support Android's predictive back gesture. Disabling it is necessary for the system back gesture to work properly with React Navigation. -:::info +To opt out, in `AndroidManifest.xml`, in the `` tag (or `` tag to opt-out at activity level), set the `android:enableOnBackInvokedCallback` flag to `false`: -When you use a navigator (such as stack navigator), you'll need to follow the installation instructions of that navigator for any additional dependencies. If you're getting an error "Unable to resolve module", you need to install that module in your project. +```xml + + + +``` -::: + + ## Setting up React Navigation @@ -143,36 +162,24 @@ Once you've installed and configured the dependencies, you can move on to settin When using React Navigation, you configure [**navigators**](glossary-of-terms.md#navigator) in your app. Navigators handle the transition between screens in your app and provide UI such as header, tab bar etc. -There are 2 primary ways to configure the navigators: - -### Static configuration - -The static configuration API has reduced boilerplate and simplifies things such as TypeScript types and deep linking. If you're starting a new project or are new to React Navigation, this is the **recommended way** to set up your app. If you need more flexibility in the future, you can always mix and match with the dynamic configuration. +:::info -Continue to ["Hello React Navigation"](hello-react-navigation.md?config=static) to start writing some code with the static API. +When you use a navigator (such as stack navigator), you'll need to follow the installation instructions of that navigator for any additional dependencies. -### Dynamic configuration +::: -The dynamic configuration allows for more flexibility but requires more boilerplate and configuration (e.g. for deep links, typescript etc.). +There are 2 primary ways to configure the navigators: -To get started with dynamic configuration, first, we need to wrap your app in `NavigationContainer`. Usually, you'd do this in your entry file, such as `index.js` or `App.js`: +### Static configuration -```js -import * as React from 'react'; -// highlight-next-line -import { NavigationContainer } from '@react-navigation/native'; +The static configuration API lets you write your configuration in an object, and is defined statically, though some aspects of the configuration can still can be changed dynamically. This has reduced boilerplate and simplifies things such as TypeScript types and deep linking. -export default function App() { - return ( - {/* Rest of your app code */} - ); -} -``` +If you're starting a new project or are new to React Navigation, this is the **recommended way** to set up your app. If you need more flexibility in the future, you can always mix and match with the dynamic configuration. -:::warning +Continue to ["Hello React Navigation"](hello-react-navigation.md?config=static) to start writing some code with the static API. -In a typical React Native app, the `NavigationContainer` should be only used once in your app at the root. You shouldn't nest multiple `NavigationContainer`s unless you have a specific use case for them. +### Dynamic configuration -::: +The dynamic configuration API lets you write your configuration in React components, and can change at runtime based on state or props. This allows for more flexibility but requires significantly more boilerplate and configuration for Typescript types, deep linking etc. Continue to ["Hello React Navigation"](hello-react-navigation.md?config=dynamic) to start writing some code with the dynamic API. diff --git a/versioned_docs/version-7.x/group.md b/versioned_docs/version-7.x/group.md index eecb2620ac7..4a7b6583abb 100644 --- a/versioned_docs/version-7.x/group.md +++ b/versioned_docs/version-7.x/group.md @@ -225,7 +225,9 @@ See [Options for screens](screen-options.md) for more details and examples. ### Screen layout -A screen layout is a wrapper around each screen in the group. It makes it easier to provide things such as a common error boundary and suspense fallback for all screens in a group: +A screen layout is a wrapper around each screen in the group. It makes it easier to provide things such as an error boundary and suspense fallback for all screens in the group, or wrap each screen with additional UI. + +It takes a function that returns a React element: diff --git a/versioned_docs/version-7.x/handling-safe-area.md b/versioned_docs/version-7.x/handling-safe-area.md index 207dbbc5002..225bfa87c1d 100755 --- a/versioned_docs/version-7.x/handling-safe-area.md +++ b/versioned_docs/version-7.x/handling-safe-area.md @@ -28,7 +28,7 @@ While React Native exports a `SafeAreaView` component, this component only suppo :::warning -The `react-native-safe-area-context` library also exports a `SafeAreaView` component. While it works on Android, it also has the same issues related to jumpy behavior when animating. So we recommend always using the `useSafeAreaInsets` hook instead and avoid using the `SafeAreaView` component. +The `react-native-safe-area-context` library also exports a `SafeAreaView` component. While it works on Android, it also has the same issues with jumpy behavior on vertical animations. In addition, the `SafeAreaView` component and `useSafeAreaInsets` hook can update at different times, resulting in flickering when using them together. So we recommend always using the `useSafeAreaInsets` hook instead and avoid using the `SafeAreaView` component for consistent behavior. ::: @@ -473,6 +473,6 @@ Similarly, you could apply these paddings in `contentContainerStyle` of `FlatLis ## Summary -- Use `useSafeAreaInsets` hook from `react-native-safe-area-context` instead of `SafeAreaView` component +- Use [`useSafeAreaInsets`](https://appandflow.github.io/react-native-safe-area-context/api/use-safe-area-insets) hook from `react-native-safe-area-context` instead of [`SafeAreaView`](https://reactnative.dev/docs/safeareaview) component - Don't wrap your whole app in `SafeAreaView`, instead apply the styles to content inside your screens - Apply only specific insets using the `useSafeAreaInsets` hook for more control diff --git a/versioned_docs/version-7.x/header-buttons.md b/versioned_docs/version-7.x/header-buttons.md index 025baecf061..8861a6120b4 100755 --- a/versioned_docs/version-7.x/header-buttons.md +++ b/versioned_docs/version-7.x/header-buttons.md @@ -297,6 +297,6 @@ Generally, this is what you want. But it's possible that in some circumstances t ## Summary -- You can set buttons in the header through the `headerLeft` and `headerRight` properties in `options`. -- The back button is fully customizable with `headerLeft`, but if you just want to change the title or image, there are other `options` for that — `headerBackTitle`, `headerBackTitleStyle`, and `headerBackImageSource`. -- You can use a callback for the options prop to access `navigation` and `route` objects. +- You can set buttons in the header through the [`headerLeft`](elements.md#headerleft) and [`headerRight`](elements.md#headerright) properties in [`options`](screen-options.md). +- The back button is fully customizable with `headerLeft`, but if you only want to change the title or image, there are other `options` for that — [`headerBackTitle`](native-stack-navigator.md#headerbacktitle), [`headerBackTitleStyle`](native-stack-navigator.md#headerbacktitlestyle), and [`headerBackImageSource`](native-stack-navigator.md#headerbackimagesource). +- You can use a callback for the options prop to access [`navigation`](navigation-object.md) and [`route`](route-object.md) objects. diff --git a/versioned_docs/version-7.x/headers.md b/versioned_docs/version-7.x/headers.md index 9bfd242ea5e..04f18811603 100755 --- a/versioned_docs/version-7.x/headers.md +++ b/versioned_docs/version-7.x/headers.md @@ -689,6 +689,6 @@ You can read the full list of available `options` for screens inside of a native ## Summary -- You can customize the header inside of the `options` property of your screens. Read the full list of options [in the API reference](native-stack-navigator.md#options). -- The `options` property can be an object or a function. When it is a function, it is provided with an object with the `navigation` and `route` objects. -- You can also specify shared `screenOptions` in the stack navigator configuration when you initialize it. This will apply to all screens in the navigator. +- You can customize the header inside of the [`options`](screen-options.md) property of your screens. Read the full list of options [in the API reference](native-stack-navigator.md#options). +- The `options` property can be an object or a function. When it is a function, it is provided with an object with the [`navigation`](navigation-object.md) and [`route`](route-object.md) objects. +- You can also specify shared [`screenOptions`](screen-options.md#screenoptions-prop-on-the-navigator) in the stack navigator configuration when you initialize it. This will apply to all screens in the navigator. diff --git a/versioned_docs/version-7.x/hello-react-navigation.md b/versioned_docs/version-7.x/hello-react-navigation.md index 5f4bfeaf0db..74e4e98f602 100755 --- a/versioned_docs/version-7.x/hello-react-navigation.md +++ b/versioned_docs/version-7.x/hello-react-navigation.md @@ -42,7 +42,7 @@ npm install @react-navigation/elements `createNativeStackNavigator` is a function that takes a configuration object containing the screens and customization options. The screens are React Components that render the content displayed by the navigator. -`createStaticNavigation` is a function that takes the navigator defined earlier and returns a component that can be rendered in the app. It's only called once in the app. +`createStaticNavigation` is a function that takes the navigator defined earlier and returns a component that can be rendered in the app. It's only called once in the app. Usually, we'd render the returned component at the root of our app, which is usually the component exported from `App.js`, `App.tsx` etc., or used with `AppRegistry.registerComponent`, `Expo.registerRootComponent` etc. ```js name="Native Stack Example" snack // In App.js in a new project @@ -73,12 +73,18 @@ export default function App() { } ``` +:::warning + +In a typical React Native app, the `createStaticNavigation` function should be only used once in your app at the root. + +::: + `createNativeStackNavigator` is a function that returns an object containing 2 properties: `Screen` and `Navigator`. Both of them are React components used for configuring the navigator. The `Navigator` should contain `Screen` elements as its children to define the configuration for routes. -`NavigationContainer` is a component that manages our navigation tree and contains the [navigation state](navigation-state.md). This component must wrap all the navigators in the app. Usually, we'd render this component at the root of our app, which is usually the component exported from `App.js`. +`NavigationContainer` is a component that manages our navigation tree and contains the [navigation state](navigation-state.md). This component must wrap all the navigators in the app. Usually, we'd render this component at the root of our app, which is usually the component exported from `App.js`, `App.tsx` etc., or used with `AppRegistry.registerComponent`, `Expo.registerRootComponent` etc. ```js name="Native Stack Example" snack // In App.js in a new project @@ -115,6 +121,12 @@ export default function App() { } ``` +:::warning + +In a typical React Native app, the `NavigationContainer` should be only used once in your app at the root. You shouldn't nest multiple `NavigationContainer`s unless you have a specific use case for them. + +::: + @@ -525,19 +537,19 @@ If you are using TypeScript, you will need to specify the types accordingly. You - React Native doesn't have a built-in API for navigation like a web browser does. React Navigation provides this for you, along with the iOS and Android gestures and animations to transition between screens. -- `createNativeStackNavigator` is a function that takes the screens configuration and renders our content. +- [`createNativeStackNavigator`](native-stack-navigator.md) is a function that takes the screens configuration and renders our content. - Each property under screens refers to the name of the route, and the value is the component to render for the route. -- To specify what the initial route in a stack is, provide an `initialRouteName` option for the navigator. -- To specify screen-specific options, we can specify an `options` property, and for common options, we can specify `screenOptions`. +- To specify what the initial route in a stack is, provide an [`initialRouteName`](navigator.md#initial-route-name) option for the navigator. +- To specify screen-specific options, we can specify an [`options`](screen-options.md#options-prop-on-screen) property, and for common options, we can specify [`screenOptions`](screen-options.md#screenoptions-prop-on-the-navigator). - React Native doesn't have a built-in API for navigation like a web browser does. React Navigation provides this for you, along with the iOS and Android gestures and animations to transition between screens. -- `Stack.Navigator` is a component that takes route configuration as its children with additional props for configuration and renders our content. -- Each `Stack.Screen` component takes a `name` prop which refers to the name of the route and `component` prop which specifies the component to render for the route. These are the 2 required props. -- To specify what the initial route in a stack is, provide an `initialRouteName` as the prop for the navigator. -- To specify screen-specific options, we can pass an `options` prop to `Stack.Screen`, and for common options, we can pass `screenOptions` to `Stack.Navigator`. +- [`Stack.Navigator`](native-stack-navigator.md) is a component that takes route configuration as its children with additional props for configuration and renders our content. +- Each [`Stack.Screen`](screen.md) component takes a [`name`](screen.md#name) prop which refers to the name of the route and [`component`](screen.md#component) prop which specifies the component to render for the route. These are the 2 required props. +- To specify what the initial route in a stack is, provide an [`initialRouteName`](navigator.md#initial-route-name) as the prop for the navigator. +- To specify screen-specific options, we can pass an [`options`](screen-options.md#options-prop-on-screen) prop to `Stack.Screen`, and for common options, we can pass [`screenOptions`](screen-options.md#screenoptions-prop-on-the-navigator) to `Stack.Navigator`. diff --git a/versioned_docs/version-7.x/material-top-tab-navigator.md b/versioned_docs/version-7.x/material-top-tab-navigator.md index 7324ed4a8a7..3cf4b61bd5d 100755 --- a/versioned_docs/version-7.x/material-top-tab-navigator.md +++ b/versioned_docs/version-7.x/material-top-tab-navigator.md @@ -20,7 +20,10 @@ To use this navigator, ensure that you have [`@react-navigation/native` and its npm install @react-navigation/material-top-tabs ``` -Then, you need to install [`react-native-pager-view`](https://github.com/callstack/react-native-pager-view) which is required by the navigator. +The navigator depends on [`react-native-pager-view`](https://github.com/callstack/react-native-pager-view) for rendering the pages. + + + If you have a Expo managed project, in your project directory, run: @@ -28,13 +31,19 @@ If you have a Expo managed project, in your project directory, run: npx expo install react-native-pager-view ``` + + + If you have a bare React Native project, in your project directory, run: ```bash npm2yarn npm install react-native-pager-view ``` -If you're on a Mac and developing for iOS, you also need to install the pods (via [Cocoapods](https://cocoapods.org/)) to complete the linking. + + + +If you're on a Mac and developing for iOS, you also need to install [pods](https://cocoapods.org/) to complete the linking. ```bash npx pod-install ios diff --git a/versioned_docs/version-7.x/modal.md b/versioned_docs/version-7.x/modal.md index b55c05b78d5..e9921b5d235 100755 --- a/versioned_docs/version-7.x/modal.md +++ b/versioned_docs/version-7.x/modal.md @@ -185,7 +185,7 @@ Instead of specifying this option for a group, it's also possible to specify it ## Summary -- To change the type of transition on a stack navigator you can use the `presentation` option. +- To change the type of transition on a stack navigator you can use the [`presentation`](native-stack-navigator.md#presentation) option. - When `presentation` is set to `modal`, the screens behave like a modal, i.e. they have a bottom to top transition and may show part of the previous screen in the background. - Setting `presentation: 'modal'` on a group makes all the screens in the group modals, so to use non-modal transitions on other screens, we add another group with the default configuration. diff --git a/versioned_docs/version-7.x/more-resources.md b/versioned_docs/version-7.x/more-resources.md index b76b7398f9d..8e71db8cf3a 100755 --- a/versioned_docs/version-7.x/more-resources.md +++ b/versioned_docs/version-7.x/more-resources.md @@ -1,7 +1,7 @@ --- id: more-resources -title: More Resources -sidebar_label: More Resources +title: More resources +sidebar_label: More resources --- ## Talks diff --git a/versioned_docs/version-7.x/native-bottom-tab-navigator.md b/versioned_docs/version-7.x/native-bottom-tab-navigator.md new file mode 100755 index 00000000000..d712521ff07 --- /dev/null +++ b/versioned_docs/version-7.x/native-bottom-tab-navigator.md @@ -0,0 +1,469 @@ +--- +id: native-bottom-tab-navigator +title: Native Bottom Tabs Navigator +sidebar_label: Native Bottom Tabs +--- + +:::warning + +This navigator is currently experimental. The API will change in future releases. + +Currently only iOS and Android are supported. Use [`createBottomTabNavigator`](bottom-tab-navigator.md) for web support. + +::: + +Native Bottom Tabs displays screens with a tab bar to switch between them. + + + + + +The navigator uses native components on iOS and Android for better platform integration. On iOS, it uses `UITabBarController` and on Android, it uses `BottomNavigationView`. + +## Installation + +To use this navigator, ensure that you have [`@react-navigation/native` and its dependencies (follow this guide)](getting-started.md), then install [`@react-navigation/bottom-tabs`](https://github.com/react-navigation/react-navigation/tree/main/packages/bottom-tabs): + +```bash npm2yarn +npm install @react-navigation/bottom-tabs +``` + +The navigator requires React Native 0.79 or above is required. If you're using [Expo](https://expo.dev/), it requires SDK 53 or above. + +## Usage + +To use this navigator, import it from `@react-navigation/bottom-tabs/unstable`: + + + + +```js name="Bottom Tab Navigator" +import { createNativeBottomTabNavigator } from '@react-navigation/bottom-tabs/unstable'; + +const MyTabs = createNativeBottomTabNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +``` + + + + +```js name="Bottom Tab Navigator" +import { createNativeBottomTabNavigator } from '@react-navigation/bottom-tabs/unstable'; + +const Tab = createNativeBottomTabNavigator(); + +function MyTabs() { + return ( + + + + + ); +} +``` + + + + +## Notes + +- Liquid Glass effect on iOS 26+ requires your app to be built with Xcode 26 or above. +- On Android, at most 5 tabs are supported. This is a limitation of the underlying native component. + +## API Definition + +### Props + +In addition to the [common props](navigator.md#configuration) shared by all navigators, the bottom tab navigator accepts the following additional props: + +#### `backBehavior` + +This controls what happens when `goBack` is called in the navigator. This includes pressing the device's back button or back gesture on Android. + +It supports the following values: + +- `firstRoute` - return to the first screen defined in the navigator (default) +- `initialRoute` - return to initial screen passed in `initialRouteName` prop, if not passed, defaults to the first screen +- `order` - return to screen defined before the focused screen +- `history` - return to last visited screen in the navigator; if the same screen is visited multiple times, the older entries are dropped from the history +- `fullHistory` - return to last visited screen in the navigator; doesn't drop duplicate entries unlike `history` - this behavior is useful to match how web pages work +- `none` - do not handle back button + +### Options + +The following [options](screen-options.md) can be used to configure the screens in the navigator. These can be specified under `screenOptions` prop of `Tab.Navigator` or `options` prop of `Tab.Screen`. + +#### `title` + +Generic title that can be used as a fallback for `headerTitle` and `tabBarLabel`. + +#### `tabBarSystemItem` + +Uses iOS built-in tab bar items with standard iOS styling and localized titles. Supported values: + +- `bookmarks` +- `contacts` +- `downloads` +- `favorites` +- `featured` +- `history` +- `more` +- `mostRecent` +- `mostViewed` +- `recents` +- `search` +- `topRated` + +The [`tabBarIcon`](#tabbaricon) and [`tabBarLabel`](#tabbarlabel) options will override the icon and label from the system item. If you want to keep the system behavior on iOS, but need to provide icon and label for other platforms, use `Platform.OS` or `Platform.select` to conditionally set `undefined` for `tabBarIcon` and `tabBarLabel` on iOS. + +##### Search tab on iOS 26+ + +The `tabBarSystemItem` option has special styling and behavior when set to `search` on iOS 26+. + +Additionally, when the `search` tab is selected, the tab bar transforms into a search field if the screen in the tab navigator or a nested [native stack navigator](native-stack-navigator.md) has [`headerSearchBarOptions`](native-stack-navigator.md#headersearchbaroptions) configured and the native header is shown with [`headerShown: true`](native-stack-navigator.md#headershown). This won't work if a custom header is provided with the `header` option. + +Example: + +```js +tabBarSystemItem: 'search', +headerShown: true, +headerSearchBarOptions: { + placeholder: 'Search', +}, +``` + + + +#### `tabBarLabel` + +Title string of a tab displayed in the tab bar. + +Overrides the label provided by [`tabBarSystemItem`](#tabbarsystemitem) on iOS. + +If not provided, or set to `undefined`: + +- The system values are used if [`tabBarSystemItem`](#tabbarsystemitem) is set on iOS. +- Otherwise, it falls back to the [`title`](#title) or route name. + +#### `tabBarLabelVisibilityMode` + +The label visibility mode for the tab bar items. Supported values: + +- `auto` - the system decides when to show or hide labels +- `selected` - labels are shown only for the selected tab +- `labeled` - labels are always shown +- `unlabeled` - labels are never shown + +Only supported on Android. + +#### `tabBarLabelStyle` + +Style object for the tab label. Supported properties: + +- `fontFamily` +- `fontSize` +- `fontWeight` +- `fontStyle` + +Example: + +```js +tabBarLabelStyle: { + fontSize: 16, + fontFamily: 'Georgia', + fontWeight: 300, +}, +``` + +#### `tabBarIcon` + +Icon to display for the tab. It overrides the icon provided by [`tabBarSystemItem`](#tabbarsystemitem) on iOS. + +It can be an icon object or a function that given `{ focused: boolean, color: string, size: number }` returns an icon object. + +The icon can be of following types: + +- Local image - Supported on iOS and Android + + ```js + tabBarIcon: { + type: 'image', + source: require('./path/to/icon.png'), + } + ``` + + On iOS, you can additionally pass a `tinted` property to control whether the icon should be tinted with the active/inactive color: + + ```js + tabBarIcon: { + type: 'image', + source: require('./path/to/icon.png'), + tinted: false, + } + ``` + + The image is tinted by default. + +- [SF Symbols](https://developer.apple.com/sf-symbols/) name - Supported on iOS + + ```js + tabBarIcon: { + type: 'sfSymbol', + name: 'heart', + } + ``` + +- [Drawable resource](https://developer.android.com/guide/topics/resources/drawable-resource) name - Supported on Android + + ```js + tabBarIcon: { + type: 'drawableResource', + name: 'sunny', + } + ``` + +To render different icons for active and inactive states, you can use a function: + +```js +tabBarIcon: ({ focused }) => { + return { + type: 'sfSymbol', + name: focused ? 'heart' : 'heart-outline', + }; +}, +``` + +This is only supported on iOS. On Android, the icon specified for inactive state will be used for both active and inactive states. + +To provide different icons for different platforms, you can use [`Platform.select`](https://reactnative.dev/docs/platform-specific-code): + +```js +tabBarIcon: Platform.select({ + ios: { + type: 'sfSymbol', + name: 'heart', + }, + android: { + type: 'drawableResource', + name: 'heart_icon', + }, +}); +``` + +#### `tabBarBadge` + +Text to show in a badge on the tab icon. Accepts a `string` or a `number`. + +#### `tabBarBadgeStyle` + +Style for the badge on the tab icon. Supported properties: + +- `backgroundColor` +- `color` + +Example: + +```js +tabBarBadgeStyle: { + backgroundColor: 'yellow', + color: 'black', +}, +``` + +Only supported on Android. + +#### `tabBarActiveTintColor` + +Color for the icon and label in the active tab. + +#### `tabBarInactiveTintColor` + +Color for the icon and label in the inactive tabs. + +Only supported on Android. + +#### `tabBarActiveIndicatorColor` + +Background color of the active indicator. + +Only supported on Android. + +#### `tabBarActiveIndicatorEnabled` + +Whether the active indicator should be used. Defaults to `true`. + +Only supported on Android. + +#### `tabBarRippleColor` + +Color of the ripple effect when pressing a tab. + +Only supported on Android. + +#### `tabBarStyle` + +Style object for the tab bar. Supported properties: + +- `backgroundColor` - Only supported on Android and iOS 18 and below. +- `shadowColor` - Only supported on iOS 18 and below. + +On iOS 26+, the background color automatically changes based on the content behind the tab bar and can't be overridden. + +#### `tabBarBlurEffect` + +Blur effect applied to the tab bar on iOS 18 and lower when tab screen is selected. + +Supported values: + +- `none` - no blur effect +- `systemDefault` - default blur effect applied by the system +- `extraLight` +- `light` +- `dark` +- `regular` +- `prominent` +- `systemUltraThinMaterial` +- `systemThinMaterial` +- `systemMaterial` +- `systemThickMaterial` +- `systemChromeMaterial` +- `systemUltraThinMaterialLight` +- `systemThinMaterialLight` +- `systemMaterialLight` +- `systemThickMaterialLight` +- `systemChromeMaterialLight` +- `systemUltraThinMaterialDark` +- `systemThinMaterialDark` +- `systemMaterialDark` +- `systemThickMaterialDark` +- `systemChromeMaterialDark` + +Defaults to `systemDefault`. + +Only supported on iOS 18 and below. + +#### `tabBarControllerMode` + +The display mode for the tab bar. Supported values: + +- `auto` - the system sets the display mode based on the tab’s content +- `tabBar` - the system displays the content only as a tab bar +- `tabSidebar` - the tab bar is displayed as a sidebar + +Only supported on iOS 18 and above. Not supported on tvOS. + +#### `tabBarMinimizeBehavior` + +The minimize behavior for the tab bar. Supported values: + +- `auto` - resolves to the system default minimize behavior +- `never` - the tab bar does not minimize +- `onScrollDown` - the tab bar minimizes when scrolling down and + expands when scrolling back up +- `onScrollUp` - the tab bar minimizes when scrolling up and expands + when scrolling back down + +Only supported on iOS 26 and above. + + + +#### `lazy` + +Whether this screen should render only after the first time it's accessed. Defaults to `true`. Set it to `false` if you want to render the screen on the initial render of the navigator. + +#### `popToTopOnBlur` + +Boolean indicating whether any nested stack should be popped to the top of the stack when navigating away from this tab. Defaults to `false`. + +It only works when there is a stack navigator (e.g. [stack navigator](stack-navigator.md) or [native stack navigator](native-stack-navigator.md)) nested under the tab navigator. + +### Header related options + +The navigator does not show a header by default. It renders a native stack header if `headerShown` is set to `true` in the screen options explicitly, or if a custom header is provided with the `header` option. Header related options require a header to be shown. + +It supports most of the [header related options supported in `@react-navigation/native-stack`](native-stack-navigator.md#header-related-options) apart from the options related to the back button (prefixed with `headerBack`). + +### Events + +The navigator can [emit events](navigation-events.md) on certain actions. Supported events are: + +#### `tabPress` + +This event is fired when the user presses the tab button for the current screen in the tab bar. By default a tab press does several things: + +- If the tab is not focused, tab press will focus that tab +- If the tab is already focused: + - If the screen for the tab renders a scroll view, you can use [`useScrollToTop`](use-scroll-to-top.md) to scroll it to top + - If the screen for the tab renders a stack navigator, a `popToTop` action is performed on the stack + +The default behavior of the tab press is controlled natively and cannot be prevented. + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('tabPress', (e) => { + // Do something manually + // ... + }); + + return unsubscribe; +}, [navigation]); +``` + +#### `transitionStart` + +This event is fired when the transition animation starts for the current screen. + +Example: + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('transitionStart', (e) => { + // Do something + }); + + return unsubscribe; +}, [navigation]); +``` + +#### `transitionEnd` + +This event is fired when the transition animation ends for the current screen. + +Example: + +```js +React.useEffect(() => { + const unsubscribe = navigation.addListener('transitionEnd', (e) => { + // Do something + }); + + return unsubscribe; +}, [navigation]); +``` + +### Helpers + +The tab navigator adds the following methods to the navigation object: + +#### `jumpTo` + +Navigates to an existing screen in the tab navigator. The method accepts following arguments: + +- `name` - _string_ - Name of the route to jump to. +- `params` - _object_ - Screen params to use for the destination route. + +```js +navigation.jumpTo('Profile', { owner: 'Michaś' }); +``` diff --git a/versioned_docs/version-7.x/native-stack-navigator.md b/versioned_docs/version-7.x/native-stack-navigator.md index 552fb221d48..f1e12a971e2 100755 --- a/versioned_docs/version-7.x/native-stack-navigator.md +++ b/versioned_docs/version-7.x/native-stack-navigator.md @@ -158,6 +158,628 @@ The following [options](screen-options.md) can be used to configure the screens String that can be used as a fallback for `headerTitle`. +#### `statusBarAnimation` + +Sets the status bar animation (similar to the `StatusBar` component). Defaults to `fade` on iOS and `none` on Android. + +Supported values: + +- `"fade"` +- `"none"` +- `"slide"` + +On Android, setting either `fade` or `slide` will set the transition of status bar color. On iOS, this option applies to appereance animation of the status bar. + +Requires setting `View controller-based status bar appearance -> YES` (or removing the config) in your `Info.plist` file. + +Only supported on Android and iOS. + +#### `statusBarHidden` + +Whether the status bar should be hidden on this screen. + +Requires setting `View controller-based status bar appearance -> YES` (or removing the config) in your `Info.plist` file. + +Only supported on Android and iOS. + +#### `statusBarStyle` + +Sets the status bar color (similar to the `StatusBar` component). + +Supported values: + +- `"auto"` (iOS only) +- `"inverted"` (iOS only) +- `"dark"` +- `"light"` + +Defaults to `auto` on iOS and `light` on Android. + +Requires setting `View controller-based status bar appearance -> YES` (or removing the config) in your `Info.plist` file. + +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. + +#### `contentStyle` + +Style object for the scene content. + +#### `animationMatchesGesture` + +Whether the gesture to dismiss should use animation provided to `animation` prop. Defaults to `false`. + +Doesn't affect the behavior of screens presented modally. + +Only supported on iOS. + +#### `fullScreenGestureEnabled` + +Whether the gesture to dismiss should work on the whole screen. Using gesture to dismiss with this option results in the same transition animation as `simple_push`. This behavior can be changed by setting `customAnimationOnGesture` prop. Achieving the default iOS animation isn't possible due to platform limitations. Defaults to `false`. + +Doesn't affect the behavior of screens presented modally. + +Only supported on iOS. + +#### `fullScreenGestureShadowEnabled` + +Whether the full screen dismiss gesture has shadow under view during transition. Defaults to `true`. + +This does not affect the behavior of transitions that don't use gestures enabled by `fullScreenGestureEnabled` prop. + +#### `gestureEnabled` + +Whether you can use gestures to dismiss this screen. Defaults to `true`. Only supported on iOS. + +#### `animationTypeForReplace` + +The type of animation to use when this screen replaces another screen. Defaults to `push`. + +Supported values: + +- `push`: the new screen will perform push animation. + + + +- `pop`: the new screen will perform pop animation. + + + +#### `animation` + +How the screen should animate when pushed or popped. + +Only supported on Android and iOS. + +Supported values: + +- `default`: use the platform default animation + + +- `fade`: fade screen in or out + + +- `fade_from_bottom`: fade the new screen from bottom + + +- `flip`: flip the screen, requires `presentation: "modal"` (iOS only) + + +- `simple_push`: default animation, but without shadow and native header transition (iOS only, uses default animation on Android) + + +- `slide_from_bottom`: slide in the new screen from bottom + + +- `slide_from_right`: slide in the new screen from right (Android only, uses default animation on iOS) + + +- `slide_from_left`: slide in the new screen from left (Android only, uses default animation on iOS) + + +- `none`: don't animate the screen + + +#### `presentation` + +How should the screen be presented. + +Only supported on Android and iOS. + +Supported values: + +- `card`: the new screen will be pushed onto a stack, which means the default animation will be slide from the side on iOS, the animation on Android will vary depending on the OS version and theme. + + +- `modal`: the new screen will be presented modally. this also allows for a nested stack to be rendered inside the screen. + + +- `transparentModal`: the new screen will be presented modally, but in addition, the previous screen will stay so that the content below can still be seen if the screen has translucent background. + + +- `containedModal`: will use "UIModalPresentationCurrentContext" modal style on iOS and will fallback to "modal" on Android. + + +- `containedTransparentModal`: will use "UIModalPresentationOverCurrentContext" modal style on iOS and will fallback to "transparentModal" on Android. + + +- `fullScreenModal`: will use "UIModalPresentationFullScreen" modal style on iOS and will fallback to "modal" on Android. A screen using this presentation style can't be dismissed by gesture. + + +- `formSheet`: will use "BottomSheetBehavior" on Android and "UIModalPresentationFormSheet" modal style on iOS. + + + +##### Using Form Sheet + +To use Form Sheet for your screen, add `presentation: 'formSheet'` to the `options`. + + + + +```js name="Form Sheet" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { + createStaticNavigation, + useNavigation, +} from '@react-navigation/native'; +import { Button } from '@react-navigation/elements'; + +// codeblock-focus-start +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +// codeblock-focus-end + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + Profile Screen + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam accumsan + euismod enim, quis porta ligula egestas sed. Maecenas vitae consequat + odio, at dignissim lorem. Ut euismod eros ac mi ultricies, vel pharetra + tortor commodo. Interdum et malesuada fames ac ante ipsum primis in + faucibus. Nullam at urna in metus iaculis aliquam at sed quam. In + ullamcorper, ex ut facilisis commodo, urna diam posuere urna, at + condimentum mi orci ac ipsum. In hac habitasse platea dictumst. Donec + congue pharetra ipsum in finibus. Nulla blandit finibus turpis, non + vulputate elit viverra a. Curabitur in laoreet nisl. + + + + ); +} + +// codeblock-focus-start +const MyStack = createNativeStackNavigator({ + screens: { + Home: { + screen: HomeScreen, + }, + Profile: { + screen: ProfileScreen, + options: { + presentation: 'formSheet', + headerShown: false, + sheetAllowedDetents: 'fitToContents', + }, + }, + }, +}); +// codeblock-focus-end + +const Navigation = createStaticNavigation(MyStack); + +export default function App() { + return ; +} +``` + + + + +```js name="Form Sheet" snack +import * as React from 'react'; +import { Text, View } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { Button } from '@react-navigation/elements'; +// codeblock-focus-start +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const Stack = createNativeStackNavigator(); + +function MyStack() { + return ( + + + + + ); +} +// codeblock-focus-end + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home Screen + + + ); +} + +function ProfileScreen() { + const navigation = useNavigation(); + + return ( + + Profile Screen + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam accumsan + euismod enim, quis porta ligula egestas sed. Maecenas vitae consequat + odio, at dignissim lorem. Ut euismod eros ac mi ultricies, vel pharetra + tortor commodo. Interdum et malesuada fames ac ante ipsum primis in + faucibus. Nullam at urna in metus iaculis aliquam at sed quam. In + ullamcorper, ex ut facilisis commodo, urna diam posuere urna, at + condimentum mi orci ac ipsum. In hac habitasse platea dictumst. Donec + congue pharetra ipsum in finibus. Nulla blandit finibus turpis, non + vulputate elit viverra a. Curabitur in laoreet nisl. + + + + ); +} + +export default function App() { + return ( + + + + ); +} +``` + + + + +:::warning + +Due to technical issues in platform component integration with `react-native`, `presentation: 'formSheet'` has limited support for `flex: 1`. + +On Android, using `flex: 1` on a top-level content container passed to a `formSheet` with `showAllowedDetents: 'fitToContents'` causes the sheet to not display at all, leaving only the dimmed background visible. This is because it is the sheet, not the parent who is source of the size. Setting fixed values for `sheetAllowedDetents`, e.g. `[0.4, 0.9]`, works correctly (content is aligned for the highest detent). + +On iOS, `flex: 1` with `showAllowedDetents: 'fitToContents'` works properly but setting a fixed value for `showAllowedDetents` causes the screen to not respect the `flex: 1` style - the height of the container does not fill the `formSheet` fully, but rather inherits intrinsic size of its contents. This tradeoff is _currently_ necessary to prevent ["sheet flickering" problem on iOS](https://github.com/software-mansion/react-native-screens/issues/1722). + +If you don't use `flex: 1` but the content's height is less than max screen height, the rest of the sheet might become translucent or use the default theme background color (you can see this happening on the screenshots in the descrption of [this PR](https://github.com/software-mansion/react-native-screens/pull/2462)). To match the sheet to the background of your content, set `backgroundColor` in the `contentStyle` prop of the given screen. + +On Android, there are also some problems with getting nested ScrollViews to work properly. The solution is to set `nestedScrollEnabled` on the `ScrollView`, but this does not work if the content's height is less than the `ScrollView`'s height. Please see [this PR](https://github.com/facebook/react-native/pull/44099) for details and suggested [workaround](https://github.com/facebook/react-native/pull/44099#issuecomment-2058469661). + +On Android, nested stack and `headerShown` prop are not currently supported for screens with `presentation: 'formSheet'`. + +::: + +#### `sheetAllowedDetents` + +:::note + +Works only when `presentation` is set to `formSheet`. + +::: + + + +Describes heights where a sheet can rest. + +Supported values: + +- `fitToContents` - intents to set the sheet height to the height of its contents. +- Array of fractions, e.g. `[0.25, 0.5, 0.75]`: + - Heights should be described as fraction (a number from `[0, 1]` interval) of screen height / maximum detent height. + - The array **must** be sorted in ascending order. This invariant is verified only in developement mode, where violation results in error. + - iOS accepts any number of detents, while **Android is limited to three** - any surplus values, beside first three are ignored. + +Defaults to `[1.0]`. + +Only supported on Android and iOS. + +#### `sheetElevation` + +:::note + +Works only when `presentation` is set to `formSheet`. + +::: + + + +Integer value describing elevation of the sheet, impacting shadow on the top edge of the sheet. + +Not dynamic - changing it after the component is rendered won't have an effect. + +Defaults to `24`. + +Only supported on Android. + +#### `sheetExpandsWhenScrolledToEdge` + +:::note + +Works only when `presentation` is set to `formSheet`. + +::: + + + +Whether the sheet should expand to larger detent when scrolling. + +Defaults to `true`. + +Only supported on iOS. + +:::warning + +Please note that for this interaction to work, the ScrollView must be "first-subview-chain" descendant of the Screen component. This restriction is due to platform requirements. + +::: + +#### `sheetCornerRadius` + +:::note + +Works only when `presentation` is set to `formSheet`. + +::: + + + +The corner radius that the sheet will try to render with. + +If set to non-negative value it will try to render sheet with provided radius, else it will apply system default. + +If left unset, system default is used. + +Only supported on Android and iOS. + +#### `sheetInitialDetentIndex` + +:::note + +Works only when `presentation` is set to `formSheet`. + +::: + + + +**Index** of the detent the sheet should expand to after being opened. + +If the specified index is out of bounds of `sheetAllowedDetents` array, in dev environment more errors will be thrown, in production the value will be reset to default value. + +Additionaly there is `last` value available, when set the sheet will expand initially to last (largest) detent. + +Defaults to `0` - which represents first detent in the detents array. + +Only supported on Android and iOS. + +#### `sheetGrabberVisible` + +:::note + +Works only when `presentation` is set to `formSheet`. + +::: + + + +Boolean indicating whether the sheet shows a grabber at the top. + +Defaults to `false`. + +Only supported on iOS. + +#### `sheetLargestUndimmedDetentIndex` + +:::note + +Works only when `presentation` is set to `formSheet`. + +::: + + + +The largest sheet detent for which a view underneath won't be dimmed. + +This prop can be set to an number, which indicates index of detent in `sheetAllowedDetents` array for which there won't be a dimming view beneath the sheet. + +Additionaly there are following options available: + +- `none` - there will be dimming view for all detents levels, +- `last` - there won't be a dimming view for any detent level. + +Defaults to `none`, indicating that the dimming view should be always present. + +Only supported on Android and iOS. + +#### `orientation` + +The display orientation to use for the screen. + +Supported values: + +- `default` - resolves to "all" without "portrait_down" on iOS. On Android, this lets the system decide the best orientation. +- `all`: all orientations are permitted. +- `portrait`: portrait orientations are permitted. +- `portrait_up`: right-side portrait orientation is permitted. +- `portrait_down`: upside-down portrait orientation is permitted. +- `landscape`: landscape orientations are permitted. +- `landscape_left`: landscape-left orientation is permitted. +- `landscape_right`: landscape-right orientation is permitted. + +Only supported on Android and iOS. + +#### `autoHideHomeIndicator` + +Boolean indicating whether the home indicator should prefer to stay hidden. Defaults to `false`. + +Only supported on iOS. + +#### `gestureDirection` + +Sets the direction in which you should swipe to dismiss the screen. + +Supported values: + +- `vertical` – dismiss screen vertically +- `horizontal` – dismiss screen horizontally (default) + +When using `vertical` option, options `fullScreenGestureEnabled: true`, `customAnimationOnGesture: true` and `animation: 'slide_from_bottom'` are set by default. + +Only supported on iOS. + +#### `animationDuration` + +Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS. Defaults to `350`. + +The duration of `default` and `flip` transitions isn't customizable. + +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. + +#### `navigationBarHidden` + +Boolean indicating whether the navigation bar should be hidden. Defaults to `false`. + +Only supported on Android. + +#### `freezeOnBlur` + +Boolean indicating whether to prevent inactive screens from re-rendering. Defaults to `false`. +Defaults to `true` when `enableFreeze()` from `react-native-screens` package is run at the top of the application. + +Only supported on iOS and Android. + +### Header related options + +The navigator supports following options to configure the header: + #### `headerBackButtonMenuEnabled` Boolean indicating whether to show the menu on longPress of iOS >= 14 back button. Defaults to `true`. @@ -174,7 +796,9 @@ 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, or "Back" if there's not enough space. Use `headerBackButtonDisplayMode` to customize the behavior. +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. Only supported on iOS. @@ -186,9 +810,17 @@ How the back button displays icon and title. Supported values: -- `default`: Displays one of the following depending on the available space: previous screen's title, generic title (e.g. 'Back') or no title (only icon). -- `generic`: Displays one of the following depending on the available space: generic title (e.g. 'Back') or no title (only icon). iOS >= 14 only, falls back to "default" on older iOS versions. -- `minimal`: Always displays only the icon without a title. +- "default" - Displays one of the following depending on the available space: previous screen's title, generic title (e.g. 'Back') or no title (only icon). +- "generic" – Displays one of the following depending on the available space: generic title (e.g. 'Back') or no title (only icon). +- "minimal" – Always displays only the icon without a title. + +The space-aware behavior is disabled when: + +- The iOS version is 13 or lower +- Custom font family or size is set (e.g. with `headerBackTitleStyle`) +- Back button menu is disabled (e.g. with `headerBackButtonMenuEnabled`) + +In such cases, a static title and icon are always displayed. Only supported on iOS. @@ -273,12 +905,8 @@ Example: fontSize: 22, fontWeight: '500', color: 'blue', - }, -``` - -#### `headerShown` - -Whether to show the header. The header is shown by default. Setting this to `false` hides the header. + }, +``` #### `headerStyle` @@ -405,7 +1033,12 @@ Tint color for the header. Changes the color of back button and title. #### `headerLeft` -Function which returns a React Element to display on the left side of the header. This replaces the back button. See `headerBackVisible` to show the back button along side left element. +Function which returns a React Element to display on the left side of the header. This replaces the back button. See `headerBackVisible` to show the back button along side left element. It receives the following properties in the arguments: + +- `tintColor` - The tint color to apply. Defaults to the [theme](themes.md)'s primary color. +- `canGoBack` - Boolean indicating whether there is a screen to go back to. +- `label` - Label text for the button. Usually the title of the previous screen. +- `href` - The `href` to use for the anchor tag on web Header right @@ -419,9 +1052,43 @@ Example: headerBackTitle: 'Back', ``` +#### `unstable_headerLeftItems` + +:::warning + +This option is experimental and may change in a minor release. + +::: + +Function which returns an array of items to display as on the left side of the header. This will override `headerLeft` if both are specified. It receives the following properties in the arguments: + +- `tintColor` - The tint color to apply. Defaults to the [theme](themes.md)'s primary color. +- `canGoBack` - Boolean indicating whether there is a screen to go back to. + +Example: + +```js +unstable_headerRightItems: () => [ + { + type: 'button', + title: 'Edit', + onPress: () => { + // Do something + }, + }, +], +``` + +See [Header items](#header-items) for more information. + +Only supported on iOS. + #### `headerRight` -Function which returns a React Element to display on the right side of the header. +Function which returns a React Element to display on the right side of the header. It receives the following properties in the arguments: + +- `tintColor` - The tint color to apply. Defaults to the [theme](themes.md)'s primary color. +- `canGoBack` - Boolean indicating whether there is a screen to go back to. Header right @@ -431,6 +1098,37 @@ Example: headerRight: () => ; ``` +#### `unstable_headerRightItems` + +:::warning + +This option is experimental and may change in a minor release. + +::: + +Function which returns an array of items to display as on the right side of the header. This will override `headerRight` if both are specified. It receives the following properties in the arguments: + +- `tintColor` - The tint color to apply. Defaults to the [theme](themes.md)'s primary color. +- `canGoBack` - Boolean indicating whether there is a screen to go back to. + +Example: + +```js +unstable_headerRightItems: () => [ + { + type: 'button', + title: 'Edit', + onPress: () => { + // Do something + }, + }, +], +``` + +See [Header items](#header-items) for more information. + +Only supported on iOS. + #### `headerTitle` String or a function that returns a React Element to be used by the header. Defaults to `title` or name of the screen. @@ -549,388 +1247,135 @@ Only supported on iOS. Whether the back button should close search bar's text input or not. Defaults to `false`. -Only supported on Android. - -##### `hideNavigationBar` - -Boolean indicating whether to hide the navigation bar during searching. Defaults to `true`. - -Only supported on iOS. - -##### `hideWhenScrolling` - -Boolean indicating whether to hide the search bar when scrolling. Defaults to `true`. - -Only supported on iOS. - -##### `inputType` - -The type of the input. Defaults to `"text"`. - -Supported values: - -- `"text"` -- `"phone"` -- `"number"` -- `"email"` - -Only supported on Android. - -##### `obscureBackground` - -Boolean indicating whether to obscure the underlying content with semi-transparent overlay. Defaults to `true`. - -##### `placeholder` - -Text displayed when search field is empty. - -##### `textColor` - -The color of the text in the search field. - -Header search bar options - Text color - -##### `hintTextColor` - -The color of the hint text in the search field. - -Only supported on Android. - -Header search bar options - Hint text color - -##### `headerIconColor` - -The color of the search and close icons shown in the header - -Only supported on Android. - -Header search bar options - Header icon color - -##### `shouldShowHintSearchIcon` - -Whether to show the search hint icon when search bar is focused. Defaults to `true`. - -Only supported on Android. - -##### `onBlur` - -A callback that gets called when search bar has lost focus. - -##### `onCancelButtonPress` - -A callback that gets called when the cancel button is pressed. - -##### `onChangeText` - -A callback that gets called when the text changes. It receives the current text value of the search bar. - -Example: - -```js -const [search, setSearch] = React.useState(''); - -React.useLayoutEffect(() => { - navigation.setOptions({ - headerSearchBarOptions: { - onChangeText: (event) => setSearch(event.nativeEvent.text), - }, - }); -}, [navigation]); -``` - -#### `header` - -Custom header to use instead of the default header. - -This accepts a function that returns a React Element to display as a header. The function receives an object containing the following properties as the argument: - -- `navigation` - The navigation object for the current screen. -- `route` - The route object for the current screen. -- `options` - The options for the current screen -- `back` - Options for the back button, contains an object with a `title` property to use for back button label. - -Example: - -```js -import { getHeaderTitle } from '@react-navigation/elements'; - -// .. - -header: ({ navigation, route, options, back }) => { - const title = getHeaderTitle(options, route.name); - - return ( - : undefined - } - style={options.headerStyle} - /> - ); -}; -``` - -To set a custom header for all the screens in the navigator, you can specify this option in the `screenOptions` prop of the navigator. - -Note that if you specify a custom header, the native functionality such as large title, search bar etc. won't work. - -#### `statusBarAnimation` - -Sets the status bar animation (similar to the `StatusBar` component). Defaults to `fade` on iOS and `none` on Android. - -Supported values: - -- `"fade"` -- `"none"` -- `"slide"` - -On Android, setting either `fade` or `slide` will set the transition of status bar color. On iOS, this option applies to appereance animation of the status bar. - -Requires setting `View controller-based status bar appearance -> YES` (or removing the config) in your `Info.plist` file. - -Only supported on Android and iOS. - -#### `statusBarHidden` - -Whether the status bar should be hidden on this screen. - -Requires setting `View controller-based status bar appearance -> YES` (or removing the config) in your `Info.plist` file. - -Only supported on Android and iOS. - -#### `statusBarStyle` - -Sets the status bar color (similar to the `StatusBar` component). Defaults to `auto`. - -Supported values: - -- `"auto"` -- `"inverted"` (iOS only) -- `"dark"` -- `"light"` - -Requires setting `View controller-based status bar appearance -> YES` (or removing the config) in your `Info.plist` file. - -Only supported on Android and iOS. - -#### `statusBarBackgroundColor` - -Sets the background color of the status bar (similar to the `StatusBar` component). - -Only supported on Android. - -#### `statusBarTranslucent` - -Sets the translucency of the status bar (similar to the `StatusBar` component). Defaults to `false`. - -Only supported on Android. - -#### `contentStyle` - -Style object for the scene content. - -#### `animationMatchesGesture` - -Whether the gesture to dismiss should use animation provided to `animation` prop. Defaults to `false`. - -Doesn't affect the behavior of screens presented modally. - -Only supported on iOS. - -#### `fullScreenGestureEnabled` - -Whether the gesture to dismiss should work on the whole screen. Using gesture to dismiss with this option results in the same transition animation as `simple_push`. This behavior can be changed by setting `customAnimationOnGesture` prop. Achieving the default iOS animation isn't possible due to platform limitations. Defaults to `false`. - -Doesn't affect the behavior of screens presented modally. - -Only supported on iOS. - -#### `fullScreenGestureShadowEnabled` - -Whether the full screen dismiss gesture has shadow under view during transition. Defaults to `true`. - -This does not affect the behavior of transitions that don't use gestures enabled by `fullScreenGestureEnabled` prop. - -#### `gestureEnabled` - -Whether you can use gestures to dismiss this screen. Defaults to `true`. Only supported on iOS. - -#### `animationTypeForReplace` - -The type of animation to use when this screen replaces another screen. Defaults to `push`. - -Supported values: - -- `push`: the new screen will perform push animation. - - - -- `pop`: the new screen will perform pop animation. +Only supported on Android. - +##### `hideNavigationBar` -#### `animation` +Boolean indicating whether to hide the navigation bar during searching. Defaults to `true`. -How the screen should animate when pushed or popped. +Only supported on iOS. -Only supported on Android and iOS. +##### `hideWhenScrolling` -Supported values: +Boolean indicating whether to hide the search bar when scrolling. Defaults to `true`. -- `default`: use the platform default animation - +Only supported on iOS. -- `fade`: fade screen in or out - +##### `inputType` -- `fade_from_bottom`: fade the new screen from bottom - +The type of the input. Defaults to `"text"`. -- `flip`: flip the screen, requires `presentation: "modal"` (iOS only) - +Supported values: -- `simple_push`: default animation, but without shadow and native header transition (iOS only, uses default animation on Android) - +- `"text"` +- `"phone"` +- `"number"` +- `"email"` -- `slide_from_bottom`: slide in the new screen from bottom - +Only supported on Android. -- `slide_from_right`: slide in the new screen from right (Android only, uses default animation on iOS) - +##### `obscureBackground` -- `slide_from_left`: slide in the new screen from left (Android only, uses default animation on iOS) - +Boolean indicating whether to obscure the underlying content with semi-transparent overlay. Defaults to `true`. -- `none`: don't animate the screen - +##### `placeholder` -#### `presentation` +Text displayed when search field is empty. -How should the screen be presented. +##### `textColor` -Only supported on Android and iOS. +The color of the text in the search field. -Supported values: +Header search bar options - Text color -- `card`: the new screen will be pushed onto a stack, which means the default animation will be slide from the side on iOS, the animation on Android will vary depending on the OS version and theme. - +##### `hintTextColor` -- `modal`: the new screen will be presented modally. this also allows for a nested stack to be rendered inside the screen. - +The color of the hint text in the search field. -- `transparentModal`: the new screen will be presented modally, but in addition, the previous screen will stay so that the content below can still be seen if the screen has translucent background. - +Only supported on Android. -- `containedModal`: will use "UIModalPresentationCurrentContext" modal style on iOS and will fallback to "modal" on Android. - +Header search bar options - Hint text color -- `containedTransparentModal`: will use "UIModalPresentationOverCurrentContext" modal style on iOS and will fallback to "transparentModal" on Android. - +##### `headerIconColor` -- `fullScreenModal`: will use "UIModalPresentationFullScreen" modal style on iOS and will fallback to "modal" on Android. A screen using this presentation style can't be dismissed by gesture. - -- `formSheet`: will use "UIModalPresentationFormSheet" modal style on iOS and will fallback to "modal" on Android. - +The color of the search and close icons shown in the header -#### `orientation` +Only supported on Android. -The display orientation to use for the screen. +Header search bar options - Header icon color -Supported values: +##### `shouldShowHintSearchIcon` -- `default` - resolves to "all" without "portrait_down" on iOS. On Android, this lets the system decide the best orientation. -- `all`: all orientations are permitted. -- `portrait`: portrait orientations are permitted. -- `portrait_up`: right-side portrait orientation is permitted. -- `portrait_down`: upside-down portrait orientation is permitted. -- `landscape`: landscape orientations are permitted. -- `landscape_left`: landscape-left orientation is permitted. -- `landscape_right`: landscape-right orientation is permitted. +Whether to show the search hint icon when search bar is focused. Defaults to `true`. -Only supported on Android and iOS. +Only supported on Android. -#### `autoHideHomeIndicator` +##### `onBlur` -Boolean indicating whether the home indicator should prefer to stay hidden. Defaults to `false`. +A callback that gets called when search bar has lost focus. -Only supported on iOS. +##### `onCancelButtonPress` -#### `gestureDirection` +A callback that gets called when the cancel button is pressed. -Sets the direction in which you should swipe to dismiss the screen. +##### `onChangeText` -Supported values: +A callback that gets called when the text changes. It receives the current text value of the search bar. -- `vertical` – dismiss screen vertically -- `horizontal` – dismiss screen horizontally (default) +Example: -When using `vertical` option, options `fullScreenGestureEnabled: true`, `customAnimationOnGesture: true` and `animation: 'slide_from_bottom'` are set by default. +```js +const [search, setSearch] = React.useState(''); -Only supported on iOS. +React.useLayoutEffect(() => { + navigation.setOptions({ + headerSearchBarOptions: { + onChangeText: (event) => setSearch(event.nativeEvent.text), + }, + }); +}, [navigation]); +``` -#### `animationDuration` +#### `headerShown` -Changes the duration (in milliseconds) of `slide_from_bottom`, `fade_from_bottom`, `fade` and `simple_push` transitions on iOS. Defaults to `350`. +Whether to show the header. The header is shown by default. Setting this to `false` hides the header. -The duration of `default` and `flip` transitions isn't customizable. +#### `header` -Only supported on iOS. +Custom header to use instead of the default header. -#### `navigationBarColor` +This accepts a function that returns a React Element to display as a header. The function receives an object containing the following properties as the argument: -Sets the navigation bar color. Defaults to initial status bar color. +- `navigation` - The navigation object for the current screen. +- `route` - The route object for the current screen. +- `options` - The options for the current screen +- `back` - Options for the back button, contains an object with a `title` property to use for back button label. -Only supported on Android. +Example: -#### `navigationBarHidden` +```js +import { getHeaderTitle } from '@react-navigation/elements'; -Boolean indicating whether the navigation bar should be hidden. Defaults to `false`. +// .. -Only supported on Android. +header: ({ navigation, route, options, back }) => { + const title = getHeaderTitle(options, route.name); -#### `freezeOnBlur` + return ( + : undefined + } + style={options.headerStyle} + /> + ); +}; +``` -Boolean indicating whether to prevent inactive screens from re-rendering. Defaults to `false`. -Defaults to `true` when `enableFreeze()` from `react-native-screens` package is run at the top of the application. +To set a custom header for all the screens in the navigator, you can specify this option in the `screenOptions` prop of the navigator. -Only supported on iOS and Android. +Note that if you specify a custom header, the native functionality such as large title, search bar etc. won't work. ### Events @@ -1016,7 +1461,8 @@ Navigates back to a previous screen in the stack by popping screens after it. Th - `name` - _string_ - Name of the route to navigate to. - `params` - _object_ - Screen params to pass to the destination route. -- `merge` - _boolean_ - Whether params should be merged with the existing route params, or replace them (when navigating to an existing screen). Defaults to `false`. +- `options` - Options object containing the following properties: + - `merge` - _boolean_ - Whether params should be merged with the existing route params, or replace them (when navigating to an existing screen). Defaults to `false`. If a matching screen is not found in the stack, this will pop the current screen and add a new screen with the specified name and params. @@ -1061,3 +1507,238 @@ const MyView = () => { ); }; ``` + +## Header items + +The [`unstable_headerLeftItems`](#unstable_headerleftitems) and [`unstable_headerRightItems`](#unstable_headerrightitems) options allow you to add header items to the left and right side of the header respectively. This items can show native buttons, menus or custom React elements. + +On iOS 26+, the header right items can also be collapsed into an overflow menu by the system when there is not enough space to show all items. Note that custom elements (with `type: 'custom'`) won't be collapsed into the overflow menu. + +Header items + +Header item with menu + +There are 3 categories of items that can be displayed in the header: + +### Action + +A regular button that performs an action when pressed, or shows a menu. + +Common properties: + +- `type`: Must be `button` or `menu`. +- `label`: Label of the item. The label is not shown if `icon` is specified. However, it is used by screen readers, or if the header items get collapsed due to lack of space. +- `labelStyle`: Style object for the label. Supported properties: + - `fontFamily` + - `fontSize` + - `fontWeight` + - `color` +- `icon`: Optional icon to show instead of the label. + + The icon can be an image: + + ```js + { + type: 'image', + source: require('./path/to/image.png'), + } + ``` + + Or a [SF Symbols](https://developer.apple.com/sf-symbols/) name: + + ```js + { + type: 'sfSymbol', + name: 'heart', + } + ``` + +- `variant`: Visual variant of the button. Supported values: + - `plain` (default) + - `done` + - `prominent` +- `tintColor`: Tint color to apply to the item. +- `disabled`: Whether the item is disabled. +- `width`: Width of the item. +- `hidesSharedBackground` (iOS 26+): Whether the background this item may share with other items should be hidden. Setting this to `true` hides the liquid glass background. +- `sharesBackground` (iOS 26+): Whether this item can share a background with other items. +- `identifier` (iOS 26+) - An identifier used to match items across transitions. +- `badge` (iOS 26+): An optional badge to display alongside the item. Supported properties: + - `value`: The value to display in the badge. It can be a string or a number. + - `style`: Style object for the badge. Supported properties: + - `fontFamily` + - `fontSize` + - `fontWeight` + - `color` +- `accessibilityLabel`: Accessibility label for the item. +- `accessibilityHint`: Accessibility hint for the item. + +Supported properties when `type` is `button`: + +- `onPress`: Function to call when the button is pressed. +- `selected`: Whether the button is in a selected state. + +Example: + +```js +unstable_headerRightItems: () => [ + { + type: 'button', + label: 'Edit', + icon: { + type: 'sfSymbol', + name: 'pencil', + }, + onPress: () => { + // Do something + }, + }, +], +``` + +Supported properties when `type` is `menu`: + +- `changesSelectionAsPrimaryAction`: Whether the menu is a selection menu. Tapping an item in a selection menu will add a checkmark to the selected item. Defaults to `false`. +- `menu`: An object containing the menu items. It contains the following properties: + + - `title`: Optional title to show on top of the menu. + - `items`: An array of menu items. A menu item can be either an `action` or a `submenu`. + + - `action`: An object with the following properties: + + - `type`: Must be `action`. + - `label`: Label of the menu item. + - `icon`: Optional icon to show alongside the label. The icon can be a [SF Symbols](https://developer.apple.com/sf-symbols/) name: + + ```js + { + type: 'sfSymbol', + name: 'trash', + } + ``` + + - `onPress`: Function to call when the menu item is pressed. + - `state`: Optional state of the menu item. Supported values: + - `on` + - `off` + - `mixed` + - `disabled`: Whether the menu item is disabled. + - `destructive`: Whether the menu item is styled as destructive. + - `hidden`: Whether the menu item is hidden. + - `keepsMenuPresented`: Whether to keep the menu open after selecting this item. Defaults to `false`. + - `discoverabilityLabel`: An elaborated title that explains the purpose of the action. + + - `submenu`: An object with the following properties: + + - `type`: Must be `submenu`. + - `label`: Label of the submenu item. + - `icon`: Optional icon to show alongside the label. The icon can be a [SF Symbols](https://developer.apple.com/sf-symbols/) name: + + ```js + { + type: 'sfSymbol', + name: 'pencil', + } + ``` + + - `items`: An array of menu items (can be either `action` or `submenu`). + +Example: + +```js +unstable_headerRightItems: () => [ + { + type: 'menu', + label: 'Options', + icon: { + type: 'sfSymbol', + name: 'ellipsis', + }, + menu: { + title: 'Options', + items: [ + { + type: 'action', + label: 'Edit', + icon: { + type: 'sfSymbol', + name: 'pencil', + }, + onPress: () => { + // Do something + }, + }, + { + type: 'submenu', + label: 'More', + items: [ + { + type: 'action', + label: 'Delete', + destructive: true, + onPress: () => { + // Do something + }, + }, + ], + }, + ], + }, + }, +], +``` + +### Spacing + +An item to add spacing between other items in the header. + +Supported properties: + +- `type`: Must be `spacing`. +- `spacing`: Amount of spacing to add. + +```js +unstable_headerRightItems: () => [ + { + type: 'button', + label: 'Edit', + onPress: () => { + // Do something + }, + }, + { + type: 'spacing', + spacing: 10, + }, + { + type: 'button', + label: 'Delete', + onPress: () => { + // Do something + }, + }, +], +``` + +### Custom + +A custom item to display any React Element in the header. + +Supported properties: + +- `type`: Must be `custom`. +- `element`: A React Element to display as the item. +- `hidesSharedBackground`: Whether the background this item may share with other items in the bar should be hidden. Setting this to `true` hides the liquid glass background on iOS 26+. + +Example: + +```js +unstable_headerRightItems: () => [ + { + type: 'custom', + element: , + }, +], +``` + +The advantage of using this over [`headerLeft`](#headerleft) or [`headerRight`](#headerright) options is that it supports features like shared background on iOS 26+. diff --git a/versioned_docs/version-7.x/navigating-without-navigation-prop.md b/versioned_docs/version-7.x/navigating-without-navigation-prop.md index e01cb26729f..0200c83209a 100755 --- a/versioned_docs/version-7.x/navigating-without-navigation-prop.md +++ b/versioned_docs/version-7.x/navigating-without-navigation-prop.md @@ -13,7 +13,7 @@ Sometimes you need to trigger a navigation action from places where you do not h - You need to navigate from inside a component without needing to pass the `navigation` prop down, see [`useNavigation`](use-navigation.md) instead. The `ref` behaves differently, and many helper methods specific to screens aren't available. - You need to handle deep links or universal links. Doing this with the `ref` has many edge cases. See [configuring links](configuring-links.md) for more information on handling deep linking. -- You need to integrate with third party libraries, such as push notifications, branch etc. See [third party integrations for deep linking](deep-linking.md#third-party-integrations) instead. +- You need to integrate with third party libraries, such as push notifications, branch etc. See [Integrating with other tools](deep-linking.md#integrating-with-other-tools) instead. **Do** use the `ref` if: diff --git a/versioned_docs/version-7.x/navigating.md b/versioned_docs/version-7.x/navigating.md index 8babb1a7e98..73057ef5193 100755 --- a/versioned_docs/version-7.x/navigating.md +++ b/versioned_docs/version-7.x/navigating.md @@ -358,8 +358,8 @@ export default function App() { ## Summary -- `navigation.navigate('RouteName')` pushes a new route to the native stack navigator if you're not already on that route. -- We can call `navigation.push('RouteName')` as many times as we like and it will continue pushing routes. -- The header bar will automatically show a back button, but you can programmatically go back by calling `navigation.goBack()`. On Android, the hardware back button just works as expected. -- You can go back to an existing screen in the stack with `navigation.popTo('RouteName')`, and you can go back to the first screen in the stack with `navigation.popToTop()`. -- The `navigation` object is available to all screen components with the [`useNavigation`](use-navigation.md) hook. +- [`navigation.navigate('RouteName')`](navigation-object.md#navigate) pushes a new route to the native stack navigator if you're not already on that route. +- We can call [`navigation.push('RouteName')`](stack-actions.md#push) as many times as we like and it will continue pushing routes. +- The header bar will automatically show a back button, but you can programmatically go back by calling [`navigation.goBack()`](navigation-object.md#goback). On Android, the hardware back button just works as expected. +- You can go back to an existing screen in the stack with [`navigation.popTo('RouteName')`](stack-actions.md#popto), and you can go back to the first screen in the stack with [`navigation.popToTop()`](stack-actions.md#poptotop). +- The [`navigation`](navigation-object.md) object is available to all screen components with the [`useNavigation`](use-navigation.md) hook. diff --git a/versioned_docs/version-7.x/navigation-actions.md b/versioned_docs/version-7.x/navigation-actions.md index 3a4f80000c8..91b6dfa41fc 100755 --- a/versioned_docs/version-7.x/navigation-actions.md +++ b/versioned_docs/version-7.x/navigation-actions.md @@ -28,7 +28,9 @@ The `navigate` action allows to navigate to a specific route. It takes the follo - `name` - _string_ - A destination name of the screen in the current or a parent navigator. - `params` - _object_ - Params to use for the destination route. -- `merge` - _boolean_ - Whether params should be merged with the existing route params, or replace them (when navigating to an existing screen). Defaults to `false`. +- `options` - Options object containing the following properties: + - `merge` - _boolean_ - Whether params should be merged with the existing route params, or replace them (when navigating to an existing screen). Defaults to `false`. + - `pop` - _boolean_ - Whether screens should be popped to navigate to a matching screen in the stack. Defaults to `false`. @@ -68,9 +70,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -88,52 +87,6 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - - ); } @@ -190,9 +143,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -211,52 +161,6 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - - ); } @@ -282,7 +186,7 @@ In a stack navigator ([stack](stack-navigator.md) or [native stack](native-stack - If you're already on a screen with the same name, it will update its params and not push a new screen. - If you're on a different screen, it will push the new screen onto the stack. -- If the [`getId`](screen.md#id) prop is specified, and another screen in the stack has the same ID, it will navigate to that screen and update its params instead. +- If the [`getId`](screen.md#id) prop is specified, and another screen in the stack has the same ID, it will bring that screen to focus and update its params instead.
Advanced usage @@ -292,6 +196,7 @@ The `navigate` action can also accepts an object as the argument with the follow - `name` - _string_ - A destination name of the screen in the current or a parent navigator - `params` - _object_ - Params to use for the destination route. - `merge` - _boolean_ - Whether params should be merged with the existing route params, or replace them (when navigating to an existing screen). Defaults to `false`. +- `pop` - _boolean_ - Whether screens should be popped to navigate to a matching screen in the stack. Defaults to `false`. - `path` - _string_ - The path (from deep link or universal link) to associate with the screen. This is primarily used internally to associate a path with a screen when it's from a URL. @@ -340,9 +245,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -360,13 +262,6 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - ); } @@ -462,9 +336,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -483,17 +354,6 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - ); } @@ -631,15 +470,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -659,46 +489,9 @@ function ProfileScreen({ route }) { {route.params.user}'s profile - - - - ); } @@ -786,50 +570,9 @@ function ProfileScreen({ route }) { {route.params.user}'s profile - - - - ); } @@ -913,48 +653,13 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - - ); } @@ -1036,31 +738,6 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - ); } @@ -1406,53 +1080,14 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - ); @@ -1508,9 +1143,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -1531,51 +1163,184 @@ function ProfileScreen({ route }) { {route.params.user}'s profile + + ); +} + +const Stack = createStackNavigator(); + +export default function App() { + return ( + + + + + + + ); +} +``` + + + + +If you want to replace params for a particular route, you can add a `source` property referring to the route key: + + + + +```js name="Common actions setParams" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { + createStaticNavigation, + useNavigation, + CommonActions, +} from '@react-navigation/native'; +import { createStackNavigator } from '@react-navigation/stack'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home! + + ); +} + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + return ( + + Profile! + {route.params.user}'s profile + + ); +} + +const Stack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(Stack); + +export default function App() { + return ; +} +``` + + + + +```js name="Common actions setParams" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { + NavigationContainer, + CommonActions, + useNavigation, +} from '@react-navigation/native'; +import { createStackNavigator } from '@react-navigation/stack'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home! + + ); +} + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + + return ( + + Profile! + {route.params.user}'s profile + ); @@ -1598,12 +1363,18 @@ export default function App() { -If you want to set params for a particular route, you can add a `source` property referring to the route key: +If the `source` property is explicitly set to `undefined`, it'll replace the params for the focused route. + +### replaceParams + +The `replaceParams` action allows to replace params for a certain route. It takes the following arguments: + +- `params` - _object_ - required - New params to use for the route. -```js name="Common actions setParams" snack +```js name="Common actions replaceParams" snack import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; @@ -1636,9 +1407,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -1656,53 +1424,14 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - ); @@ -1725,7 +1454,7 @@ export default function App() { -```js name="Common actions setParams" snack +```js name="Common actions replaceParams" snack import * as React from 'react'; import { View, Text } from 'react-native'; import { Button } from '@react-navigation/elements'; @@ -1758,9 +1487,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -1781,51 +1507,187 @@ function ProfileScreen({ route }) { {route.params.user}'s profile + + ); +} + +const Stack = createStackNavigator(); + +export default function App() { + return ( + + + + + + + ); +} +``` + + + + +If you want to replace params for a particular route, you can add a `source` property referring to the route key: + + + + +```js name="Common actions replaceParams" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { + createStaticNavigation, + useNavigation, + CommonActions, +} from '@react-navigation/native'; +import { createStackNavigator } from '@react-navigation/stack'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home! + + + ); +} + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + return ( + + Profile! + {route.params.user}'s profile + + ); +} + +const Stack = createStackNavigator({ + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); + +const Navigation = createStaticNavigation(Stack); + +export default function App() { + return ; +} +``` + + + + +```js name="Common actions replaceParams" snack +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { Button } from '@react-navigation/elements'; +import { + NavigationContainer, + CommonActions, + useNavigation, +} from '@react-navigation/native'; +import { createStackNavigator } from '@react-navigation/stack'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + Home! + + + ); +} + +function ProfileScreen({ route }) { + const navigation = useNavigation(); + + return ( + + Profile! + {route.params.user}'s profile ); @@ -1848,4 +1710,4 @@ export default function App() { -If the `source` property is explicitly set to `undefined`, it'll set the params for the focused route. +If the `source` property is explicitly set to `undefined`, it'll replace the params for the focused route. diff --git a/versioned_docs/version-7.x/navigation-container.md b/versioned_docs/version-7.x/navigation-container.md index 5b11e4516e4..536dd908ae2 100644 --- a/versioned_docs/version-7.x/navigation-container.md +++ b/versioned_docs/version-7.x/navigation-container.md @@ -237,6 +237,19 @@ Note that the returned `options` object will be `undefined` if there are no navi The `addListener` method lets you listen to the following events: +##### `ready` + +The event is triggered when the navigation tree is ready. This is useful for cases where you want to wait until the navigation tree is mounted: + +```js +const unsubscribe = navigationRef.addListener('ready', () => { + // Get the initial state of the navigation tree + console.log(navigationRef.getRootState()); +}); +``` + +This is analogous to the [`onReady`](#onready) method. + ##### `state` The event is triggered whenever the [navigation state](navigation-state.md) changes in any navigator in the navigation tree: @@ -450,7 +463,7 @@ const Navigation = createStaticNavigation(RootStack); function App() { const linking = { - prefixes: ['https://mychat.com', 'mychat://'], + prefixes: ['https://example.com', 'example://'], }; return ( @@ -471,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', @@ -512,7 +525,7 @@ Example: Loading...} /> @@ -525,7 +538,7 @@ Example: listener(url); @@ -688,7 +701,7 @@ import messaging from '@react-native-firebase/messaging'; ```js +##### `linking.getActionFromState` + +The state parsed with [`getStateFromPath`](#linkinggetstatefrompath) is used as the initial state of the navigator. But for subsequent deep links and URLs, the state is converted to a navigation action. Typically it is a [`navigate`](navigation-actions.md#navigate) action. + +You can provide a custom `getActionFromState` function to customize how the state is converted to an action. + +Example: + + + + +```js + +``` + + + + +```js + + {/* content */} + +``` + + + + ### `fallback` React Element to use as a fallback while we resolve deep links. Defaults to `null`. diff --git a/versioned_docs/version-7.x/navigation-events.md b/versioned_docs/version-7.x/navigation-events.md index 279b918b801..1d3c6430a14 100644 --- a/versioned_docs/version-7.x/navigation-events.md +++ b/versioned_docs/version-7.x/navigation-events.md @@ -25,6 +25,12 @@ For most cases, the [`useFocusEffect`](use-focus-effect.md) hook might be approp This event is emitted when the screen goes out of focus. +:::note + +In some cases, such as going back from a screen in [native-stack navigator](native-stack-navigator.md), the screen may not receive the `blur` event as the screen is unmounted immediately. For cleaning up resources, it's recommended to use the cleanup function of [`useFocusEffect`](use-focus-effect.md) hook instead that considers both blur and unmounting of the screen. + +::: + ### `state` This event is emitted when the navigator's state changes. This event receives the navigator's state in the event data (`event.data.state`). @@ -70,7 +76,7 @@ React.useEffect( ); ``` -:::note +:::warning Preventing the action in this event doesn't work properly with [`@react-navigation/native-stack`](native-stack-navigator.md). We recommend using the [`usePreventRemove` hook](preventing-going-back.md) instead. @@ -263,7 +269,7 @@ class Profile extends React.Component { } ``` -One thing to keep in mind is that you can only listen to events from the immediate navigator with `addListener`. For example, if you try to add a listener in a screen that's inside a stack that's nested in a tab, it won't get the `tabPress` event. If you need to listen to an event from a parent navigator, you may use [`navigation.getParent`](navigation-object.md#getparent) to get a reference to the parent screen's navigation object and add a listener. +Keep in mind that you can only listen to events from the immediate navigator with `addListener`. For example, if you try to add a listener in a screen that's inside a stack that's nested in a tab, it won't get the `tabPress` event. If you need to listen to an event from a parent navigator, you may use [`navigation.getParent`](navigation-object.md#getparent) to get a reference to the parent screen's navigation object and add a listener. ```js const unsubscribe = navigation @@ -275,6 +281,12 @@ const unsubscribe = navigation Here `'MyTabs'` refers to the value you pass in the `id` prop of the parent `Tab.Navigator` whose event you want to listen to. +:::warning + +The component needs to be rendered for the listeners to be added in `useEffect` or `componentDidMount`. Navigators such as [bottom tabs](bottom-tab-navigator.md) and [drawer](drawer-navigator.md) lazily render the screen after navigating to it. So if your listener is not being called, double check that the component is rendered. + +::: + ### `listeners` prop on `Screen` Sometimes you might want to add a listener from the component where you defined the navigator rather than inside the screen. You can use the `listeners` prop on the `Screen` component to add listeners. The `listeners` prop takes an object with the event names as keys and the listener callbacks as values. diff --git a/versioned_docs/version-7.x/navigation-lifecycle.md b/versioned_docs/version-7.x/navigation-lifecycle.md index 4119652a4e4..d2a2317652b 100755 --- a/versioned_docs/version-7.x/navigation-lifecycle.md +++ b/versioned_docs/version-7.x/navigation-lifecycle.md @@ -474,7 +474,7 @@ export default function App() { See [Navigation events](navigation-events.md) for more details on the available events and the API usage. -Instead of adding event listeners manually, we can use the [`useFocusEffect`](use-focus-effect.md) hook to perform side effects. It's like React's `useEffect` hook, but it ties into the navigation lifecycle. +For performing side effects, we can use the [`useFocusEffect`](use-focus-effect.md) hook instead of subscribing to events. It's like React's `useEffect` hook, but it ties into the navigation lifecycle. Example: @@ -622,7 +622,11 @@ export default function App() { If you want to render different things based on if the screen is focused or not, you can use the [`useIsFocused`](use-is-focused.md) hook which returns a boolean indicating whether the screen is focused. +If you want to know if the screen is focused or not inside of an event listener, you can use the [`navigation.isFocused()`](navigation-object.md#isfocused) method. Note that using this method doesn't trigger a re-render like the `useIsFocused` hook does, so it is not suitable for rendering different things based on focus state. + ## Summary -- While React's lifecycle methods are still valid, React Navigation adds more events that you can subscribe to through the `navigation` object. -- You may also use the `useFocusEffect` or `useIsFocused` hooks. +- React Navigation does not unmount screens when navigating away from them +- The [`useFocusEffect`](use-focus-effect.md) hook is analogous to React's [`useEffect`](https://react.dev/reference/react/useEffect) but is tied to the navigation lifecycle instead of the component lifecycle. +- The [`useIsFocused`](use-is-focused.md) hook and [`navigation.isFocused()`](navigation-object.md#isfocused) method can be used to determine if a screen is currently focused. +- React Navigation emits [`focus`](navigation-events.md#focus) and [`blur`](navigation-events.md#blur) events that can be listened to when a screen comes into focus or goes out of focus. diff --git a/versioned_docs/version-7.x/navigation-object.md b/versioned_docs/version-7.x/navigation-object.md index c61d575c806..42af46e61b2 100755 --- a/versioned_docs/version-7.x/navigation-object.md +++ b/versioned_docs/version-7.x/navigation-object.md @@ -76,7 +76,9 @@ The `navigate` method lets us navigate to another screen in your app. It takes t - `name` - _string_ - A destination name of the screen in the current or a parent navigator. - `params` - _object_ - Params to use for the destination route. -- `merge` - _boolean_ - Whether params should be merged with the existing route params, or replace them (when navigating to an existing screen). Defaults to `false`. +- `options` - Options object containing the following properties: + - `merge` - _boolean_ - Whether params should be merged with the existing route params, or replace them (when navigating to an existing screen). Defaults to `false`. + - `pop` - _boolean_ - Whether screens should be popped to navigate to a matching screen in the stack. Defaults to `false`. @@ -248,40 +250,6 @@ In a stack navigator ([stack](stack-navigator.md) or [native stack](native-stack - If the [`getId`](screen.md#id) prop is specified, and another screen in the stack has the same ID, it will bring that screen to focus and update its params instead. - If none of the above conditions match, it'll push a new screen to the stack. -By default, the screen is identified by its name. But you can also customize it to take the params into account by using the [`getId`](screen.md#id) prop. - -For example, say you have specified a `getId` prop for `Profile` screen: - - - - -```js -const Tabs = createBottomTabNavigator({ - screens: { - Profile: { - screen: ProfileScreen, - getId: ({ params }) => params.userId, - }, - }, -}); -``` - - - - -```js - params.userId} -/> -``` - - - - -Now, if you have a stack with the history `Home > Profile (userId: bob) > Settings` and you call `navigate(Profile, { userId: 'alice' })`, the resulting screens will be `Home > Profile (userId: bob) > Settings > Profile (userId: alice)` since it'll add a new `Profile` screen as no matching screen was found. - In a tab or drawer navigator, calling `navigate` will switch to the relevant screen if it's not focused already and update the params of the screen. ### `navigateDeprecated` @@ -304,9 +272,8 @@ It takes the following arguments: In a stack navigator ([stack](stack-navigator.md) or [native stack](native-stack-navigator.md)), calling `navigate` with a screen name will have the following behavior: - If you're already on a screen with the same name, it will update its params and not push a new screen. -- If a screen with the same name already exists in the stack, it will pop all the screens after it to go back to the existing screen. -- If the [`getId`](screen.md#id) prop is specified, and another screen in the stack has the same ID, it will pop any screens to navigate to that screen and update its params instead. -- If none of the above conditions match, it'll push a new screen to the stack. +- If you're on a different screen, it will push the new screen onto the stack. +- If the [`getId`](screen.md#id) prop is specified, and another screen in the stack has the same ID, it will bring that screen to focus and update its params instead. In a tab or drawer navigator, calling `navigate` will switch to the relevant screen if it's not focused already and update the params of the screen. @@ -484,7 +451,7 @@ The `reset` method lets us replace the navigator state with a new state: -```js name="Navigate - replace and reset" snack +```js name="Navigation object replace and reset" snack import * as React from 'react'; import { Button } from '@react-navigation/elements'; import { View, Text } from 'react-native'; @@ -623,7 +590,7 @@ export default App; -```js name="Navigate - replace and reset" snack +```js name="Navigation object replace and reset" snack import * as React from 'react'; import { Button } from '@react-navigation/elements'; import { View, Text } from 'react-native'; @@ -1005,7 +972,7 @@ The `setParams` method lets us update the params (`route.params`) of the current -```js name="Navigate - setParams" snack +```js name="Navigation object setParams" snack import * as React from 'react'; import { Button } from '@react-navigation/elements'; import { View, Text } from 'react-native'; @@ -1107,7 +1074,7 @@ export default App; -```js name="Navigate - setParams" snack +```js name="Navigation object setParams" snack import * as React from 'react'; import { Button } from '@react-navigation/elements'; import { View, Text } from 'react-native'; @@ -1206,6 +1173,214 @@ export default App; +### `replaceParams` + +The `replaceParams` method lets us replace the params (`route.params`) of the current screen with a new params object. + + + + +```js name="Navigation object replaceParams" snack +import * as React from 'react'; +import { Button } from '@react-navigation/elements'; +import { View, Text } from 'react-native'; +import { + useNavigation, + createStaticNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + This is the home screen of the app + + + ); +} + +// codeblock-focus-start +function ProfileScreen({ route }) { + const navigation = useNavigation(); + + return ( + + Profile Screen + Friends: + {route.params.friends[0]} + {route.params.friends[1]} + {route.params.friends[2]} + + + + ); +} +// codeblock-focus-end + +const Stack = createNativeStackNavigator({ + initialRouteName: 'Home', + screens: { + Home: HomeScreen, + Profile: { + screen: ProfileScreen, + options: ({ route }) => ({ title: route.params.title }), + }, + }, +}); + +const Navigation = createStaticNavigation(Stack); + +function App() { + return ; +} + +export default App; +``` + + + + +```js name="Navigation object replaceParams" snack +import * as React from 'react'; +import { Button } from '@react-navigation/elements'; +import { View, Text } from 'react-native'; +import { NavigationContainer, useNavigation } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +function HomeScreen() { + const navigation = useNavigation(); + + return ( + + This is the home screen of the app + + + ); +} + +// codeblock-focus-start +function ProfileScreen({ route }) { + const navigation = useNavigation(); + + return ( + + Profile Screen + Friends: + {route.params.friends[0]} + {route.params.friends[1]} + {route.params.friends[2]} + + + + ); +} +// codeblock-focus-end + +const Stack = createNativeStackNavigator(); + +function App() { + return ( + + + + ({ title: route.params.title })} + /> + + + ); +} + +export default App; +``` + + + + ### `setOptions` The `setOptions` method lets us set screen options from within the component. This is useful if we need to use the component's props, state or context to configure our screen. @@ -1213,7 +1388,7 @@ The `setOptions` method lets us set screen options from within the component. Th -```js name="Navigate - setOptions" snack +```js name="Navigation object setOptions" snack import * as React from 'react'; import { View, Text, TextInput } from 'react-native'; import { Button } from '@react-navigation/elements'; @@ -1303,7 +1478,7 @@ export default App; -```js name="Navigate - setOptions" snack +```js name="Navigation object setOptions" snack import * as React from 'react'; import { View, Text, TextInput } from 'react-native'; import { Button } from '@react-navigation/elements'; diff --git a/versioned_docs/version-7.x/navigation-solutions-and-community-libraries.md b/versioned_docs/version-7.x/navigation-solutions-and-community-libraries.md deleted file mode 100755 index 1891a0afcf4..00000000000 --- a/versioned_docs/version-7.x/navigation-solutions-and-community-libraries.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -id: navigation-solutions-and-community-libraries -title: Navigation Solutions and Community Libraries -sidebar_label: Community Libraries ---- - -:::note - -Libraries listed in this guide may not have been updated to work with the latest version of React Navigation. Please refer to the library's documentation to see which version of React Navigation it supports. - -::: - -## Solutions built on top of React Navigation - -### Solito - -A tiny wrapper around React Navigation and Next.js that lets you share navigation code across platforms. Also, it provides a set of patterns and examples for building cross-platform apps with React Native + Next.js. - -[Documentation](https://solito.dev/) - -[github.com/nandorojo/solito](https://github.com/nandorojo/solito) - -### Expo Router - -File-based router for React Native apps. With Expo Router pages are automatically generated by simply creating files in a project. - -[Documentation](https://expo.github.io/router/docs) - -[github.com/expo/router](https://github.com/expo/router) - -### Navio - -A navigation library built on top of React Navigation. It's main goal is to improve DX by building the app layout in one place and using the power of TypeScript to provide route names autocompletion. - -[github.com/kanzitelli/rn-navio](https://github.com/kanzitelli/rn-navio) - -[Demo on Snack](https://snack.expo.dev/@kanzitelli/rn-navio-snack) - -## Community libraries - -### react-native-paper - -The [React Native Paper](https://callstack.github.io/react-native-paper/) library provides React Navigation integration for its Material Bottom Tabs. Material Bottom Tabs is a material-design themed tab bar on the bottom of the screen that lets you switch between different routes with animation. - -[callstack.github.io/react-native-paper/docs/guides/bottom-navigation](https://callstack.github.io/react-native-paper/docs/guides/bottom-navigation/) - -### react-native-screens - -This project aims to expose native navigation container components to React Native and React Navigation can integrate with it since version 2.14.0. Using `react-native-screens` brings several benefits, such as support for the ["reachability feature"](https://www.cnet.com/how-to/how-to-use-reachability-on-iphone-6-6-plus/) on iOS, and improved memory consumption on both platforms. - -[github.com/software-mansion/react-native-screens](https://github.com/software-mansion/react-native-screens) - -### react-navigation-header-buttons - -Helps you to render buttons in the navigation bar and handle the styling so you don't have to. It tries to mimic the appearance of native navbar buttons and attempts to offer a simple interface for you to interact with. - -[github.com/vonovak/react-navigation-header-buttons](https://github.com/vonovak/react-navigation-header-buttons) - -[Demo on expo](https://expo.io/@vonovak/navbar-buttons-demo) - -### react-navigation-props-mapper - -Provides simple HOCs that map react-navigation props to your screen components directly - ie. instead of `const user = this.props.route.params.activeUser`, you'd write `const user = this.props.activeUser`. - -[github.com/vonovak/react-navigation-props-mapper](https://github.com/vonovak/react-navigation-props-mapper) - -## react-native-bottom-tabs - -This project aims to expose the native Bottom Tabs component to React Native. It exposes SwiftUI's TabView on iOS and the material design tab bar on Android. Using `react-native-bottom-tabs` can bring several benefits, including multi-platform support and a native-feeling tab bar. - -[github.com/okwasniewski/react-native-bottom-tabs](https://github.com/okwasniewski/react-native-bottom-tabs) diff --git a/versioned_docs/version-7.x/navigation-state.md b/versioned_docs/version-7.x/navigation-state.md index 46d2bf13a2c..43d2a59a7b5 100644 --- a/versioned_docs/version-7.x/navigation-state.md +++ b/versioned_docs/version-7.x/navigation-state.md @@ -30,14 +30,14 @@ There are few properties present in every navigation state object: - `routes` - List of route objects (screens) which are rendered in the navigator. It also represents the history in a stack navigator. There should be at least one item present in this array. - `index` - Index of the focused route object in the `routes` array. - `history` - A list of visited items. This is an optional property and not present in all navigators. For example, it's only present in tab and drawer navigators in the core. The shape of the items in the `history` array can vary depending on the navigator. There should be at least one item present in this array. -- `stale` - A navigation state is assumed to be stale unless the `stale` property is explicitly set to `false`. This means that the state object needs to be ["rehydrated"](#partial-state-objects). +- `stale` - A navigation state is assumed to be stale unless the `stale` property is explicitly set to `false`. This means that the state object needs to be ["rehydrated"](#stale-state-objects). Each route object in a `routes` array may contain the following properties: - `key` - Unique key of the screen. Created automatically or added while navigating to this screen. - `name` - Name of the screen. Defined in navigator component hierarchy. - `params` - An optional object containing params which is defined while navigating e.g. `navigate('Home', { sortBy: 'latest' })`. -- `state` - An optional object containing the navigation state of a child navigator nested inside this screen. +- `state` - An optional object containing the [stale navigation state](#stale-state-objects) of a child navigator nested inside this screen. For example, a stack navigator containing a tab navigator nested inside it's home screen may have a navigation state object like this: @@ -69,11 +69,17 @@ const state = { It's important to note that even if there's a nested navigator, the `state` property on the `route` object is not added until a navigation happens, hence it's not guaranteed to exist. -## Partial state objects +## Stale state objects -Earlier there was a mention of `stale` property in the navigation state. A stale navigation state means that the state object needs to be rehydrated or fixed or fixed up, such as adding missing keys, removing invalid screens etc. before being used. As a user, you don't need to worry about it, React Navigation will fix up any issues in a state object automatically unless `stale` is set to `false`. If you're writing a [custom router](custom-routers.md), the `getRehydratedState` method let's you write custom rehydration logic to fix up state objects. +Earlier there was a mention of `stale` property in the navigation state. If the `stale` property is set to `true` or is missing, the state is assumed to be stale. A stale navigation state means that the state object may be partial, such as missing keys or routes, contain invalid routes, or may not be up-to-date. A stale state can be a result of [deep linking](deep-linking.md), r[estoring from a persisted state](state-persistence.md) etc. -This also applies to the `index` property: `index` should be the last route in a stack, and if a different value was specified, React Navigation fixes it. For example, if you wanted to reset your app's navigation state to have it display the `Profile` route, and have the `Home` route displayed upon going back, and did the below, +If you're accessing the navigation state of a navigator using the built-in APIs such as [`useNavigationState()`](use-navigation-state.md), [`navigation.getState()`](navigation-object.md#getstate) etc., the state object is guaranteed to be complete and not stale. However, if you try to access a child navigator's state with the `state` property on the `route` object, it maybe a stale or partial state object. So it's not recommended to use this property directly. + +Using the [`ref.getRootState()`](navigation-container.md#getrootstate) API will always return a complete and up-to-date state object for the current navigation tree, including any nested child navigators. + +When React Navigation encounters stale or partial state, it will automatically fix it up before using it. This includes adding missing keys, removing any invalid routes, ensuring the `index` is correct etc. This process of fixing stale state is called **rehydration**. If you're writing a [custom router](custom-routers.md), the `getRehydratedState` method lets you write custom rehydration logic to fix up state objects. + +For example, `index` should be the last route in a stack, and if a different value was specified, React Navigation fixes it. For example, if you wanted to reset your app's navigation state to have it display the `Profile` route, and have the `Home` route displayed upon going back, and dispatched the following action: ```js navigation.reset({ @@ -82,7 +88,7 @@ navigation.reset({ }); ``` -React Navigation would correct `index` to 1, and display the route and perform navigation as intended. +React Navigation would correct `index` to `1` before the routes are displayed. This feature comes handy when doing operations such as [reset](navigation-actions.md#reset), [providing a initial state](navigation-container.md#initialstate) etc., as you can safely omit many properties from the navigation state object and relying on React Navigation to add those properties for you, making your code simpler. For example, you can only provide a `routes` array without any keys and React Navigation will automatically add everything that's needed to make it work: @@ -118,4 +124,4 @@ If you want React Navigation to fix invalid state, you need to make sure that yo ::: -When you're providing a state object in [`initialState`](navigation-container.md#initialstate), React Navigation will always assume that it's a stale state object, which makes sure that things like state persistence work smoothly without extra manipulation of the state object. +When you're providing a state object in [`initialState`](navigation-container.md#initialstate), React Navigation will always assume that it's a stale state object, since navigation configuration may have changed since the last time. This makes sure that things like [state persistence](state-persistence.md) work smoothly without extra manipulation of the state object. diff --git a/versioned_docs/version-7.x/navigator.md b/versioned_docs/version-7.x/navigator.md index 6d10993cae9..f34638f4be2 100644 --- a/versioned_docs/version-7.x/navigator.md +++ b/versioned_docs/version-7.x/navigator.md @@ -132,7 +132,9 @@ function MyStack() { A layout is a wrapper around the navigator. It can be useful for augmenting the navigators with additional UI with a wrapper. -The difference from adding a wrapper around the navigator manually is that the code in a layout callback has access to the navigator's state, options etc.: +The difference from adding a wrapper around the navigator manually is that the code in a layout callback has access to the navigator's state, options etc. + +It takes a function that returns a React element: @@ -242,7 +244,9 @@ Event listeners can be used to subscribe to various events emitted for the scree ### Screen layout -A screen layout is a wrapper around each screen in the navigator. It makes it easier to provide things such as a common error boundary and suspense fallback for all screens in the navigator: +A screen layout is a wrapper around each screen in the navigator. It makes it easier to provide things such as an error boundary and suspense fallback for all screens in the navigator, or wrap each screen with additional UI. + +It takes a function that returns a React element: @@ -305,3 +309,155 @@ function MyStack() { + +### Router + +:::warning + +This API is experimental and may change in a minor release. + +::: + +Routers can be customized with the `UNSTABLE_router` prop on navigator to override how navigation actions are handled. + +It takes a function that receives the original router and returns an object with overrides: + + + + +```js +const MyStack = createNativeStackNavigator({ + // highlight-start + UNSTABLE_router: (original) => ({ + getStateForAction(state, action) { + if (action.type === 'SOME_ACTION') { + // Custom logic + } + + // Fallback to original behavior + return original.getStateForAction(state, action); + }, + }), + // highlight-end + screens: { + Home: HomeScreen, + Profile: ProfileScreen, + }, +}); +``` + + + + +```js +const Stack = createNativeStackNavigator(); + +function MyStack() { + return ( + ({ + getStateForAction(state, action) { + if (action.type === 'SOME_ACTION') { + // Custom logic + } + + // Fallback to original behavior + return original.getStateForAction(state, action); + }, + })} + // highlight-end + > + + + + ); +} +``` + + + + +The function passed to `UNSTABLE_router` **must be a pure function and cannot reference outside dynamic variables**. + +The overrides object is shallow merged with the original router. So you don't need to specify all properties of the router, only the ones you want to override. + +See [custom routers](custom-routers.md) for more details on routers. + +### Route names change behavior + +:::warning + +This API is experimental and may change in a minor release. + +::: + +When the list of available routes in a navigator changes dynamically, e.g. based on conditional rendering, looping over data from an API etc., the navigator needs to update the [navigation state](navigation-state.md) according to the new list of routes. + +By default, it works as follows: + +- Any routes not present in the new available list of routes are removed from the navigation state +- If the currently focused route is still present in the new available list of routes, it remains focused. +- If the currently focused route has been removed, but the navigation state has other routes that are present in the new available list, the first route in from the list of rendered routes becomes focused. +- If none of the routes in the navigation state are present in the new available list of routes, one of the following things can happen based on the `UNSTABLE_routeNamesChangeBehavior` prop: + - `'firstMatch'` - The first route defined in the new list of routes becomes focused. This is the default behavior based on [`getStateForRouteNamesChange`](custom-routers.md) in the router. + - `'lastUnhandled'` - The last state that was unhandled due to conditional rendering is restored. + +Example cases where state might have been unhandled: + +- Opened a deep link to a screen, but a login screen was shown. +- Navigated to a screen containing a navigator, but a different screen was shown. +- Reset the navigator to a state with different routes not matching the available list of routes. + +In these cases, specifying `'lastUnhandled'` will reuse the unhandled state if present. If there's no unhandled state, it will fallback to `'firstMatch'` behavior. + +Caveats: + +- Direct navigation is only handled for `NAVIGATE` actions. +- Unhandled state is restored only if the current state becomes invalid, i.e. it doesn't contain any currently defined screens. + +Example usage: + + + + +```js +const RootStack = createNativeStackNavigator({ + // highlight-next-line + UNSTABLE_routeNamesChangeBehavior: 'lastUnhandled', + screens: { + Home: { + if: useIsSignedIn, + screen: HomeScreen, + }, + SignIn: { + if: useIsSignedOut, + screen: SignInScreen, + options: { + title: 'Sign in', + }, + }, + }, +}); +``` + + + + +```js + + {isSignedIn ? ( + + ) : ( + + )} + +``` + + + + +The most common use case for this is to [show the correct screen based on authentication based on deep link](auth-flow.md#handling-deep-links-after-auth). diff --git a/versioned_docs/version-7.x/params.md b/versioned_docs/version-7.x/params.md index c5c5b26d1c6..4905bd4ef55 100755 --- a/versioned_docs/version-7.x/params.md +++ b/versioned_docs/version-7.x/params.md @@ -193,9 +193,11 @@ export default function App() { } ``` +The `setParams` method merges the new params with the existing ones. To replace the existing params, you can use [`replaceParams`](navigation-object.md#replaceparams) instead. + :::note -Avoid using `setParams` to update screen options such as `title` etc. If you need to update options, use [`setOptions`](navigation-object.md#setoptions) instead. +Avoid using `setParams` or `replaceParams` to update screen options such as `title` etc. If you need to update options, use [`setOptions`](navigation-object.md#setoptions) instead. ::: @@ -371,6 +373,17 @@ export default function App() { See [Nesting navigators](nesting-navigators.md) for more details on nesting. +## Reserved param names + +Some param names are reserved by React Navigation as part of the API for nested navigators. The list of the reserved param names are as follows: + +- `screen` +- `params` +- `initial` +- `state` + +You should avoid using these param names in your code unless navigating to a screen containing a nested navigator. Otherwise it will result in unexpected behavior, such as the screen not being able to access the params you passed. If you need to pass data to a nested screen, use a different names for the param. + ## What should be in params Params are essentially options for a screen. They should contain the minimal data required to show a screen, nothing more. If the data is used by multiple screens, it should be in a global store or global cache. Params is not designed for state management. @@ -398,7 +411,6 @@ However, this is an anti-pattern. There are many reasons why this is a bad idea: - The same data is duplicated in multiple places. This can lead to bugs such as the profile screen showing outdated data even if the user object has changed after navigation. - Each screen that navigates to the `Profile` screen now needs to know how to fetch the user object - which increases the complexity of the code. - URLs to the screen (browser URL on the web, or deep links on native) will contain the user object. This is problematic: - 1. Since the user object is in the URL, it's possible to pass a random user object representing a user that doesn't exist or has incorrect data in the profile. 2. If the user object isn't passed or improperly formatted, this could result in crashes as the screen won't know how to handle it. 3. The URL can become very long and unreadable. @@ -409,11 +421,11 @@ A better way is to pass only the ID of the user in params: navigation.navigate('Profile', { userId: 'jane' }); ``` -Now, you can use the passed `userId` to grab the user from your global store. This eliminates a host of issues such as outdated data, or problematic URLs. +Now, you can use the passed `userId` to grab the user from your global cache or fetch it from the API. Using a library such as [React Query](https://tanstack.com/query/) can simplify this process since it makes it easy to fetch and cache your data. This approach helps to avoid the problems mentioned above. Some examples of what should be in params are: -1. IDs like user id, item id etc., e.g. `navigation.navigate('Profile', { userId: 'Jane' })` +1. IDs such as user id, item id etc., e.g. `navigation.navigate('Profile', { userId: 'Jane' })` 2. Params for sorting, filtering data etc. when you have a list of items, e.g. `navigation.navigate('Feeds', { sortBy: 'latest' })` 3. Timestamps, page numbers or cursors for pagination, e.g. `navigation.navigate('Chat', { beforeTime: 1603897152675 })` 4. Data to fill inputs on a screen to compose something, e.g. `navigation.navigate('ComposeTweet', { title: 'Hello world!' })` @@ -422,8 +434,9 @@ In essence, pass the least amount of data required to identify a screen in param ## Summary -- `navigate` and `push` accept an optional second argument to let you pass parameters to the route you are navigating to. For example: `navigation.navigate('RouteName', { paramName: 'value' })`. -- You can read the params through `route.params` inside a screen -- You can update the screen's params with `navigation.setParams` -- Initial params can be passed via the `initialParams` prop on `Screen` +- [`navigate`](navigation-actions.md#navigate) and [`push`](stack-actions.md#push) accept an optional second argument to let you pass parameters to the route you are navigating to. For example: `navigation.navigate('RouteName', { paramName: 'value' })`. +- You can read the params through [`route.params`](route-object.md) inside a screen +- You can update the screen's params with [`navigation.setParams`](navigation-object.md#setparams) or [`navigation.replaceParams`](navigation-object.md#replaceparams) +- Initial params can be passed via the [`initialParams`](screen.md#initial-params) prop on `Screen` or in the navigator config - Params should contain the minimal data required to show a screen, nothing more +- Some [param names are reserved](#reserved-param-names) by React Navigation and should be avoided diff --git a/versioned_docs/version-7.x/screen-tracking.md b/versioned_docs/version-7.x/screen-tracking.md index 26e6a0970ec..87786b0bfe4 100644 --- a/versioned_docs/version-7.x/screen-tracking.md +++ b/versioned_docs/version-7.x/screen-tracking.md @@ -181,3 +181,9 @@ export default function App() { + +:::note + +If you are building a library that wants to provide screen tracking integration with React Navigation, you can accept a [`ref`](navigation-container.md#ref) to the navigation container and use the [`ready`](navigation-container.md#ready) and [`state`](navigation-container.md#state) events instead of `onReady` and `onStateChange` props to keep your logic self-contained. + +::: diff --git a/versioned_docs/version-7.x/screen.md b/versioned_docs/version-7.x/screen.md index 8589e630686..354b1a00ba4 100644 --- a/versioned_docs/version-7.x/screen.md +++ b/versioned_docs/version-7.x/screen.md @@ -218,7 +218,7 @@ This can be done by specifying the `getId` callback. It receives an object with ```js -const Stack = createNativeStackNavigator({ +const Stack = createStackNavigator({ screens: { Profile: { screen: ProfileScreen, @@ -244,49 +244,22 @@ const Stack = createNativeStackNavigator({ -By default, `navigate('ScreenName', params)` updates the current screen if the screen name matches, otherwise adds a new screen in a stack navigator. So if you're on `ScreenName` and navigate to `ScreenName` again, it won't add a new screen even if the params are different - it'll update the current screen with the new params instead: - -```js -// Let's say you're on `Home` screen -// Then you navigate to `Profile` screen with `userId: 1` -navigation.navigate('Profile', { userId: 1 }); - -// Now the stack will have: `Home` -> `Profile` with `userId: 1` - -// Then you navigate to `Profile` screen again with `userId: 2` -navigation.navigate('Profile', { userId: 2 }); - -// The stack will now have: `Home` -> `Profile` with `userId: 2` -``` - -If you specify `getId` and it doesn't return `undefined`, the screen is identified by both the screen name and the returned ID. That means that if you're on `ScreenName` and navigate to `ScreenName` again with different params - and return a different ID from the `getId` callback, it'll add a new screen to the stack: - -```js -// Let's say you're on `Home` screen -// Then you navigate to `Profile` screen with `userId: 1` -navigation.navigate('Profile', { userId: 1 }); - -// Now the stack will have: `Home` -> `Profile` with `userId: 1` - -// Then you navigate to `Profile` screen again with `userId: 2` -navigation.navigate('Profile', { userId: 2 }); +In the above example, `params.userId` is used as an ID for the `Profile` screen with `getId`. This changes how the navigation works to ensure that the screen with the same ID appears only once in the stack. -// The stack will now have: `Home` -> `Profile` with `userId: 1` -> `Profile` with `userId: 2` -``` +Let's say you have a stack with the history `Home > Profile (userId: bob) > Settings`, consider following scenarios: -The `getId` callback can also be used to ensure that the screen with the same ID doesn't appear multiple times in the stack: +- You call `navigate(Profile, { userId: 'bob' })`: + The resulting screens will be `Home > Settings > Profile (userId: bob)` since the existing `Profile` screen matches the ID. +- You call `navigate(Profile, { userId: 'alice' })`: + The resulting screens will be `Home > Profile (userId: bob) > Settings > Profile (userId: alice)` since it'll add a new `Profile` screen as no matching screen was found. -```js -// Let's say you have a stack with the screens: `Home` -> `Profile` with `userId: 1` -> `Settings` -// Then you navigate to `Profile` screen with `userId: 1` again -navigation.navigate('Profile', { userId: 1 }); +If `getId` is specified in a tab or drawer navigator, the screen will remount if the ID changes. -// Now the stack will have: `Home` -> `Profile` with `userId: 1` -``` +:::warning -In the above examples, `params.userId` is used as an ID, subsequent navigation to the screen with the same `userId` will navigate to the existing screen instead of adding a new one to the stack. If the navigation was with a different `userId`, then it'll add a new screen. +If you're using [`@react-navigation/native-stack`](native-stack-navigator.md), it doesn't work correctly with the `getId` callback. So it's recommended to avoid using it in that case. -If `getId` is specified in a tab or drawer navigator, the screen will remount if the ID changes. +::: ### Component @@ -361,7 +334,9 @@ By default, React Navigation applies optimizations to screen components to preve ### Layout -A layout is a wrapper around the screen. It makes it easier to provide things such as an error boundary and suspense fallback for a screen: +A layout is a wrapper around the screen. It makes it easier to provide things such as an error boundary and suspense fallback for a screen, or wrap the screen with additional UI. + +It takes a function that returns a React element: diff --git a/versioned_docs/version-7.x/server-rendering.md b/versioned_docs/version-7.x/server-rendering.md index 0c388ffcb38..4dddb2019b0 100644 --- a/versioned_docs/version-7.x/server-rendering.md +++ b/versioned_docs/version-7.x/server-rendering.md @@ -12,7 +12,7 @@ This guide will cover how to server render your React Native app using React Nat 1. Rendering the correct layout depending on the request URL 2. Setting appropriate page metadata based on the focused screen -::: warning +:::warning Server rendering support is currently limited. It's not possible to provide a seamless SSR experience due to a lack of APIs such as media queries. In addition, many third-party libraries often don't work well with server rendering. diff --git a/versioned_docs/version-7.x/shared-element-transitions.md b/versioned_docs/version-7.x/shared-element-transitions.md index 7b4f50d6888..18d5fd917f0 100644 --- a/versioned_docs/version-7.x/shared-element-transitions.md +++ b/versioned_docs/version-7.x/shared-element-transitions.md @@ -7,12 +7,14 @@ sidebar_label: Shared element transitions import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -This guide covers how to animate elements between screens. This feature is known as a [Shared Element Transition](https://docs.swmansion.com/react-native-reanimated/docs/api/sharedElementTransitions) and it's implemented in the [`@react-navigation/native-stack`](native-stack-navigator.md) with [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/). +This guide covers how to animate elements between screens. This feature is known as a [Shared Element Transition](https://docs.swmansion.com/react-native-reanimated/docs/shared-element-transitions/overview/) and it's implemented in the [`@react-navigation/native-stack`](native-stack-navigator.md) with [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/). :::warning As of writing this guide, Shared Element Transitions are considered an experimental feature not recommended for production use. +Shared Element Transitions are currently only supported on **old React Native architecture** (Paper). + ::: ); } @@ -303,4 +311,4 @@ if (!isReady) { Each param, route, and navigation state must be fully serializable for this feature to work. Typically, you would serialize the state as a JSON string. This means that your routes and params must contain no functions, class instances, or recursive data structures. React Navigation already [warns you during development](troubleshooting.md#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state) if it encounters non-serializable data, so watch out for the warning if you plan to persist navigation state. -You can modify the initial state object before passing it to container, but note that if your `initialState` isn't a [valid navigation state](navigation-state.md#partial-state-objects), React Navigation may not be able to handle the situation gracefully. +You can modify the initial state object before passing it to container, but note that if your `initialState` isn't a [valid navigation state](navigation-state.md#stale-state-objects), React Navigation may not be able to handle the situation gracefully in some scenarios. diff --git a/versioned_docs/version-7.x/tab-view.md b/versioned_docs/version-7.x/tab-view.md index d30320ea3c9..b1e13b3d2c2 100644 --- a/versioned_docs/version-7.x/tab-view.md +++ b/versioned_docs/version-7.x/tab-view.md @@ -22,21 +22,34 @@ To use this package, open a Terminal in the project root and run: npm install react-native-tab-view ``` -Next, install [`react-native-pager-view`](https://github.com/callstack/react-native-viewpager) if you plan to support iOS and Android. +The library depends on [`react-native-pager-view`](https://github.com/callstack/react-native-pager-view) for rendering the pages. -If you are using Expo, to ensure that you get the compatible versions of the libraries, run: + + + +If you have a Expo managed project, in your project directory, run: ```bash -expo install react-native-pager-view +npx expo install react-native-pager-view ``` -If you are not using Expo, run the following: + + + +If you have a bare React Native project, in your project directory, run: ```bash npm2yarn npm install react-native-pager-view ``` -We're done! Now you can build and run the app on your device/simulator. + + + +If you're on a Mac and developing for iOS, you also need to install [pods](https://cocoapods.org/) to complete the linking. + +```bash +npx pod-install ios +``` ## Quick start diff --git a/versioned_docs/version-7.x/testing.md b/versioned_docs/version-7.x/testing.md index 07b86a0e892..a39c33b1fdb 100644 --- a/versioned_docs/version-7.x/testing.md +++ b/versioned_docs/version-7.x/testing.md @@ -1,115 +1,957 @@ --- id: testing -title: Testing with Jest -sidebar_label: Testing with Jest +title: Writing tests +sidebar_label: Writing tests --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Testing code using React Navigation may require some setup since we need to mock native dependencies used in the navigators. We recommend using [Jest](https://jestjs.io) to write unit tests. +React Navigation components can be tested in a similar way to other React components. This guide will cover how to write tests for components using React Navigation using [Jest](https://jestjs.io). -## Mocking native modules +## Guiding principles + +When writing tests, it's encouraged to write tests that closely resemble how users interact with your app. Keeping this in mind, here are some guiding principles to follow: + +- **Test the result, not the action**: Instead of checking if a specific navigation action was called, check if the expected components are rendered after navigation. +- **Avoid mocking React Navigation**: Mocking React Navigation components can lead to tests that don't match the actual logic. Instead, use a real navigator in your tests. + +Following these principles will help you write tests that are more reliable and easier to maintain by avoiding testing implementation details. + +## Setting up Jest + +### Compiling React Navigation + +React Navigation ships [ES modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). However, Jest does not support ES modules natively. + +It's necessary to transform the code to CommonJS to use them in tests. The `react-native` preset for Jest does not transform the code in `node_modules` by default. To enable this, you need to add the [`transformIgnorePatterns`](https://jestjs.io/docs/configuration#transformignorepatterns-arraystring) option in your Jest configuration where you can specify a regexp pattern. To compile React Navigation packages, you can add `@react-navigation` to the regexp. + +This is usually done in a `jest.config.js` file or the `jest` key in `package.json`: + +```diff lang=json +{ + "preset": "react-native", ++ "transformIgnorePatterns": [ ++ "node_modules/(?!(@react-native|react-native|@react-navigation)/)" ++ ] +} +``` + +### Mocking native dependencies To be able to test React Navigation components, certain dependencies will need to be mocked depending on which components are being used. -If you're using `@react-navigation/drawer`, you will need to mock: +If you're using `@react-navigation/stack`, you will need to mock: -- `react-native-reanimated` - `react-native-gesture-handler` -If you're using `@react-navigation/stack`, you will only need to mock: +If you're using `@react-navigation/drawer`, you will need to mock: +- `react-native-reanimated` - `react-native-gesture-handler` To add the mocks, create a file `jest/setup.js` (or any other file name of your choice) and paste the following code in it: ```js -// include this line for mocking react-native-gesture-handler +// Include this line for mocking react-native-gesture-handler import 'react-native-gesture-handler/jestSetup'; -// include this section and the NativeAnimatedHelper section for mocking react-native-reanimated -jest.mock('react-native-reanimated', () => { - const Reanimated = require('react-native-reanimated/mock'); - - // The mock for `call` immediately calls the callback which is incorrect - // So we override it with a no-op - Reanimated.default.call = () => {}; +// Include this section for mocking react-native-reanimated +import { setUpTests } from 'react-native-reanimated'; - return Reanimated; -}); +setUpTests(); // Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing +import { jest } from '@jest/globals'; + jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper'); ``` -Then we need to use this setup file in our jest config. You can add it under `setupFiles` option in a `jest.config.js` file or the `jest` key in `package.json`: +Then we need to use this setup file in our jest config. You can add it under [`setupFilesAfterEnv`](https://jestjs.io/docs/configuration#setupfilesafterenv-array) option in a `jest.config.js` file or the `jest` key in `package.json`: -```json +```diff lang=json { "preset": "react-native", - "setupFiles": ["/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 `setupFiles` 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` + +This shouldn't be necessary in most cases. However, if you find yourself in a need to mock `react-native-screens` component for some reason, you should do it by adding following code in `jest/setup.js` file: + +```js +// Include this section for mocking react-native-screens +jest.mock('react-native-screens', () => { + // Require actual module instead of a mock + let screens = jest.requireActual('react-native-screens'); + + // All exports in react-native-screens are getters + // We cannot use spread for cloning as it will call the getters + // So we need to clone it with Object.create + screens = Object.create( + Object.getPrototypeOf(screens), + Object.getOwnPropertyDescriptors(screens) + ); + + // Add mock of the component you need + // Here is the example of mocking the Screen component as a View + Object.defineProperty(screens, 'Screen', { + value: require('react-native').View, + }); + + return screens; +}); +``` + +
If you're not using Jest, then you'll need to mock these modules according to the test framework you are using. -## Writing tests +## Fake timers + +When writing tests containing navigation with animations, you need to wait until the animations finish. In such cases, we recommend using [`Fake Timers`](https://jestjs.io/docs/timer-mocks) to simulate the passage of time in your tests. This can be done by adding the following line at the beginning of your test file: + +```js +jest.useFakeTimers(); +``` + +Fake timers replace real implementation of the native timer functions (e.g. `setTimeout()`, `setInterval()` etc,) with a custom implementation that uses a fake clock. This lets you instantly skip animations and reduce the time needed to run your tests by calling methods such as `jest.runAllTimers()`. + +Often, component state is updated after an animation completes. To avoid getting an error in such cases, wrap `jest.runAllTimers()` in `act`: + +```js +import { act } from 'react-test-renderer'; + +// ... + +act(() => jest.runAllTimers()); +``` + +See the examples below for more details on how to use fake timers in tests involving navigation. + +## Navigation and visibility + +In React Navigation, the previous screen is not unmounted when navigating to a new screen. This means that the previous screen is still present in the component tree, but it's not visible. + +When writing tests, you should assert that the expected component is visible or hidden instead of checking if it's rendered or not. React Native Testing Library provides a `toBeVisible` matcher that can be used to check if an element is visible to the user. + +```js +expect(screen.getByText('Settings screen')).toBeVisible(); +``` + +This is in contrast to the `toBeOnTheScreen` matcher, which checks if the element is rendered in the component tree. This matcher is not recommended when writing tests involving navigation. + +By default, the queries from React Native Testing Library (e.g. `getByRole`, `getByText`, `getByLabelText` etc.) [only return visible elements](https://callstack.github.io/react-native-testing-library/docs/api/queries#includehiddenelements-option). So you don't need to do anything special. However, if you're using a different library for your tests, you'll need to account for this behavior. + +## Example tests + +We recommend using [React Native Testing Library](https://callstack.github.io/react-native-testing-library/) to write your tests. + +In this guide, we will go through some example scenarios and show you how to write tests for them using Jest and React Native Testing Library: + +### Navigation between tabs + +In this example, we have a bottom tab navigator with two tabs: Home and Settings. We will write a test that asserts that we can navigate between these tabs by pressing the tab bar buttons. + + + + +```js title="MyTabs.js" +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Text, View } from 'react-native'; + +const HomeScreen = () => { + return ( + + Home screen + + ); +}; + +const SettingsScreen = () => { + return ( + + Settings screen + + ); +}; + +export const MyTabs = createBottomTabNavigator({ + screens: { + Home: HomeScreen, + Settings: SettingsScreen, + }, +}); +``` + + + + +```js title="MyTabs.js" +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { Text, View } from 'react-native'; + +const HomeScreen = () => { + return ( + + Home screen + + ); +}; + +const SettingsScreen = () => { + return ( + + Settings screen + + ); +}; + +const Tab = createBottomTabNavigator(); + +export const MyTabs = () => { + return ( + + + + + ); +}; +``` + + + + + + + +```js title="MyTabs.test.js" +import { expect, jest, test } from '@jest/globals'; +import { createStaticNavigation } from '@react-navigation/native'; +import { act, render, screen, userEvent } from '@testing-library/react-native'; + +import { MyTabs } from './MyTabs'; -We recommend using [React Native Testing Library](https://callstack.github.io/react-native-testing-library/) along with [`jest-native`](https://github.com/testing-library/jest-native) to write your tests. +jest.useFakeTimers(); -Example: +test('navigates to settings by tab bar button press', async () => { + const user = userEvent.setup(); - + const Navigation = createStaticNavigation(MyTabs); + + render(); + + const button = screen.getByRole('button', { name: 'Settings, tab, 2 of 2' }); + + await user.press(button); + + act(() => jest.runAllTimers()); + + expect(screen.getByText('Settings screen')).toBeVisible(); +}); +``` + + + + +```js title="MyTabs.test.js" +import { expect, jest, test } from '@jest/globals'; +import { NavigationContainer } from '@react-navigation/native'; +import { act, render, screen, userEvent } from '@testing-library/react-native'; + +import { MyTabs } from './MyTabs'; + +jest.useFakeTimers(); + +test('navigates to settings by tab bar button press', async () => { + const user = userEvent.setup(); + + render( + + + + ); + + const button = screen.getByLabelText('Settings, tab, 2 of 2'); + + await user.press(button); + + act(() => jest.runAllTimers()); + + expect(screen.getByText('Settings screen')).toBeVisible(); +}); +``` + + + + +In the above test, we: + +- Render the `MyTabs` navigator within a [NavigationContainer](navigation-container.md) in our test. +- Get the tab bar button using the `getByLabelText` query that matches its accessibility label. +- Press the button using `userEvent.press(button)` to simulate a user interaction. +- Run all timers using `jest.runAllTimers()` to skip animations (e.g. animations in the `Pressable` for the button). +- Assert that the `Settings screen` is visible after the navigation. + +### Reacting to a navigation event + +In this example, we have a stack navigator with two screens: Home and Surprise. We will write a test that asserts that the text "Surprise!" is displayed after navigating to the Surprise screen. + + + + +```js title="MyStack.js" +import { useNavigation } from '@react-navigation/native'; +import { createStackNavigator } from '@react-navigation/stack'; +import { Button, Text, View } from 'react-native'; +import { useEffect, useState } from 'react'; + +const HomeScreen = () => { + const navigation = useNavigation(); + + return ( + + Home screen +