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/.github/workflows/main.yml b/.github/workflows/main.yml index b6cae2ead00..28efada4217 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,7 @@ name: Deploy on: + workflow_dispatch: push: branches: - main diff --git a/blog/2020-02-06-react-navigation-5.0.md b/blog/2020-02-06-react-navigation-5.0.md index d167bd53faa..fd15458e996 100644 --- a/blog/2020-02-06-react-navigation-5.0.md +++ b/blog/2020-02-06-react-navigation-5.0.md @@ -53,7 +53,7 @@ Hooks are great for stateful logic and code organization. Now we have several ho ### Update options from component -We’ve added a new [`setOptions`](/docs/5.x/navigation-prop#setoptions---update-screen-options-from-the-component) method on the `navigation` prop to make configuring screen navigation options more intuitive than its `static navigationOptions` predecessor. It lets us **easily set screen options based on props, state or context without messing with params**. Instead of using static options, we can call it anytime to configure the screen. +We’ve added a new [`setOptions`](/docs/5.x/navigation-prop#setoptions) method on the `navigation` prop to make configuring screen navigation options more intuitive than its `static navigationOptions` predecessor. It lets us **easily set screen options based on props, state or context without messing with params**. Instead of using static options, we can call it anytime to configure the screen. ```js navigation.setOptions({ 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/docusaurus.config.js b/docusaurus.config.js index 81d286b274a..dd90010233c 100755 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -12,6 +12,8 @@ export default { favicon: 'img/favicon.ico', organizationName: 'react-navigation', projectName: 'react-navigation.github.io', + onBrokenAnchors: 'throw', + onBrokenMarkdownLinks: 'throw', scripts: ['/js/snack-helpers.js', '/js/toc-fixes.js'], themeConfig: { colorMode: { @@ -107,6 +109,7 @@ export default { }, }, plugins: [ + './src/plugins/disable-fully-specified.mjs', './src/plugins/react-navigation-versions.mjs', [ '@docusaurus/plugin-client-redirects', diff --git a/package.json b/package.json index b3bc78be503..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", @@ -21,7 +21,7 @@ "@docusaurus/preset-classic": "3.6.1", "@docusaurus/remark-plugin-npm2yarn": "3.6.1", "@octokit/graphql": "^7.1.0", - "@react-navigation/core": "7.0.0-alpha.17", + "@react-navigation/core": "^7.0.4", "escape-html": "^1.0.3", "mkdirp": "^3.0.1", "netlify-plugin-cache": "^1.0.3", 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/Splash/index.js b/src/pages/home/Splash/index.js index f9aefc5c5c6..7310f105816 100644 --- a/src/pages/home/Splash/index.js +++ b/src/pages/home/Splash/index.js @@ -5,7 +5,6 @@ import React from 'react'; import SplashLeftIllustration from './SplashLeftIllustration'; import SplashRightIllustration from './SplashRightIllustration'; import styles from './styles.module.css'; -import Spiro from '/img/spiro_header.svg'; export default function Splash() { return ( @@ -13,24 +12,22 @@ export default function Splash() {
-
- -

React Navigation

- Routing and navigation for Expo and React Native apps. + Routing and navigation for React Native and Web apps.

Read Docs Try It diff --git a/src/pages/home/Splash/styles.module.css b/src/pages/home/Splash/styles.module.css index 94c9d6575e8..8cf912d1c3f 100644 --- a/src/pages/home/Splash/styles.module.css +++ b/src/pages/home/Splash/styles.module.css @@ -48,12 +48,6 @@ border-bottom-right-radius: 0.5rem; } -.spiroContainer { - color: var(--ifm-home-color-background-secondary); - position: absolute; - z-index: 0; -} - .mainContent { position: relative; } @@ -62,12 +56,14 @@ font-size: 4.375rem; color: var(--ifm-home-color-text); font-weight: 900; + margin-bottom: 0; } .subText { font-size: 1.5rem; color: var(--ifm-home-color-text); font-weight: 600; + margin-top: 0; } .buttonContainer { 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/src/plugins/disable-fully-specified.mjs b/src/plugins/disable-fully-specified.mjs new file mode 100644 index 00000000000..031a96df5ca --- /dev/null +++ b/src/plugins/disable-fully-specified.mjs @@ -0,0 +1,19 @@ +export default function disableFullySpecified(context, options) { + return { + name: 'disable-fully-specified', + configureWebpack() { + return { + module: { + rules: [ + { + test: /\.js$/, + resolve: { + fullySpecified: false, + }, + }, + ], + }, + }; + }, + }; +} 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/static/js/snack-helpers.js b/static/js/snack-helpers.js index 907443b3781..66f7f19df11 100644 --- a/static/js/snack-helpers.js +++ b/static/js/snack-helpers.js @@ -184,24 +184,11 @@ function appendSnackLink() { }); } -// This is used to update links like the following: -// [Full source of what we have built so far](#example/full-screen-modal) -function transformExistingSnackLinks() { - document.querySelectorAll('a[href*="#example/"]').forEach((a) => { - let urlParts = a.href.split('#example/'); - let templateId = urlParts[urlParts.length - 1]; - a.href = getSnackUrl({ templateId }); - a.target = '_blank'; - }); -} - function initializeSnackObservers() { appendSnackLink(); - transformExistingSnackLinks(); const mutationObserver = new MutationObserver((mutations) => { mutations.forEach(appendSnackLink); - mutations.forEach(transformExistingSnackLinks); }); mutationObserver.observe(document.documentElement, { 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-1.x/navigation-actions.md b/versioned_docs/version-1.x/navigation-actions.md index 43794b24941..361460ce185 100644 --- a/versioned_docs/version-1.x/navigation-actions.md +++ b/versioned_docs/version-1.x/navigation-actions.md @@ -13,15 +13,15 @@ The following actions are supported: - [Navigate](#navigate) - Navigate to another route - [Back](#back) - Go back to previous state - [Set Params](#setparams) - Set Params for given route -- [Init](#init) - Used to initialize first state if state is undefined +- Init - Used to initialize first state if state is undefined Within a stack, you can also use: - [Reset](#reset) - Replace current state with a new state - [Replace](#replace) - Replace a route at a given key with another route -- [Push](#push) - Add a route on the top of the stack, and navigate forward to it -- [Pop](#pop) - Navigate back to previous routes -- [PopToTop](#poptotop) - Navigate to the top route of the stack, dismissing all other routes +- Push - Add a route on the top of the stack, and navigate forward to it +- Pop - Navigate back to previous routes +- PopToTop - Navigate to the top route of the stack, dismissing all other routes The action creator functions define `toString()` to return the action type, which enables easy usage with third-party Redux libraries, including redux-actions and redux-saga. diff --git a/versioned_docs/version-1.x/navigation-options.md b/versioned_docs/version-1.x/navigation-options.md index b5cd08a7e31..5cd2848c3cb 100644 --- a/versioned_docs/version-1.x/navigation-options.md +++ b/versioned_docs/version-1.x/navigation-options.md @@ -102,5 +102,5 @@ List of available navigation options depends on the `navigator` the screen is ad Check available options for: - [`drawer navigator`](drawer-navigator.md#screen-navigation-options) -- [`stack navigator`](stack-navigator.md#screen-navigation-options) -- [`tab navigator`](tab-navigator.md#screen-navigation-options) +- [`stack navigator`](stack-navigator.md#navigationoptions-used-by-stacknavigator) +- [`tab navigator`](tab-navigator.md#navigationoptions-used-by-tabnavigator) diff --git a/versioned_docs/version-1.x/redux-integration.md b/versioned_docs/version-1.x/redux-integration.md index d0064b7fa15..bb6fadee8ec 100644 --- a/versioned_docs/version-1.x/redux-integration.md +++ b/versioned_docs/version-1.x/redux-integration.md @@ -14,7 +14,7 @@ _You probably do not need to do this!_ Storing your React Navigation state in yo 1. To handle your app's navigation state in Redux, you can pass your own `navigation` prop to a navigator. -2. Once you pass your own navigation prop to the navigator, the default [`navigation`](navigation-prop.md) prop gets destroyed. You must construct your own `navigation` prop with [`state`](navigation-prop.md#state-the-screen-s-current-state-route), [`dispatch`](navigation-prop.md#dispatch-send-an-action-to-the-router), and `addListener` properties. +2. Once you pass your own navigation prop to the navigator, the default [`navigation`](navigation-prop.md) prop gets destroyed. You must construct your own `navigation` prop with [`state`](navigation-prop.md#state---the-screens-current-stateroute), [`dispatch`](navigation-prop.md#dispatch---send-an-action-to-the-router), and `addListener` properties. 3. The `state` will be fed from the reducer assigned to handle navigation state and the `dispatch` will be Redux's default `dispatch`. Thus you will be able to dispatch normal redux actions using `this.props.navigation.dispatch(ACTION)`, reducer will update the navigation state on the basis of dispatched action, the new navigation state will then be passed to the navigator. diff --git a/versioned_docs/version-2.x/custom-android-back-button-handling.md b/versioned_docs/version-2.x/custom-android-back-button-handling.md index 10b48240c14..0b5f3beb7de 100644 --- a/versioned_docs/version-2.x/custom-android-back-button-handling.md +++ b/versioned_docs/version-2.x/custom-android-back-button-handling.md @@ -8,7 +8,7 @@ By default, when user presses the Android hardware back button, react-navigation > If you're looking for an easy-to-use solution, you can check out a community-developed package [react-navigation-backhandler](https://github.com/vonovak/react-navigation-backhandler). The following text shows what the package does under the cover. -As an example, consider a screen where user is selecting items in a list, and a "selection mode" is active. On a back button press, you would first want the "selection mode" to be deactivated, and the screen should be popped only on the second back button press. The following code snippet demonstrates the situation. We make use of [`BackHandler`](https://reactnative.dev/docs/backhandler.html) which comes with react-native and we [subscribe to navigation lifecycle updates](navigation-prop.md#addlistener-subscribe-to-updates-to-navigation-lifecycle) to add our custom `hardwareBackPress` listener. +As an example, consider a screen where user is selecting items in a list, and a "selection mode" is active. On a back button press, you would first want the "selection mode" to be deactivated, and the screen should be popped only on the second back button press. The following code snippet demonstrates the situation. We make use of [`BackHandler`](https://reactnative.dev/docs/backhandler.html) which comes with react-native and we [subscribe to navigation lifecycle updates](navigation-prop.md#addlistener---subscribe-to-updates-to-navigation-lifecycle) to add our custom `hardwareBackPress` listener. Returning `true` from `onBackButtonPressAndroid` denotes that we have handled the event, and react-navigation's listener will not get called, thus not popping the screen. Returning `false` will cause the event to bubble up and react-navigation's listener will pop the screen. diff --git a/versioned_docs/version-2.x/custom-navigator-overview.md b/versioned_docs/version-2.x/custom-navigator-overview.md index a633b82f0d0..c1323d63ce7 100644 --- a/versioned_docs/version-2.x/custom-navigator-overview.md +++ b/versioned_docs/version-2.x/custom-navigator-overview.md @@ -24,7 +24,7 @@ The navigators render application screens which are just React components. To learn how to create screens, read about: - [Screen `navigation` prop](navigation-prop.md) to allow the screen to dispatch navigation actions, such as opening another screen -- Screen `navigationOptions` to customize how the screen gets presented by the navigator (e.g. [header title](stack-navigator.md#navigationoptions-used-by-stacknavigator), tab label) +- Screen `navigationOptions` to customize how the screen gets presented by the navigator (e.g. [header title](stack-navigator.md#navigationoptions-for-screens-inside-of-the-navigator), tab label) ### Calling Navigate on Top Level Component 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-2.x/drawer-actions.md b/versioned_docs/version-2.x/drawer-actions.md index 800725f688e..44f67c73056 100644 --- a/versioned_docs/version-2.x/drawer-actions.md +++ b/versioned_docs/version-2.x/drawer-actions.md @@ -8,9 +8,9 @@ sidebar_label: DrawerActions The following actions are supported: -- [openDrawer](#openDrawer) - open the drawer -- [closeDrawer](#closeDrawer) - close the drawer -- [toggleDrawer](#toggleDrawer) - toggle the state, ie. switch from closed to open and vice versa +- openDrawer - open the drawer +- closeDrawer - close the drawer +- toggleDrawer - toggle the state, ie. switch from closed to open and vice versa ### Usage diff --git a/versioned_docs/version-2.x/headers.md b/versioned_docs/version-2.x/headers.md index fb5b547f22d..e4c4d144f98 100644 --- a/versioned_docs/version-2.x/headers.md +++ b/versioned_docs/version-2.x/headers.md @@ -202,11 +202,11 @@ class HomeScreen extends React.Component { ## Additional configuration -You can read the full list of available `navigationOptions` for screens inside of a stack navigator in the [`createStackNavigator` reference](stack-navigator.md#navigationoptions-used-by-stacknavigator). +You can read the full list of available `navigationOptions` for screens inside of a stack navigator in the [`createStackNavigator` reference](stack-navigator.md#navigationoptions-for-screens-inside-of-the-navigator). ## Summary -- You can customize the header inside of the `navigationOptions` static property on your screen components. Read the full list of options [in the API reference](stack-navigator.md#navigationoptions-used-by-stacknavigator). +- You can customize the header inside of the `navigationOptions` static property on your screen components. Read the full list of options [in the API reference](stack-navigator.md#navigationoptions-for-screens-inside-of-the-navigator). - The `navigationOptions` static property can be an object or a function. When it is a function, it is provided with an object with the `navigation` prop, `screenProps`, and `navigationOptions` on it. - You can also specify shared `navigationOptions` in the stack navigator configuration when you initialize it. The static property takes precedence over that configuration. - [Full source of what we have built so far](https://snack.expo.io/@react-navigation/custom-header-title-component-v2). diff --git a/versioned_docs/version-2.x/navigation-actions.md b/versioned_docs/version-2.x/navigation-actions.md index 04fa334796c..a0ff2cc460d 100644 --- a/versioned_docs/version-2.x/navigation-actions.md +++ b/versioned_docs/version-2.x/navigation-actions.md @@ -13,7 +13,6 @@ The following actions are supported: - [Navigate](#navigate) - Navigate to another route - [Back](#back) - Go back to previous state - [Set Params](#setparams) - Set Params for given route -- [Init](#init) - Used to initialize first state if state is undefined For actions specific to a StackNavigator, see [StackActions](stack-actions.md). diff --git a/versioned_docs/version-2.x/navigation-events.md b/versioned_docs/version-2.x/navigation-events.md index 088c0c082bd..a258d361e33 100644 --- a/versioned_docs/version-2.x/navigation-events.md +++ b/versioned_docs/version-2.x/navigation-events.md @@ -14,7 +14,7 @@ sidebar_label: NavigationEvents - `onWillBlur` - event listener - `onDidBlur` - event listener -The event listener is the same as the imperative [`navigation.addListener(...)`](navigation-prop.md#addlistener-subscribe-to-updates-to-navigation-lifecycle) API. +The event listener is the same as the imperative [`navigation.addListener(...)`](navigation-prop.md#addlistener---subscribe-to-updates-to-navigation-lifecycle) API. ### Example diff --git a/versioned_docs/version-2.x/navigation-key.md b/versioned_docs/version-2.x/navigation-key.md index 8655464c291..941c6189082 100644 --- a/versioned_docs/version-2.x/navigation-key.md +++ b/versioned_docs/version-2.x/navigation-key.md @@ -17,14 +17,14 @@ If, however, you want to push several instances of the same route, you can do so > Note: the behavior of `navigate` without a `key` is significantly different in the 1.x series of releases. Read more about it [here](https://gist.github.com/vonovak/ef72f5efe1d36742de8968ff6a708985). -### Usage with [`reset`](navigation-actions.md#reset) +### Usage with [`reset`](stack-actions.md#reset) When resetting, `key` is also optional and can be a string or `null`. If set, the navigator with the given key will reset. If `null`, the root navigator will reset. You can obtain a route's navigator key by calling `this.props.navigation.dangerouslyGetParent().state.key`. Reason why the function is called `dangerouslyGetParent` is to warn developers against overusing it to eg. get parent of parent and other hard-to-follow patterns. -### Usage with [`replace`](navigation-actions.md#replace) +### Usage with [`replace`](stack-actions.md#replace) With the `replace` navigation action, `key` is a required parameter used for identifying the route to be replaced. If you use the helper function `this.props.navigation.replace`, we will automatically substitute the key of the current route. ### Usage with `goBack` -Please refer to the [`goBack docs`](navigation-prop.md#goback-close-the-active-screen-and-move-back) for a detailed explanation. +Please refer to the [`goBack docs`](navigation-prop.md#goback---close-the-active-screen-and-move-back) for a detailed explanation. diff --git a/versioned_docs/version-2.x/navigation-lifecycle.md b/versioned_docs/version-2.x/navigation-lifecycle.md index 7ece38a19b4..e6210d5f11e 100644 --- a/versioned_docs/version-2.x/navigation-lifecycle.md +++ b/versioned_docs/version-2.x/navigation-lifecycle.md @@ -41,7 +41,7 @@ We start on the `HomeScreen` and navigate to `DetailsScreen`. Then we use the ta Now that we understand how React lifecycle methods work in React Navigation, let's answer the question we asked at the beginning: "How do we find out that a user is leaving it or coming back to it?" -React Navigation emits events to screen components that subscribe to them. There are four different events that you can subscribe to: `willFocus`, `willBlur`, `didFocus` and `didBlur`. Read more about them in the [API reference](navigation-prop.md#addlistener-subscribe-to-updates-to-navigation-lifecycle). +React Navigation emits events to screen components that subscribe to them. There are four different events that you can subscribe to: `willFocus`, `willBlur`, `didFocus` and `didBlur`. Read more about them in the [API reference](navigation-prop.md#addlistener---subscribe-to-updates-to-navigation-lifecycle). Many of your use cases may be covered with the [`withNavigationFocus` HOC](with-navigation-focus.md) or the [`` component](navigation-events.md) which are a little more straightforward to use. diff --git a/versioned_docs/version-2.x/navigation-options-resolution.md b/versioned_docs/version-2.x/navigation-options-resolution.md index 80c91173736..746b673e557 100644 --- a/versioned_docs/version-2.x/navigation-options-resolution.md +++ b/versioned_docs/version-2.x/navigation-options-resolution.md @@ -108,7 +108,7 @@ HomeStack.navigationOptions = { We understand that overloading the naming here is a little bit confusing. Please [open a RFC](https://github.com/react-navigation/rfcs) if you have a suggestion about how we can make this API easier to learn and work with. -# A stack contains a tab navigator and you want to set the title on the stack header +## A stack contains a tab navigator and you want to set the title on the stack header Imagine the following configuration: @@ -177,7 +177,7 @@ Using this configuration, the `headerTitle` or `title` from `navigationOptions` Additionally, you can push new screens to the feed and profile stacks without hiding the tab bar by adding more routes to those stacks. If you want to push screens on top of the tab bar, then you can add them to the `AppNavigator` stack. -# A tab navigator contains a stack and you want to hide the tab bar on specific screens +## A tab navigator contains a stack and you want to hide the tab bar on specific screens Similar to the example above where a stack contains a tab navigator, we can solve this in two ways: add `navigationOptions` to our tab navigator to set the tab bar to hidden depending on which route is active in the child stack, or we can move the tab navigator inside of the stack. @@ -247,7 +247,7 @@ const AppNavigator = createSwitchNavigator({ }); ``` -# A drawer has a stack inside of it and you want to lock the drawer on certain screens +## A drawer has a stack inside of it and you want to lock the drawer on certain screens This is conceptually identical to having a tab with a stack inside of it (read that above if you have not already), where you want to hide the tab bar on certain screens. The only difference is that rather than using `tabBarVisible` you will use `drawerLockMode`. diff --git a/versioned_docs/version-3.x/MST-integration.md b/versioned_docs/version-3.x/MST-integration.md index 2ce8a8755f6..93e55fac08e 100644 --- a/versioned_docs/version-3.x/MST-integration.md +++ b/versioned_docs/version-3.x/MST-integration.md @@ -61,7 +61,7 @@ export const NavigationStore = types })); ``` -Note that `userProfileScreenParams` is a simple model with a `user` entry, while `productDetailScreenParams` is a map of `ProductDetailScreenParams` model. The reason we chose this shape of data is that we only have a single user profile screen in our app which reads its params from `userProfileScreenParams`. `productDetailScreenParams` is a map because the app can have several product screens on a stack. Each screen points to a `Product` instance saved in the map. The keys into the map are the React Navigation [keys](navigation-key.md#usage-with-the-navigate-navigation-actionshtml-navigate-call): think of the `key` as of an identifier of the route. +Note that `userProfileScreenParams` is a simple model with a `user` entry, while `productDetailScreenParams` is a map of `ProductDetailScreenParams` model. The reason we chose this shape of data is that we only have a single user profile screen in our app which reads its params from `userProfileScreenParams`. `productDetailScreenParams` is a map because the app can have several product screens on a stack. Each screen points to a `Product` instance saved in the map. The keys into the map are the React Navigation [keys](navigation-key.md#usage-with-the-navigate-call): think of the `key` as of an identifier of the route. Your navigation store may also be just one map where for each screen (regardless if it is a product or user profile screen), we store its navigation params. This is the approach taken in the [sample app](https://github.com/vonovak/react-navigation-mst-demo). diff --git a/versioned_docs/version-3.x/custom-android-back-button-handling.md b/versioned_docs/version-3.x/custom-android-back-button-handling.md index 10b48240c14..0b5f3beb7de 100644 --- a/versioned_docs/version-3.x/custom-android-back-button-handling.md +++ b/versioned_docs/version-3.x/custom-android-back-button-handling.md @@ -8,7 +8,7 @@ By default, when user presses the Android hardware back button, react-navigation > If you're looking for an easy-to-use solution, you can check out a community-developed package [react-navigation-backhandler](https://github.com/vonovak/react-navigation-backhandler). The following text shows what the package does under the cover. -As an example, consider a screen where user is selecting items in a list, and a "selection mode" is active. On a back button press, you would first want the "selection mode" to be deactivated, and the screen should be popped only on the second back button press. The following code snippet demonstrates the situation. We make use of [`BackHandler`](https://reactnative.dev/docs/backhandler.html) which comes with react-native and we [subscribe to navigation lifecycle updates](navigation-prop.md#addlistener-subscribe-to-updates-to-navigation-lifecycle) to add our custom `hardwareBackPress` listener. +As an example, consider a screen where user is selecting items in a list, and a "selection mode" is active. On a back button press, you would first want the "selection mode" to be deactivated, and the screen should be popped only on the second back button press. The following code snippet demonstrates the situation. We make use of [`BackHandler`](https://reactnative.dev/docs/backhandler.html) which comes with react-native and we [subscribe to navigation lifecycle updates](navigation-prop.md#addlistener---subscribe-to-updates-to-navigation-lifecycle) to add our custom `hardwareBackPress` listener. Returning `true` from `onBackButtonPressAndroid` denotes that we have handled the event, and react-navigation's listener will not get called, thus not popping the screen. Returning `false` will cause the event to bubble up and react-navigation's listener will pop the screen. diff --git a/versioned_docs/version-3.x/custom-navigator-overview.md b/versioned_docs/version-3.x/custom-navigator-overview.md index 97826743dd7..39699c3bda9 100644 --- a/versioned_docs/version-3.x/custom-navigator-overview.md +++ b/versioned_docs/version-3.x/custom-navigator-overview.md @@ -24,4 +24,4 @@ The navigators render application screens which are just React components. To learn how to create screens, read about: - [Screen `navigation` prop](navigation-prop.md) to allow the screen to dispatch navigation actions, such as opening another screen -- Screen `navigationOptions` to customize how the screen gets presented by the navigator (e.g. [header title](stack-navigator.md#navigationoptions-used-by-stacknavigator), tab label) +- Screen `navigationOptions` to customize how the screen gets presented by the navigator (e.g. [header title](stack-navigator.md#navigationoptions-for-screens-inside-of-the-navigator), tab label) 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-3.x/drawer-actions.md b/versioned_docs/version-3.x/drawer-actions.md index 800725f688e..44f67c73056 100644 --- a/versioned_docs/version-3.x/drawer-actions.md +++ b/versioned_docs/version-3.x/drawer-actions.md @@ -8,9 +8,9 @@ sidebar_label: DrawerActions The following actions are supported: -- [openDrawer](#openDrawer) - open the drawer -- [closeDrawer](#closeDrawer) - close the drawer -- [toggleDrawer](#toggleDrawer) - toggle the state, ie. switch from closed to open and vice versa +- openDrawer - open the drawer +- closeDrawer - close the drawer +- toggleDrawer - toggle the state, ie. switch from closed to open and vice versa ### Usage diff --git a/versioned_docs/version-3.x/headers.md b/versioned_docs/version-3.x/headers.md index 8ee2f7cfce1..cc00a8848a0 100644 --- a/versioned_docs/version-3.x/headers.md +++ b/versioned_docs/version-3.x/headers.md @@ -230,11 +230,11 @@ class HomeScreen extends React.Component { ## Additional configuration -You can read the full list of available `navigationOptions` for screens inside of a stack navigator in the [`createStackNavigator` reference](stack-navigator.md#navigationoptions-used-by-stacknavigator). +You can read the full list of available `navigationOptions` for screens inside of a stack navigator in the [`createStackNavigator` reference](stack-navigator.md#navigationoptions-for-screens-inside-of-the-navigator). ## Summary -- You can customize the header inside of the `navigationOptions` static property on your screen components. Read the full list of options [in the API reference](stack-navigator.md#navigationoptions-used-by-stacknavigator). +- You can customize the header inside of the `navigationOptions` static property on your screen components. Read the full list of options [in the API reference](stack-navigator.md#navigationoptions-for-screens-inside-of-the-navigator). - The `navigationOptions` static property can be an object or a function. When it is a function, it is provided with an object with the `navigation` prop, `screenProps`, and `navigationOptions` on it. - You can also specify shared `navigationOptions` in the stack navigator configuration when you initialize it. The static property takes precedence over that configuration. - [Full source of what we have built so far](https://snack.expo.io/@react-navigation/custom-header-title-component-v3). diff --git a/versioned_docs/version-3.x/navigation-actions.md b/versioned_docs/version-3.x/navigation-actions.md index 04fa334796c..a0ff2cc460d 100644 --- a/versioned_docs/version-3.x/navigation-actions.md +++ b/versioned_docs/version-3.x/navigation-actions.md @@ -13,7 +13,6 @@ The following actions are supported: - [Navigate](#navigate) - Navigate to another route - [Back](#back) - Go back to previous state - [Set Params](#setparams) - Set Params for given route -- [Init](#init) - Used to initialize first state if state is undefined For actions specific to a StackNavigator, see [StackActions](stack-actions.md). diff --git a/versioned_docs/version-3.x/navigation-events.md b/versioned_docs/version-3.x/navigation-events.md index 088c0c082bd..a258d361e33 100644 --- a/versioned_docs/version-3.x/navigation-events.md +++ b/versioned_docs/version-3.x/navigation-events.md @@ -14,7 +14,7 @@ sidebar_label: NavigationEvents - `onWillBlur` - event listener - `onDidBlur` - event listener -The event listener is the same as the imperative [`navigation.addListener(...)`](navigation-prop.md#addlistener-subscribe-to-updates-to-navigation-lifecycle) API. +The event listener is the same as the imperative [`navigation.addListener(...)`](navigation-prop.md#addlistener---subscribe-to-updates-to-navigation-lifecycle) API. ### Example diff --git a/versioned_docs/version-3.x/navigation-key.md b/versioned_docs/version-3.x/navigation-key.md index 8655464c291..941c6189082 100644 --- a/versioned_docs/version-3.x/navigation-key.md +++ b/versioned_docs/version-3.x/navigation-key.md @@ -17,14 +17,14 @@ If, however, you want to push several instances of the same route, you can do so > Note: the behavior of `navigate` without a `key` is significantly different in the 1.x series of releases. Read more about it [here](https://gist.github.com/vonovak/ef72f5efe1d36742de8968ff6a708985). -### Usage with [`reset`](navigation-actions.md#reset) +### Usage with [`reset`](stack-actions.md#reset) When resetting, `key` is also optional and can be a string or `null`. If set, the navigator with the given key will reset. If `null`, the root navigator will reset. You can obtain a route's navigator key by calling `this.props.navigation.dangerouslyGetParent().state.key`. Reason why the function is called `dangerouslyGetParent` is to warn developers against overusing it to eg. get parent of parent and other hard-to-follow patterns. -### Usage with [`replace`](navigation-actions.md#replace) +### Usage with [`replace`](stack-actions.md#replace) With the `replace` navigation action, `key` is a required parameter used for identifying the route to be replaced. If you use the helper function `this.props.navigation.replace`, we will automatically substitute the key of the current route. ### Usage with `goBack` -Please refer to the [`goBack docs`](navigation-prop.md#goback-close-the-active-screen-and-move-back) for a detailed explanation. +Please refer to the [`goBack docs`](navigation-prop.md#goback---close-the-active-screen-and-move-back) for a detailed explanation. diff --git a/versioned_docs/version-3.x/navigation-lifecycle.md b/versioned_docs/version-3.x/navigation-lifecycle.md index 7ece38a19b4..e6210d5f11e 100644 --- a/versioned_docs/version-3.x/navigation-lifecycle.md +++ b/versioned_docs/version-3.x/navigation-lifecycle.md @@ -41,7 +41,7 @@ We start on the `HomeScreen` and navigate to `DetailsScreen`. Then we use the ta Now that we understand how React lifecycle methods work in React Navigation, let's answer the question we asked at the beginning: "How do we find out that a user is leaving it or coming back to it?" -React Navigation emits events to screen components that subscribe to them. There are four different events that you can subscribe to: `willFocus`, `willBlur`, `didFocus` and `didBlur`. Read more about them in the [API reference](navigation-prop.md#addlistener-subscribe-to-updates-to-navigation-lifecycle). +React Navigation emits events to screen components that subscribe to them. There are four different events that you can subscribe to: `willFocus`, `willBlur`, `didFocus` and `didBlur`. Read more about them in the [API reference](navigation-prop.md#addlistener---subscribe-to-updates-to-navigation-lifecycle). Many of your use cases may be covered with the [`withNavigationFocus` HOC](with-navigation-focus.md) or the [`` component](navigation-events.md) which are a little more straightforward to use. diff --git a/versioned_docs/version-3.x/navigation-options-resolution.md b/versioned_docs/version-3.x/navigation-options-resolution.md index c0461b7245d..3ab0a7263e9 100644 --- a/versioned_docs/version-3.x/navigation-options-resolution.md +++ b/versioned_docs/version-3.x/navigation-options-resolution.md @@ -126,7 +126,7 @@ const HomeStack = createStackNavigator( → Run this code -# getActiveChildNavigationOptions +## getActiveChildNavigationOptions If you would like to get the `navigationOptions` from the active child of a navigator, you can do that with `getActiveChildNavigationOptions`. This makes it possible for you to set the `tabBarLabel` directly on a screen inside of a stack that is inside of a tab, for example. @@ -157,11 +157,11 @@ const HomeStack = createStackNavigator( → Run this code -# **Note**: the navigationOptions property vs navigator configuration +## **Note**: the navigationOptions property vs navigator configuration Navigators are initialized with `create*Navigator(routeConfig, navigatorConfig)`. Inside of `navigatorConfig` we can add a `defaultNavigationOptions` property. These `defaultNavigationOptions` are the default options for screens within that navigator ([read more about sharing common navigationOptions](headers.md#sharing-common-navigationoptions-across-screens)), they do not refer to the `navigationOptions` for that navigator — as we have seen above, we set the `navigationOptions` property directly on the navigator for that use case. -# A stack contains a tab navigator and you want to set the title on the stack header +## A stack contains a tab navigator and you want to set the title on the stack header Imagine the following configuration: @@ -230,7 +230,7 @@ Using this configuration, the `headerTitle` or `title` from `navigationOptions` Additionally, you can push new screens to the feed and profile stacks without hiding the tab bar by adding more routes to those stacks. If you want to push screens on top of the tab bar, then you can add them to the `AppNavigator` stack. -# A tab navigator contains a stack and you want to hide the tab bar on specific screens +## A tab navigator contains a stack and you want to hide the tab bar on specific screens Similar to the example above where a stack contains a tab navigator, we can solve this in two ways: add `navigationOptions` to our tab navigator to set the tab bar to hidden depending on which route is active in the child stack, or we can move the tab navigator inside of the stack. @@ -300,7 +300,7 @@ const AppNavigator = createSwitchNavigator({ }); ``` -# A drawer has a stack inside of it and you want to lock the drawer on certain screens +## A drawer has a stack inside of it and you want to lock the drawer on certain screens This is conceptually identical to having a tab with a stack inside of it (read that above if you have not already), where you want to hide the tab bar on certain screens. The only difference is that rather than using `tabBarVisible` you will use `drawerLockMode`. diff --git a/versioned_docs/version-4.x/MST-integration.md b/versioned_docs/version-4.x/MST-integration.md index 2ce8a8755f6..93e55fac08e 100644 --- a/versioned_docs/version-4.x/MST-integration.md +++ b/versioned_docs/version-4.x/MST-integration.md @@ -61,7 +61,7 @@ export const NavigationStore = types })); ``` -Note that `userProfileScreenParams` is a simple model with a `user` entry, while `productDetailScreenParams` is a map of `ProductDetailScreenParams` model. The reason we chose this shape of data is that we only have a single user profile screen in our app which reads its params from `userProfileScreenParams`. `productDetailScreenParams` is a map because the app can have several product screens on a stack. Each screen points to a `Product` instance saved in the map. The keys into the map are the React Navigation [keys](navigation-key.md#usage-with-the-navigate-navigation-actionshtml-navigate-call): think of the `key` as of an identifier of the route. +Note that `userProfileScreenParams` is a simple model with a `user` entry, while `productDetailScreenParams` is a map of `ProductDetailScreenParams` model. The reason we chose this shape of data is that we only have a single user profile screen in our app which reads its params from `userProfileScreenParams`. `productDetailScreenParams` is a map because the app can have several product screens on a stack. Each screen points to a `Product` instance saved in the map. The keys into the map are the React Navigation [keys](navigation-key.md#usage-with-the-navigate-call): think of the `key` as of an identifier of the route. Your navigation store may also be just one map where for each screen (regardless if it is a product or user profile screen), we store its navigation params. This is the approach taken in the [sample app](https://github.com/vonovak/react-navigation-mst-demo). 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/custom-navigator-overview.md b/versioned_docs/version-4.x/custom-navigator-overview.md index 97826743dd7..39699c3bda9 100644 --- a/versioned_docs/version-4.x/custom-navigator-overview.md +++ b/versioned_docs/version-4.x/custom-navigator-overview.md @@ -24,4 +24,4 @@ The navigators render application screens which are just React components. To learn how to create screens, read about: - [Screen `navigation` prop](navigation-prop.md) to allow the screen to dispatch navigation actions, such as opening another screen -- Screen `navigationOptions` to customize how the screen gets presented by the navigator (e.g. [header title](stack-navigator.md#navigationoptions-used-by-stacknavigator), tab label) +- Screen `navigationOptions` to customize how the screen gets presented by the navigator (e.g. [header title](stack-navigator.md#navigationoptions-for-screens-inside-of-the-navigator), tab label) 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/drawer-actions.md b/versioned_docs/version-4.x/drawer-actions.md index 91dd2574ae8..c09626ede59 100644 --- a/versioned_docs/version-4.x/drawer-actions.md +++ b/versioned_docs/version-4.x/drawer-actions.md @@ -8,9 +8,9 @@ sidebar_label: DrawerActions The following actions are supported: -- [openDrawer](#openDrawer) - open the drawer -- [closeDrawer](#closeDrawer) - close the drawer -- [toggleDrawer](#toggleDrawer) - toggle the state, ie. switches from closed to open and vice versa +- openDrawer - open the drawer +- closeDrawer - close the drawer +- toggleDrawer - toggle the state, ie. switches from closed to open and vice versa ### Usage diff --git a/versioned_docs/version-4.x/header-buttons.md b/versioned_docs/version-4.x/header-buttons.md index 3d218a8ceae..47466e9994d 100644 --- a/versioned_docs/version-4.x/header-buttons.md +++ b/versioned_docs/version-4.x/header-buttons.md @@ -90,4 +90,3 @@ Generally, this is what you want. But it's possible that in some circumstances t - You can set buttons in the header through the `headerLeft` and `headerRight` properties in `navigationOptions`. - The back button is fully customizable with `headerLeft`, but if you just want to change the title or image, there are other `navigationOptions` for that — `headerBackTitle`, `headerTruncatedBackTitle`, and `headerBackImage`. -- [Full source of what we have built so far](#example/header-interaction). diff --git a/versioned_docs/version-4.x/headers.md b/versioned_docs/version-4.x/headers.md index a064dfbe26b..8b93bc3ff2f 100644 --- a/versioned_docs/version-4.x/headers.md +++ b/versioned_docs/version-4.x/headers.md @@ -230,11 +230,10 @@ class HomeScreen extends React.Component { ## Additional configuration -You can read the full list of available `navigationOptions` for screens inside of a stack navigator in the [`createStackNavigator` reference](stack-navigator.md#navigationoptions-used-by-stacknavigator). +You can read the full list of available `navigationOptions` for screens inside of a stack navigator in the [`createStackNavigator` reference](stack-navigator.md#navigationoptions-for-screens-inside-of-the-navigator). ## Summary -- You can customize the header inside of the `navigationOptions` static property on your screen components. Read the full list of options [in the API reference](stack-navigator.md#navigationoptions-used-by-stacknavigator). +- You can customize the header inside of the `navigationOptions` static property on your screen components. Read the full list of options [in the API reference](stack-navigator.md#navigationoptions-for-screens-inside-of-the-navigator). - The `navigationOptions` static property can be an object or a function. When it is a function, it is provided with an object with the `navigation` prop, `screenProps`, and `navigationOptions` on it. - You can also specify shared `navigationOptions` in the stack navigator configuration when you initialize it. The static property takes precedence over that configuration. -- [Full source of what we have built so far](#example/custom-header-title-component). diff --git a/versioned_docs/version-4.x/hello-react-navigation.md b/versioned_docs/version-4.x/hello-react-navigation.md index f52560019ad..e45acbff1b8 100644 --- a/versioned_docs/version-4.x/hello-react-navigation.md +++ b/versioned_docs/version-4.x/hello-react-navigation.md @@ -115,4 +115,3 @@ Now our stack has two _routes_, a `Home` route and a `Details` route. The `Home` - `createStackNavigator` is a function that takes a route configuration object and an options object and returns a React component. - The keys in the route configuration object are the route names and the values are the configuration for that route. The only required property on the configuration is the `screen` (the component to use for the route). - To specify what the initial route in a stack is, provide an `initialRouteName` on the stack options object. -- [Full source of what we have built so far](#example/hello-react-navigation-full). diff --git a/versioned_docs/version-4.x/modal.md b/versioned_docs/version-4.x/modal.md index e4119780951..fe31980e201 100644 --- a/versioned_docs/version-4.x/modal.md +++ b/versioned_docs/version-4.x/modal.md @@ -91,4 +91,3 @@ There are some important things to notice here: - To change the type of transition on a stack navigator you can use the `mode` configuration. When set to `modal`, all screens animate-in from bottom to top rather than right to left. This applies to that entire stack navigator, so to use right to left transitions on other screens, we add another navigation stack with the default configuration. - `this.props.navigation.navigate` traverses up the navigator tree to find a navigator that can handle the `navigate` action. -- [Full source of what we have built so far](#example/full-screen-modal) diff --git a/versioned_docs/version-4.x/navigating.md b/versioned_docs/version-4.x/navigating.md index f12c9ddf8e6..da79fd58e5e 100644 --- a/versioned_docs/version-4.x/navigating.md +++ b/versioned_docs/version-4.x/navigating.md @@ -134,4 +134,3 @@ Another common requirement is to be able to go back _multiple_ screens -- for ex - The header bar will automatically show a back button, but you can programmatically go back by calling `this.props.navigation.goBack()`. On Android, the hardware back button just works as expected. - You can go back to an existing screen in the stack with `this.props.navigation.navigate('RouteName')`, and you can go back to the first screen in the stack with `this.props.navigation.popToTop()`. - The `navigation` prop is available to all screen components (components defined as screens in route configuration and rendered by React Navigation as a route). -- [Full source of what we have built so far](#example/go-back). diff --git a/versioned_docs/version-4.x/navigation-actions.md b/versioned_docs/version-4.x/navigation-actions.md index cf196c9c287..f8268ae5ba2 100644 --- a/versioned_docs/version-4.x/navigation-actions.md +++ b/versioned_docs/version-4.x/navigation-actions.md @@ -15,7 +15,6 @@ The following actions are supported: - [Navigate](#navigate) - Navigate to another route - [Back](#back) - Go back to previous state - [Set Params](#setparams) - Set Params for given route -- [Init](#init) - Used to initialize first state if state is undefined For actions specific to a StackNavigator, see [StackActions](stack-actions.md). For actions specific to a switch-based navigators such as TabNavigator, see [SwitchActions](switch-actions.md). diff --git a/versioned_docs/version-4.x/navigation-events.md b/versioned_docs/version-4.x/navigation-events.md index 088c0c082bd..a258d361e33 100644 --- a/versioned_docs/version-4.x/navigation-events.md +++ b/versioned_docs/version-4.x/navigation-events.md @@ -14,7 +14,7 @@ sidebar_label: NavigationEvents - `onWillBlur` - event listener - `onDidBlur` - event listener -The event listener is the same as the imperative [`navigation.addListener(...)`](navigation-prop.md#addlistener-subscribe-to-updates-to-navigation-lifecycle) API. +The event listener is the same as the imperative [`navigation.addListener(...)`](navigation-prop.md#addlistener---subscribe-to-updates-to-navigation-lifecycle) API. ### Example diff --git a/versioned_docs/version-4.x/navigation-key.md b/versioned_docs/version-4.x/navigation-key.md index 8655464c291..941c6189082 100644 --- a/versioned_docs/version-4.x/navigation-key.md +++ b/versioned_docs/version-4.x/navigation-key.md @@ -17,14 +17,14 @@ If, however, you want to push several instances of the same route, you can do so > Note: the behavior of `navigate` without a `key` is significantly different in the 1.x series of releases. Read more about it [here](https://gist.github.com/vonovak/ef72f5efe1d36742de8968ff6a708985). -### Usage with [`reset`](navigation-actions.md#reset) +### Usage with [`reset`](stack-actions.md#reset) When resetting, `key` is also optional and can be a string or `null`. If set, the navigator with the given key will reset. If `null`, the root navigator will reset. You can obtain a route's navigator key by calling `this.props.navigation.dangerouslyGetParent().state.key`. Reason why the function is called `dangerouslyGetParent` is to warn developers against overusing it to eg. get parent of parent and other hard-to-follow patterns. -### Usage with [`replace`](navigation-actions.md#replace) +### Usage with [`replace`](stack-actions.md#replace) With the `replace` navigation action, `key` is a required parameter used for identifying the route to be replaced. If you use the helper function `this.props.navigation.replace`, we will automatically substitute the key of the current route. ### Usage with `goBack` -Please refer to the [`goBack docs`](navigation-prop.md#goback-close-the-active-screen-and-move-back) for a detailed explanation. +Please refer to the [`goBack docs`](navigation-prop.md#goback---close-the-active-screen-and-move-back) for a detailed explanation. diff --git a/versioned_docs/version-4.x/navigation-lifecycle.md b/versioned_docs/version-4.x/navigation-lifecycle.md index dc72def1d0e..f40ec969cad 100644 --- a/versioned_docs/version-4.x/navigation-lifecycle.md +++ b/versioned_docs/version-4.x/navigation-lifecycle.md @@ -20,7 +20,7 @@ When going back from `B` to `A`, `componentWillUnmount` of `B` is called, but `c Now that we understand how React lifecycle methods work in React Navigation, let's answer the question we asked at the beginning: "How do we find out that a user is leaving it or coming back to it?" -React Navigation emits events to screen components that subscribe to them. There are four different events that you can subscribe to: `willFocus`, `willBlur`, `didFocus` and `didBlur`. Read more about them in the [API reference](navigation-prop.md#addlistener-subscribe-to-updates-to-navigation-lifecycle). +React Navigation emits events to screen components that subscribe to them. There are four different events that you can subscribe to: `willFocus`, `willBlur`, `didFocus` and `didBlur`. Read more about them in the [API reference](navigation-prop.md#addlistener---subscribe-to-updates-to-navigation-lifecycle). Many of your use cases may be covered with the [`withNavigationFocus` higher-order-component](with-navigation-focus.md), the [`` component](navigation-events.md), or the [useFocusState hook](https://github.com/react-navigation/hooks#usefocusstate). diff --git a/versioned_docs/version-4.x/navigation-options-resolution.md b/versioned_docs/version-4.x/navigation-options-resolution.md index 0ad9065f545..c29573edfa4 100644 --- a/versioned_docs/version-4.x/navigation-options-resolution.md +++ b/versioned_docs/version-4.x/navigation-options-resolution.md @@ -126,7 +126,7 @@ const HomeStack = createStackNavigator( → Run this code -# getActiveChildNavigationOptions +## getActiveChildNavigationOptions If you would like to get the `navigationOptions` from the active child of a navigator, you can do that with `getActiveChildNavigationOptions`. This makes it possible for you to set the `tabBarLabel` directly on a screen inside of a stack that is inside of a tab, for example. @@ -157,11 +157,11 @@ const HomeStack = createStackNavigator( → Run this code -# **Note**: the navigationOptions property vs navigator configuration +## **Note**: the navigationOptions property vs navigator configuration Navigators are initialized with `create*Navigator(routeConfig, navigatorConfig)`. Inside of `navigatorConfig` we can add a `defaultNavigationOptions` property. These `defaultNavigationOptions` are the default options for screens within that navigator ([read more about sharing common navigationOptions](headers.md#sharing-common-navigationoptions-across-screens)), they do not refer to the `navigationOptions` for that navigator — as we have seen above, we set the `navigationOptions` property directly on the navigator for that use case. -# A stack contains a tab navigator and you want to set the title on the stack header +## A stack contains a tab navigator and you want to set the title on the stack header Imagine the following configuration: @@ -230,7 +230,7 @@ Using this configuration, the `headerTitle` or `title` from `navigationOptions` Additionally, you can push new screens to the feed and profile stacks without hiding the tab bar by adding more routes to those stacks. If you want to push screens on top of the tab bar, then you can add them to the `AppNavigator` stack. -# A tab navigator contains a stack and you want to hide the tab bar on specific screens +## A tab navigator contains a stack and you want to hide the tab bar on specific screens Similar to the example above where a stack contains a tab navigator, we can solve this in two ways: add `navigationOptions` to our tab navigator to set the tab bar to hidden depending on which route is active in the child stack, or we can move the tab navigator inside of the stack. @@ -300,7 +300,7 @@ const AppNavigator = createSwitchNavigator({ }); ``` -# A drawer has a stack inside of it and you want to lock the drawer on certain screens +## A drawer has a stack inside of it and you want to lock the drawer on certain screens This is conceptually identical to having a tab with a stack inside of it (read that above if you have not already), where you want to hide the tab bar on certain screens. The only difference is that rather than using `tabBarVisible` you will use `drawerLockMode`. diff --git a/versioned_docs/version-4.x/params.md b/versioned_docs/version-4.x/params.md index f3d239d3a50..d959b7fbe47 100644 --- a/versioned_docs/version-4.x/params.md +++ b/versioned_docs/version-4.x/params.md @@ -74,4 +74,3 @@ class DetailsScreen extends React.Component { - `navigate` and `push` accept an optional second argument to let you pass parameters to the route you are navigating to. For example: `this.props.navigation.navigate('RouteName', {paramName: 'value'})`. - You can read the params through `this.props.navigation.getParam` - As an alternative to `getParam`, you may use `this.props.navigation.state.params`. It is `null` if no parameters are specified. -- [Full source of what we have built so far](#example/passing-params). 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/headers.md b/versioned_docs/version-5.x/headers.md index 027e479bfd6..bd4501e6dea 100755 --- a/versioned_docs/version-5.x/headers.md +++ b/versioned_docs/version-5.x/headers.md @@ -177,10 +177,10 @@ function StackScreen() { ## Additional configuration -You can read the full list of available `options` for screens inside of a stack navigator in the [`createStackNavigator` reference](stack-navigator.md#navigationoptions-used-by-stacknavigator). +You can read the full list of available `options` for screens inside of a stack navigator in the [`createStackNavigator` reference](stack-navigator.md#options). ## Summary -- You can customize the header inside of the `options` prop of your screen components. Read the full list of options [in the API reference](stack-navigator.md#navigationoptions-used-by-stacknavigator). +- You can customize the header inside of the `options` prop of your screen components. Read the full list of options [in the API reference](stack-navigator.md#options). - The `options` prop can be an object or a function. When it is a function, it is provided with an object with the `navigation` and `route` prop. - You can also specify shared `screenOptions` in the stack navigator configuration when you initialize it. The prop takes precedence over that configuration. 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 If you want React Navigation to fix invalid state, you need to make sure that you don't have `stale: false` in the state object. State objects with `stale: false` are assumed to be valid state objects and React Navigation won't attempt to fix them. -When you're providing a state object in [`initialState`](navigation-container.md#initial-state), 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, which makes sure that things like state persistence work smoothly without extra manipulation of the state object. diff --git a/versioned_docs/version-5.x/stack-navigator.md b/versioned_docs/version-5.x/stack-navigator.md index 73071371465..42c84ef7c6f 100755 --- a/versioned_docs/version-5.x/stack-navigator.md +++ b/versioned_docs/version-5.x/stack-navigator.md @@ -70,8 +70,8 @@ Defines the style for rendering and transitions: 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 shown. It's recommended to use [`headerShown`](#headershown) option instead for more granularity. #### `detachInactiveScreens` diff --git a/versioned_docs/version-5.x/use-linking.md b/versioned_docs/version-5.x/use-linking.md index b7cd5daaa57..c566e800982 100755 --- a/versioned_docs/version-5.x/use-linking.md +++ b/versioned_docs/version-5.x/use-linking.md @@ -20,7 +20,7 @@ export default function App() { const ref = React.useRef(); const { getInitialState } = useLinking(ref, { - prefixes: ['https://mychat.com', 'mychat://'], + prefixes: ['https://example.com', 'example://'], config: { screens: { Chat: 'feed/:sort', @@ -69,7 +69,7 @@ Example: ```js useLinking(ref, { - prefixes: ['https://mychat.com', 'mychat://'], + prefixes: ['https://example.com', 'example://'], config: { screens: { Chat: 'feed/:sort', @@ -191,7 +191,7 @@ Example: ```js useLinking(ref, { - prefixes: ['https://mychat.com', 'mychat://'], + prefixes: ['https://example.com', 'example://'], config: { screens: { Chat: 'feed/:sort', @@ -212,7 +212,7 @@ Example: ```js useLinking(ref, { - prefixes: ['https://mychat.com', 'mychat://'], + prefixes: ['https://example.com', 'example://'], config: { screens: { Chat: 'feed/:sort', diff --git a/versioned_docs/version-6.x/configuring-links.md b/versioned_docs/version-6.x/configuring-links.md index a0e24f2f50b..cd9ebd50e60 100644 --- a/versioned_docs/version-6.x/configuring-links.md +++ b/versioned_docs/version-6.x/configuring-links.md @@ -47,13 +47,13 @@ You can also pass a [`fallback`](navigation-container.md#fallback) prop to `Navi ## 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'], }; ``` @@ -61,11 +61,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'], }; ``` @@ -77,7 +77,7 @@ To achieve this, you can use the `filter` option: ```js const linking = { - prefixes: ['mychat://', 'https://mychat.com'], + prefixes: ['example://', 'https://example.com'], filter: (url) => !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 486a7e62f06..4b286b0ac7c 100755 --- a/versioned_docs/version-6.x/native-stack-navigator.md +++ b/versioned_docs/version-6.x/native-stack-navigator.md @@ -259,7 +259,17 @@ React.useLayoutEffect(() => { }, [navigation]); ``` -Supported properties are described below. +Supported properties are: + +##### `ref` + +Ref to manipulate the search input imperatively. It contains the following methods: + +- `focus` - focuses the search bar +- `blur` - removes focus from the search bar +- `setText` - sets the search bar's content to given value +- `clearText` - removes any text present in the search bar input field +- `cancelSearch` - cancel the search and close the search bar ##### `autoCapitalize` @@ -447,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 - + ); } @@ -193,6 +193,12 @@ navigation.navigate('Root', { In the above case, you're navigating to the `Media` screen, which is in a navigator nested inside the `Sound` screen, which is in a navigator nested inside the `Settings` screen. +:::warning + +The `screen` and related params are reserved for internal use and are managed by React Navigation. While you can access `route.params.screen` etc. in the parent screens, relying on them may lead to unexpected behavior. + +::: + ### Rendering initial route defined in the navigator By default, when you navigate a screen in the nested navigator, the specified screen is used as the initial screen and the initial route prop on the navigator is ignored. This behaviour is different from the React Navigation 4. diff --git a/versioned_docs/version-6.x/stack-navigator.md b/versioned_docs/version-6.x/stack-navigator.md index 8752db1f6ec..d35a5b73b5b 100755 --- a/versioned_docs/version-6.x/stack-navigator.md +++ b/versioned_docs/version-6.x/stack-navigator.md @@ -335,8 +335,8 @@ return ( 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 default on iOS. -- `screen` - Each screen has a header attached to it and the header fades in and out together with the screen. This is default on other platforms. +- `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. #### `headerShown` diff --git a/versioned_docs/version-6.x/state-persistence.md b/versioned_docs/version-6.x/state-persistence.md index d697850d2f4..3bde677dc8a 100755 --- a/versioned_docs/version-6.x/state-persistence.md +++ b/versioned_docs/version-6.x/state-persistence.md @@ -100,6 +100,6 @@ if (!isReady) { ## Warning: Serializable State -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. +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. diff --git a/versioned_docs/version-6.x/troubleshooting.md b/versioned_docs/version-6.x/troubleshooting.md index 51436d92854..5a00ee9ab80 100755 --- a/versioned_docs/version-6.x/troubleshooting.md +++ b/versioned_docs/version-6.x/troubleshooting.md @@ -237,7 +237,7 @@ LogBox.ignoreLogs([ ## I'm getting "Invalid hook call. Hooks can only be called inside of the body of a function component" -This can happen when you pass a React component to an option that accepts a function returning a react element. For example, the [`headerTitle` option in native stack navigator](native-stack-navigator.md#headerTitle) expects a function returning a react element: +This can happen when you pass a React component to an option that accepts a function returning a react element. For example, the [`headerTitle` option in native stack navigator](native-stack-navigator.md#headertitle) expects a function returning a react element: ```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-6.x/upgrading-from-5.x.md b/versioned_docs/version-6.x/upgrading-from-5.x.md index e49d644c845..47c5099f8d6 100755 --- a/versioned_docs/version-6.x/upgrading-from-5.x.md +++ b/versioned_docs/version-6.x/upgrading-from-5.x.md @@ -395,11 +395,11 @@ The `headerMode` option supports 2 values: `screen` and `float`. ### Header is now taller in modals on iOS -The header is now 56dp tall in modals to match the native iOS style. If you are using the [`useHeaderHeight`](elements.md#use-header-height) hook to get the height of the header, then you won't need to change any code. +The header is now 56dp tall in modals to match the native iOS style. If you are using the [`useHeaderHeight`](elements.md#useheaderheight) hook to get the height of the header, then you won't need to change any code. ### The header height from hidden header is now ignored -Previously, the [`useHeaderHeight`](elements.md#use-header-height) hook returned `0` if the header was hidden in a Stack Navigator. Now it returns the height of the closes visible header instead. +Previously, the [`useHeaderHeight`](elements.md#useheaderheight) hook returned `0` if the header was hidden in a Stack Navigator. Now it returns the height of the closes visible header instead. ### Custom header now uses 'headerMode: screen' by default diff --git a/versioned_docs/version-7.x/auth-flow.md b/versioned_docs/version-7.x/auth-flow.md index f649b26663d..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 means: -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. +- 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 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`. +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. -The example shows stack navigator, but you can use the same approach with any navigator. +When the value of `isSignedin` changes, the screens defined based on the condition will change: -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. +- 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(); +We can use [`groups`](static-configuration.md#groups) to define multiple screens: -function useIsSignedIn() { - const isSignedIn = React.useContext(SignInContext); - return isSignedIn; -} - -function useIsSignedOut() { - const isSignedIn = React.useContext(SignInContext); - return !isSignedIn; -} - -/* content */ - -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 ? ( <> @@ -403,15 +360,15 @@ 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. ::: + + + ## Implement the logic for restoring the token :::note @@ -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() { @@ -601,10 +557,6 @@ export default function App() { return ; } - if (state.isLoading) { - return ; - } - const isSignedIn = state.userToken != null; return ( @@ -623,11 +575,11 @@ const RootStack = createNativeStackNavigator({ screen: HomeScreen, }, SignIn: { + if: useIsSignedOut, screen: SignInScreen, options: { title: 'Sign in', }, - if: useIsSignedOut, }, }, }); @@ -979,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 f46652fac87..574ffc16b34 100755 --- a/versioned_docs/version-7.x/bottom-tab-navigator.md +++ b/versioned_docs/version-7.x/bottom-tab-navigator.md @@ -152,16 +152,13 @@ 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` Boolean used to indicate whether inactive screens should be detached from the view hierarchy to save memory. This enables integration with [react-native-screens](https://github.com/software-mansion/react-native-screens). Defaults to `true`. -#### `sceneContainerStyle` - -Style object for the component wrapping the screen content. - #### `tabBar` Function that returns a React element to display as the tab bar. @@ -329,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` @@ -499,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', }, // ... @@ -584,9 +581,13 @@ Boolean indicating whether any nested stack should be popped to the top of the s 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. +#### `sceneStyle` + +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 408837bb090..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, }; @@ -1246,6 +1246,109 @@ const config = { Depending on your requirements, you can use this functionality to parse and stringify more complex data. +## Matching regular expressions + +If you need more complex matching logic, you can use regular expressions to match the path. For example, if you want to use the pattern `@username` to match a user's profile, you can use a regular expression to match the path. This allows you to have the same path pattern for multiple screens, but fine-tune the matching logic to be more specific for a particular screen. + +Regular expressions can be specified between parentheses `(` and `)` in the after a param name. For example: + + + + +```js +const RootStack = createStackNavigator({ + screens: { + Feed: { + screen: FeedScreen, + linking: { + path: ':sort(latest|popular)', + }, + }, + Profile: { + screen: ProfileScreen, + linking: { + path: ':username(@[A-Za-z0-9_]+)', + }, + }, + }, +}); +``` + + + + +```js +const config = { + screens: { + Feed: ':sort(latest|popular)', + Profile: ':username(@[A-Za-z0-9_]+)', + }, +}; +``` + + + + +This will only match the path if it starts with `@` followed by alphanumeric characters or underscores. For example, the URL `/@jane` will match the `Profile` screen, but `/jane` won't. + +Regular expressions are intended to only match path segments, not the entire path. So avoid using `/`, `^`, `$`, etc. in the regular expressions. + +:::warning + +Regular expressions are an advanced feature. They cannot be validated to warn you about potential issues, so it's up to you to ensure that the regular expression is correct. + +::: + +## Alias for paths + +If you want to have multiple paths for the same screen, you can use the `alias` property to specify an array of paths. This can be useful to keep backward compatibility with old URLs while transitioning to a new URL structure. + +For example, if you want to match both `/users/:id` and `/:id` to the `Profile` screen, you can do this: + + + + +```js +const RootStack = createStackNavigator({ + screens: { + Profile: { + screen: ProfileScreen, + linking: { + path: ':id', + alias: ['users/:id'], + }, + }, + }, +}); +``` + + + + +```js +const config = { + screens: { + Profile: { + path: ':id', + alias: ['users/:id'], + }, + }, +}; +``` + + + + +In this case, when the URL is `/users/jane` or `/jane`, it'll match the `Profile` screen. The `path` is the primary pattern that will be used to generate the URL, e.g. when navigating to the `Profile` screen in the app on the Web. The patterns in `alias` will be ignored when generating URLs. The `alias` patterns are not used for matching any child screens in nested navigators. + +On the web, if a screen containing an alias contains a nested navigator, the URL matching the alias will only be used to match the screen, and will be updated to the URL of the focused child screen once the app renders. + +Each item in the `alias` array can be a string matching the syntax of the `path` property, or an object with the following properties: + +- `path` (required) - The path pattern to match. +- `exact` - Whether to match the path exactly. Defaults to `false`. See [Matching exact paths](#matching-exact-paths) for more details. +- `parse` - Function to parse path segments into param values. See [Passing params](#passing-params) for more details. + ## Advanced cases For some advanced cases, specifying the mapping may not be sufficient. To handle such cases, you can specify a custom function to parse the URL into a state object ([`getStateFromPath`](navigation-container.md#linkinggetstatefrompath)), and a custom function to serialize the state object into an URL ([`getPathFromState`](navigation-container.md#linkinggetpathfromstate)). @@ -1254,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-based-navigation.md b/versioned_docs/version-7.x/drawer-based-navigation.md index e01f90d9016..d760ff5d33c 100755 --- a/versioned_docs/version-7.x/drawer-based-navigation.md +++ b/versioned_docs/version-7.x/drawer-based-navigation.md @@ -266,7 +266,6 @@ const Drawer = createDrawerNavigator(); function MyDrawer() { return ( } > @@ -418,7 +417,6 @@ const Drawer = createDrawerNavigator(); function MyDrawer() { return ( } > @@ -598,7 +596,6 @@ const Drawer = createDrawerNavigator(); function MyDrawer() { return ( } > diff --git a/versioned_docs/version-7.x/drawer-layout.md b/versioned_docs/version-7.x/drawer-layout.md index 1b7b92a0178..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 @@ -143,7 +123,7 @@ Type of the drawer. It determines how the drawer looks and animates. - `slide`: Both the screen and the drawer slide on swipe to reveal the drawer. - `permanent`: A permanent drawer is shown as a sidebar. -Defaults to `slide` on iOS and `front` on other platforms. +Defaults to `front`. ##### `drawerStyle` @@ -200,11 +180,15 @@ Minimum swipe velocity that should activate opening the drawer. Defaults to `500 This is only supported on iOS and Android. -##### `gestureHandlerProps` +#### `configureGestureHandler` -Props to pass to the underlying pan gesture handler. +Callback to configure the underlying [gesture from `react-native-gesture-handler`](https://docs.swmansion.com/react-native-gesture-handler/docs/gestures/gesture). It receives the `gesture` object as an argument: -This is only supported on iOS and Android. +```js +configureGestureHandler={({ gesture }) => { + return gesture.enableTrackpadTwoFingerGesture(false); +}} +``` ##### `children` diff --git a/versioned_docs/version-7.x/drawer-navigator.md b/versioned_docs/version-7.x/drawer-navigator.md index 93312af224b..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` @@ -530,13 +513,19 @@ Color overlay to be displayed on top of the content view when drawer gets open. -#### `sceneContainerStyle` +#### `sceneStyle` Style object for the component wrapping the screen content. -#### `gestureHandlerProps` +#### `configureGestureHandler` + +Callback to configure the underlying [gesture from `react-native-gesture-handler`](https://docs.swmansion.com/react-native-gesture-handler/docs/gestures/gesture). It receives the `gesture` object as an argument: -Props to pass to the underlying pan gesture handler. +```js +configureGestureHandler: ({ gesture }) => { + return gesture.enableTrackpadTwoFingerGesture(false); +}, +``` This is not supported on Web. @@ -575,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/elements.md b/versioned_docs/version-7.x/elements.md index 4ddd0fe471c..0e9195a327a 100644 --- a/versioned_docs/version-7.x/elements.md +++ b/versioned_docs/version-7.x/elements.md @@ -343,6 +343,12 @@ Options for the search bar in the header. When this is specified, the header wil It can contain the following properties: +- `ref`: Ref to manipulate the search input imperatively. It contains the following methods: + - `focus` - focuses the search bar + - `blur` - removes focus from the search bar + - `setText` - sets the search bar's content to given value + - `clearText` - removes any text present in the search bar input field + - `cancelSearch` - cancel the search and close the search bar - `autoCapitalize`: The auto-capitalization behavior. Possible values: `none`, `words`, `sentences`, `characters`. - `autoFocus`: Automatically focus search input on mount. - `cancelButtonText`: Text to be used instead of default `Cancel` button text (iOS only). @@ -380,11 +386,19 @@ This is a short-hand for the following styles: } ``` -If the above styles are specified in `headerStyle` along with `headerShadowVisible: false`, then `headerShadowVisible: false` will take precedence. +If any of the above styles are specified in `headerStyle` along with `headerShadowVisible: false`, then the styles in `headerStyle` will take precedence. #### `headerStyle` -Style object for the header. You can specify a custom background color here, for example. +Style object for the header. You can specify a custom background color here, for example: + +```js +{ + backgroundColor: 'tomato', +} +``` + +Note that `headerStyle` won't take effect if you are also using [`headerBackground`](#headerbackground). In that case, you should style the element returned from `headerBackground` instead. #### `headerTitleStyle` diff --git a/versioned_docs/version-7.x/getting-started.md b/versioned_docs/version-7.x/getting-started.md index 8f751321c80..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 @@ -26,19 +26,36 @@ Here are some resources to help you out: - `expo` >= 52 (if you use [Expo Go](https://expo.dev/go)) - `typescript` >= 5.0.0 (if you use TypeScript) +## Starter template + +If you're starting a new project, you can use the [React Navigation template](https://github.com/react-navigation/template) to quickly set up a new project with [Static configuration](#static-configuration): + +```bash +npx create-expo-app@latest --template react-navigation/template +``` + +See the project's `README.md` for more information on how to get started. + +If you created a new project using the template, you can skip the installation steps below and move on to ["Hello React Navigation"](hello-react-navigation.md?config=static). + +Otherwise, you can follow the instructions below to install React Navigation into your existing project. + ## 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: @@ -46,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: @@ -58,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 + +[`react-native-screens`](https://github.com/software-mansion/react-native-screens) requires one additional configuration to properly work on Android. -Add the highlighted code to the body of `MainActivity` class: +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 // ... } ``` @@ -109,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 @@ -129,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 517eee93e51..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 @@ -198,10 +207,6 @@ Object containing the initial height and width of the screens. Passing this will } ``` -#### `sceneContainerStyle` - -Style to apply to the view wrapping each screen. You can pass this to override some default styles such as overflow clipping. - #### `style` Style to apply to the tab view container. @@ -402,10 +407,6 @@ Boolean indicating whether to make the tab bar scrollable. If you set this to `true`, you should also specify a width in `tabBarItemStyle` to improve the performance of initial render. -#### `tabBarIconStyle` - -Style object for the tab icon container. - #### `tabBarLabelStyle` Style object for the tab label. @@ -444,6 +445,10 @@ This view is usually only shown for a split second. Keep it lightweight. By default, this renders `null`. +#### `sceneStyle` + +Style to apply to the view wrapping each screen. You can pass this to override some default styles such as overflow clipping. + ### Events The navigator can [emit events](navigation-events.md) on certain actions. Supported events are: 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/multiple-drawers.md b/versioned_docs/version-7.x/multiple-drawers.md index 9beb0b01f1c..8b369a690a7 100644 --- a/versioned_docs/version-7.x/multiple-drawers.md +++ b/versioned_docs/version-7.x/multiple-drawers.md @@ -404,7 +404,7 @@ To solve this, we need to use [`navigation.getParent`](navigation-object.md#getp ``` -However, this means that our button needs to know about the parent navigators, which isn't ideal. If our button is further nested inside other navigators, it'd need multiple `getParent()` calls. To address this, we can use the [`id` prop](drawer-navigator.md#id) to identify the parent navigator. +However, this means that our button needs to know about the parent navigators, which isn't ideal. If our button is further nested inside other navigators, it'd need multiple `getParent()` calls. To address this, we can use the [`id` prop](navigator.md#id) to identify the parent navigator. To customize the contents of the drawer, we can use the [`drawerContent` prop](drawer-navigator.md#drawercontent) to pass in a function that renders a custom component. @@ -557,4 +557,4 @@ Here, we are passing `"LeftDrawer"` and `"RightDrawer"` strings (you can use any - To have multiple drawers, you can use [`react-native-drawer-layout`](drawer-layout.md) directly in combination with a drawer navigator. - The [`drawerPosition`](drawer-layout.md#drawerposition) prop can be used to position the drawer on the right. - The methods to control the drawer can be passed down using context API when using [`react-native-drawer-layout`](drawer-layout.md). -- When nesting multiple navigators, you can use [`navigation.getParent`](navigation-object.md#getparent) in combination with the [`id` prop](drawer-navigator.md#id) to refer to the desired drawer. +- When nesting multiple navigators, you can use [`navigation.getParent`](navigation-object.md#getparent) in combination with the [`id` prop](navigator.md#id) to refer to the desired drawer. 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 3baf93f4e1b..d0bd10b6a8a 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. @@ -276,10 +908,6 @@ Example: }, ``` -#### `headerShown` - -Whether to show the header. The header is shown by default. Setting this to `false` hides the header. - #### `headerStyle` Style object for header. Supported properties: @@ -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. @@ -477,9 +1175,9 @@ Example: #### `headerSearchBarOptions` -Options to render a native search bar on iOS. Search bars are rarely static so normally it is controlled by passing an object to `headerSearchBarOptions` navigation option in the component's body. +Options to render a native search bar. Search bars are rarely static so normally it is controlled by passing an object to `headerSearchBarOptions` navigation option in the component's body. -You also need to specify `contentInsetAdjustmentBehavior="automatic"` in your `ScrollView`, `FlatList` etc. If you don't have a `ScrollView`, specify `headerTransparent: false`. +On iOS, you also need to specify `contentInsetAdjustmentBehavior="automatic"` in your `ScrollView`, `FlatList` etc. If you don't have a `ScrollView`, specify `headerTransparent: false`. Example: @@ -493,19 +1191,31 @@ React.useLayoutEffect(() => { }, [navigation]); ``` -Supported properties are described below. +Supported properties are: + +##### `ref` + +Ref to manipulate the search input imperatively. It contains the following methods: + +- `focus` - focuses the search bar +- `blur` - removes focus from the search bar +- `setText` - sets the search bar's content to given value +- `clearText` - removes any text present in the search bar input field +- `cancelSearch` - cancel the search and close the search bar +- `toggleCancelButton` - depending on passed boolean value, hides or shows cancel button (only supported on iOS) ##### `autoCapitalize` Controls whether the text is automatically auto-capitalized as it is entered by the user. Possible values: +- `systemDefault` - `none` - `words` - `sentences` - `characters` -Defaults to `sentences`. +Defaults to `systemDefault` which is the same as `sentences` on iOS and `none` on Android. ##### `autoFocus` @@ -533,7 +1243,7 @@ Only supported on iOS. The text to be used instead of default `Cancel` button text. -Only supported on iOS. +Only supported on iOS. **Deprecated** starting from iOS 26. ##### `disableBackButtonOverride` @@ -543,7 +1253,9 @@ Only supported on Android. ##### `hideNavigationBar` -Boolean indicating whether to hide the navigation bar during searching. Defaults to `true`. +Boolean indicating whether to hide the navigation bar during searching. + +If left unset, system default is used. Only supported on iOS. @@ -553,374 +1265,150 @@ Boolean indicating whether to hide the search bar when scrolling. Defaults to `t 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` +##### `inputType` -The type of animation to use when this screen replaces another screen. Defaults to `push`. +The type of the input. Defaults to `"text"`. Supported values: -- `push`: the new screen will perform push animation. +- `"text"` +- `"phone"` +- `"number"` +- `"email"` - +Only supported on Android. -- `pop`: the new screen will perform pop animation. +##### `obscureBackground` - +Boolean indicating whether to obscure the underlying content with semi-transparent overlay. -#### `animation` +If left unset, system default is used. -How the screen should animate when pushed or popped. +Only supported on iOS. -Only supported on Android and iOS. +##### `placement` -Supported values: +Controls preferred placement of the search bar. Defaults to `automatic`. -- `default`: use the platform default animation - +Supported values: -- `fade`: fade screen in or out - +- `automatic` +- `stacked` +- `inline` (**deprecated** starting from iOS 26, it is mapped to `integrated`) +- `integrated` (available starting from iOS 26, on prior versions it is mapped to `inline`) +- `integratedButton` (available starting from iOS 26, on prior versions it is mapped to `inline`) +- `integratedCentered` (available starting from iOS 26, on prior versions it is mapped to `inline`) -- `fade_from_bottom`: fade the new screen from bottom - +Only supported on iOS. -- `flip`: flip the screen, requires `presentation: "modal"` (iOS only) - +##### `allowToolbarIntegration` -- `simple_push`: default animation, but without shadow and native header transition (iOS only, uses default animation on Android) - +Boolean indicating whether the system can place the search bar among other toolbar items on iPhone. -- `slide_from_bottom`: slide in the new screen from bottom - +Set this prop to `false` to prevent the search bar from appearing in the toolbar when `placement` is `automatic`, `integrated`, `integratedButton` or `integratedCentered`. -- `slide_from_right`: slide in the new screen from right (Android only, uses default animation on iOS) - +Defaults to `true`. If `placement` is set to `stacked`, the value of this prop will be overridden with `false`. -- `slide_from_left`: slide in the new screen from left (Android only, uses default animation on iOS) - +Only supported on iOS, starting from iOS 26. -- `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 @@ -1006,6 +1494,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. +- `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. @@ -1050,3 +1540,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 fa0bb6e3f30..91b6dfa41fc 100755 --- a/versioned_docs/version-7.x/navigation-actions.md +++ b/versioned_docs/version-7.x/navigation-actions.md @@ -28,6 +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. +- `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`. @@ -67,9 +70,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -87,52 +87,6 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - - ); } @@ -189,9 +143,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -210,52 +161,6 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - - ); } @@ -281,15 +186,23 @@ 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#getid) 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 The `navigate` action can also accepts an object as the argument with the following properties: - `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 we should merge the params of the current route with the provided `params`. Defaults to `false`. +- `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. + +
+ ### reset The `reset` action allows to reset the [navigation state](navigation-state.md) to the given state. It takes the following arguments: @@ -332,9 +245,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -352,13 +262,6 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - ); } @@ -454,9 +336,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -475,17 +354,6 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - ); } @@ -623,15 +470,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -651,46 +489,9 @@ function ProfileScreen({ route }) { {route.params.user}'s profile - - - - ); } @@ -778,50 +570,9 @@ function ProfileScreen({ route }) { {route.params.user}'s profile - - - - ); } @@ -905,48 +653,13 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - - ); } @@ -1028,31 +738,6 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - ); } @@ -1398,53 +1080,14 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - ); @@ -1500,9 +1143,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -1523,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 + ); @@ -1590,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'; @@ -1628,9 +1407,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -1648,53 +1424,14 @@ function ProfileScreen({ route }) { > Profile! {route.params.user}'s profile - - - ); @@ -1717,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'; @@ -1750,9 +1487,6 @@ function HomeScreen() { > Navigate to Profile - ); } @@ -1773,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 ); @@ -1840,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 3d1bc3d294d..42af46e61b2 100755 --- a/versioned_docs/version-7.x/navigation-object.md +++ b/versioned_docs/version-7.x/navigation-object.md @@ -74,8 +74,11 @@ The `navigate` method lets us navigate to another screen in your app. It takes t `navigation.navigate(name, params)` -- `name` - A destination name of the route that has been defined somewhere -- `params` - Params to pass to the destination route. +- `name` - _string_ - A destination name of the screen in the current or a parent navigator. +- `params` - _object_ - Params to use for the destination route. +- `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`. @@ -244,43 +247,9 @@ 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#getid) 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 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#getid) 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` @@ -297,15 +266,14 @@ It takes the following arguments: `navigation.navigateDeprecated(name, params)` -- `name` - A destination name of the route that has been defined somewhere -- `params` - Params to pass to the destination route. +- `name` - _string_ - A destination name of the screen in the current or a parent navigator. +- `params` - _object_ - Params to use for the destination route. 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#getid) 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. @@ -483,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'; @@ -622,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'; @@ -966,7 +934,7 @@ Preloading a screen means that the screen will be rendered in the background. Al Depending on the navigator, `preload` may work slightly differently: -- In a stack navigator ([stack](stack-navigator.md), [native stack](native-stack-navigator.md)), the screen will be rendered off-screen and animated in when you navigate to it. If [`getId`](screen.md#getid) is specified, it'll be used for the navigation to identify the preloaded screen. +- In a stack navigator ([stack](stack-navigator.md), [native stack](native-stack-navigator.md)), the screen will be rendered off-screen and animated in when you navigate to it. If [`getId`](screen.md#id) is specified, it'll be used for the navigation to identify the preloaded screen. - In a tab or drawer navigator ([bottom tabs](bottom-tab-navigator.md), [material top tabs](material-top-tab-navigator.md), [drawer](drawer-navigator.md), etc.), the existing screen will be rendered as if `lazy` was set to `false`. Calling `preload` on a screen that is already rendered will not have any effect. When a screen is preloaded in a stack navigator, it will have a few limitations: @@ -1004,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'; @@ -1106,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'; @@ -1205,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. @@ -1212,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'; @@ -1302,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 6070d146199..43d2a59a7b5 100644 --- a/versioned_docs/version-7.x/navigation-state.md +++ b/versioned_docs/version-7.x/navigation-state.md @@ -4,7 +4,7 @@ title: Navigation state reference sidebar_label: Navigation state --- -The navigation state is the state where React Navigation stores the navigation structure and history of the app. It's useful to know about the structure of the navigation state if you need to do advanced operations such as [resetting the state](navigation-actions.md#reset), [providing a custom initial state](navigation-container.md#initial-state) etc. +The navigation state is the state where React Navigation stores the navigation structure and history of the app. It's useful to know about the structure of the navigation state if you need to do advanced operations such as [resetting the state](navigation-actions.md#reset), [providing a custom initial state](navigation-container.md#initialstate) etc. It's a JavaScript object which looks like this: @@ -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,9 +88,9 @@ 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#initial-state) 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: +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: ```js const state = { @@ -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#initial-state), 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/nesting-navigators.md b/versioned_docs/version-7.x/nesting-navigators.md index bb96f16f59b..4264cc57dcb 100755 --- a/versioned_docs/version-7.x/nesting-navigators.md +++ b/versioned_docs/version-7.x/nesting-navigators.md @@ -791,6 +791,12 @@ navigation.navigate('Home', { In the above case, you're navigating to the `Media` screen, which is in a navigator nested inside the `Sound` screen, which is in a navigator nested inside the `Settings` screen. +:::warning + +The `screen` and related params are reserved for internal use and are managed by React Navigation. While you can access `route.params.screen` etc. in the parent screens, relying on them may lead to unexpected behavior. + +::: + ### Rendering initial route defined in the navigator By default, when you navigate a screen in the nested navigator, the specified screen is used as the initial screen and the `initialRouteName` prop on the navigator is ignored. 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). + ::: ); } @@ -301,6 +309,6 @@ if (!isReady) { ## Warning: Serializable State -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. +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 50f785ff2b5..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 @@ -425,10 +438,6 @@ Style to apply to the active indicator. Style to apply to the container view for the indicator. -##### `labelStyle` - -Style to apply to the tab item label. - ##### `contentContainerStyle` Style to apply to the inner container for tabs. @@ -515,6 +524,10 @@ label: ({ route, labelText, focused, color }) => ( ); ``` +##### `labelStyle` + +Style to apply to the tab item label. + ##### `icon` A function that returns a custom React Element to be used as an icon. The function receives an object with the following properties: 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 +