diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000000..e918c96b33 --- /dev/null +++ b/.babelrc @@ -0,0 +1,32 @@ +{ + "env": { + // For RN example development + "development": { + "presets": ["react-native"], + "plugins": [ + "transform-flow-strip-types", + ], + }, + // For Jest + "test": { + "presets": ["react-native"], + "plugins": [ + "transform-flow-strip-types", + ], + }, + // For publishing to NPM for RN + "publish-rn": { + "presets": ["react-native-syntax"], + "plugins": [ + "transform-flow-strip-types", + ], + }, + // For publishing to NPM for RN + "publish-web": { + "presets": ["es2015", "stage-1", "react"], + "plugins": [ + "transform-flow-strip-types", + ], + }, + }, +} diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000000..f6b6bc9af5 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,77 @@ +{ + "extends": "airbnb", + "parser": "babel-eslint", + "plugins": [ + "flowtype" + ], + "env": { + "jasmine": true + }, + "rules": { + "no-underscore-dangle": 0, + "no-use-before-define": 0, + "no-unused-expressions": 0, + "new-cap": 0, + "no-plusplus": 0, + "no-class-assign": 0, + "no-duplicate-imports": 0, + + "import/extensions": 0, + "import/no-extraneous-dependencies": 0, + "import/no-unresolved": 0, + + "react/jsx-filename-extension": [ + 0, { "extensions": [".js", ".jsx"] } + ], + "react/forbid-prop-types": 0, + "react/sort-comp": 0, + "react/prefer-stateless-function": 0, + + "flowtype/boolean-style": [ + 2, + "boolean" + ], + "flowtype/define-flow-type": 1, + "flowtype/generic-spacing": [ + 2, + "never" + ], + "flowtype/no-weak-types": 1, + "flowtype/require-parameter-type": 2, + "flowtype/require-return-type": [ + 0, + "always", + { + "annotateUndefined": "never" + } + ], + "flowtype/require-valid-file-annotation": 2, + "flowtype/semi": [ + 2, + "always" + ], + "flowtype/space-after-type-colon": [ + 2, + "always" + ], + "flowtype/space-before-generic-bracket": [ + 2, + "never" + ], + "flowtype/space-before-type-colon": [ + 2, + "never" + ], + "flowtype/union-intersection-spacing": [ + 2, + "always" + ], + "flowtype/use-flow-type": 1, + "flowtype/valid-syntax": 1 + }, + "settings": { + "flowtype": { + "onlyFilesWithFlowAnnotation": false + } + } +} diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000000..6336698c61 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,55 @@ +[ignore] +; We fork some components by platform +.*/*[.]android.js + +; Ignore templates for 'react-native init' +.*/local-cli/templates/.* + +; Ignore the website subdir +.*/node_modules/react-native/website/.* + +; Ignore "BUCK" generated dirs +.*/node_modules/react-native/\.buckd/ + +; Ignore unexpected extra "@providesModule" +.*/node_modules/.*/node_modules/fbjs/.* + +; Ignore duplicate module providers +; For RN Apps installed via npm, "Libraries" folder is inside +; "node_modules/react-native" but in the source repo it is in the root +.*/node_modules/react-native/Libraries/react-native/React.js +.*/node_modules/react-native/Libraries/react-native/ReactNative.js + +/lib +/lib-rn +/examples +/website + +[include] + +[libs] +node_modules/react-native/Libraries/react-native/react-native-interface.js +node_modules/react-native/flow/ +flow/ + +[options] +module.system=haste + +experimental.strict_type_args=true + +munge_underscores=true + +module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' + +suppress_type=$FlowIssue +suppress_type=$FlowFixMe +suppress_type=$FixMe + +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-7]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-7]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy + +unsafe.enable_getters_and_setters=true + +[version] +^0.35.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..2428835b39 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# VSCode +.vscode/ +jsconfig.json + +# NodeJS +npm-debug.log +node_modules +lib-rn +lib +yarn-error.log + +# OS X +.DS_Store + +# Exponent +.exponent diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000000..b7265688ad --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ +examples \ No newline at end of file diff --git a/.watchmanconfig b/.watchmanconfig new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/.watchmanconfig @@ -0,0 +1 @@ +{} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..063ab66be4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +BSD License + +For React Navigation software + +Copyright (c) 2016-present, React Navigation Contributors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..67820a3a74 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,17 @@ +Please provide enough information so that others can review your pull request: + +Explain the **motivation** for making this change. What existing problem does the pull request solve? + +Prefer **small pull requests**. These are much easier to review and more likely to get merged. Make sure the PR does only one thing, otherwise split it. + +**Test plan (required)** + +Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI. + +Make sure you test on both platforms if your change affects both platforms. + +The code must pass tests and shouldn't add more Flow errors. + +**Code formatting** + +Look around. Match the style of the rest of the codebase. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..ca6f52cb84 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# React Navigation [![Circle CI](https://circleci.com/gh/reactjs/react-navigation.svg?style=svg&circle-token=296a074544f10b6580652bd283b2c1817154dc20)](https://circleci.com/gh/reactjs/react-navigation) + +*Learn once, navigate anywhere.* + +Browse the docs on [reactnavigation.org](https://navigate:navigate@reactnavigation.org/). (The username/pass is `navigate:navigate`) + +## [Getting started](https://reactnavigation.org/docs/intro/) + +1. Create a new React Native App + ``` + react-native init SimpleApp + cd SimpleApp + ``` + +2. Install the latest version of react-navigation from npm + ``` + yarn add react-navigation + ``` + or + ``` + npm install --save react-navigation + ``` + +3. Run the new app + ``` + react-native run-android # or: + react-native run-ios + ``` + +## Advanced guide + +- [Redux integration](https://reactnavigation.org/docs/guides/redux) +- [Web integration](https://reactnavigation.org/docs/guides/web) +- [Deep linking](https://reactnavigation.org/docs/guides/linking) +- [Contributors guide](https://reactnavigation.org/docs/guides/contributors) + +## React Navigation API + +- [Navigators](https://reactnavigation.org/docs/navigators/) +- [Routers](https://reactnavigation.org/docs/routers/) +- [Views](https://reactnavigation.org/docs/views/) + diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000000..7911679bf4 --- /dev/null +++ b/circle.yml @@ -0,0 +1,23 @@ +machine: + node: + version: 6.1.0 + pre: + - mkdir ~/.yarn-cache +dependencies: + pre: + # Install Yarn + - sudo apt-key adv --fetch-keys http://dl.yarnpkg.com/debian/pubkey.gpg + - echo "deb http://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list + - sudo apt-get update -qq + - sudo apt-get install -y -qq yarn + cache_directories: + - "~/.yarn-cache" + override: + - yarn install + - cd website && yarn install +deployment: + website-prod: + branch: master + commands: + - yarn run build-docs + - ./scripts/deploy-website.sh diff --git a/docs/api/navigators/DrawerNavigator.md b/docs/api/navigators/DrawerNavigator.md new file mode 100644 index 0000000000..ef8f312065 --- /dev/null +++ b/docs/api/navigators/DrawerNavigator.md @@ -0,0 +1,120 @@ +# DrawerNavigator + +Used to easily set up a screen with a drawer navigation. + +```js +class MyHomeScreen extends React.Component { + static navigationOptions = { + drawer: () => ({ + label: 'Home', + icon: ({ tintColor }) => ( + + ), + }), + } + + render() { + return ( + +); +``` + + +### The static `router` + +A router object may be statically defined on your component. If defined, it must follow this interface: + +```javascript +type BackAction = {type: 'Back'}; +type URIAction = {type: 'URI', uri: string}; + +interface Router { + getStateForAction(action: (A | BackAction | URIAction), lastState: ?S): ?S; + getActionForURI(uri: string): ?A; +} +``` + +The state and action types of the static router must match the state and action types associated with the navigation prop passed into the component. + +#### router.getStateForAction(action, lastState) + +This function is defined on the static router and is used to define the expected navigation state. + +```javascript +class ScreenWithEditMode extends React.Component { + static router = { + getStateForAction: (action, prevState) => { + return { isEditing: true }; + }, + }; + render() { + // this.props.navigation.state.isEditing === true + ... + } +} +``` + +`getStateForAction` must **always** return a navigation state that can be rendered by the component when passed in as the `navigation.state` prop. + +If null is returned, we are signaling that the previous navigation state has not changed, but the action is handled. This is usually used in cases where the action is being swallowed. + + +#### router.getActionForURI(uri) + +Return an action if a URI can be handled, otherwise return `null` + + + +### Special Actions + +There are two special actions that can be fired into `navigation.dispatch` and can be handled by your `router.getStateForAction`. + +#### Back Action + +This action means the same thing as an Android back button press. + +``` +type BackAction = { type: 'Back' }; +``` + +#### URI Open Action + +Used to request the enclosing app or OS to open a link at a particular URI. If it is a web URI like `http` or `https`, the app may open a WebView to present the page. Or the app may open the URI in a web browser. In some cases, an app may choose to block a URI action or handle it differently. + +``` +type URIAction = { type: 'URI', uri: string }; +``` + + +### Special Navigation State + +The state defined by `router.getStateForAction` can contain special navigation properties that may be relevant to your app. The title and current URI of a component may change over time, and the parent often needs to observe the behavior. + +#### `state.title` + +If the navigation state contains 'title', it will be used as the title for the given component. This is relevant for top-level components on the web to update the browser title, and is relevant in mobile apps where a title is shown in the header. + +#### `state.uri` + +A URI can also be put in `state.uri`, which will signal to the parent how it may be possible to deep link into a similar navigation state. In web apps, this will be used to keep the URI bar in sync with the current navigation state of the app. + + +## Use Cases + +### "Block the Android back button on one screen of my app" + +To block the Android back button: + +``` +class Foo extends React.Component { + static router = { + getStateForAction(action, prevState = {}) { + if (action.type === 'Back') return null; + else return prevState; + }, + }; + render() { + ... +``` + +Because we return null, we signal to our container that the action has been handled but the state does not change. The parent should not handle the back behavior at this point, and nothing should be re-rendered. + +### "Link deeply into one screen of my app" + +``` +class Foo extends React.Component { + static router = { + getStateForAction(action, prevState = {deep: false}) { + if (action.type === 'GoDeep') return { deep: true }; + else return prevState; + }, + getActionForURI(uri) { + if (uri === 'myapp://foo') + return {type: 'Go'}; + else if (uri === 'myapp://foo_deep') + return {type: 'GoDeep'}; + return null; + }, + }; + render() { + // this.props.navigation.state.deep may be true or false + ... +``` + +Based on the state URI we may decide to return an action. If an action is returned, `getStateForAction` is expected to output the correct state for a deep link. + +## Reference Implementations + +A library to that helps easily produce navigation-aware components: https://github.com/reactjs/react-navigation . (Also uses a HOC to provide navigation containers when needed.) + +A simple navigation container: https://gist.github.com/ericvicenti/77d190e2ec408012255937400e34bdb1 + +A web implementation of a navigation container: https://gist.github.com/ericvicenti/55bef95fcd8558029a3bae8483baea6c diff --git a/docs/guides/Contributors.md b/docs/guides/Contributors.md new file mode 100644 index 0000000000..656a0f82b6 --- /dev/null +++ b/docs/guides/Contributors.md @@ -0,0 +1,79 @@ +# Contributors Guide + +## Environment + +React navigation was initially developed on macOS 10.12, with node 7+, and react-native v0.39+. Please open issues when uncovering problems in different environments. + +## Development + +### 0. Basic Install + +``` +git clone git@github.com:reactjs/react-navigation.git +cd react-navigation +npm install +``` + +### 1. Run the native playground + +``` +cd examples/NavigationPlayground +npm install +cd ../.. +npm start + +# In a seperate terminal tab: +npm run run-playground-android +npm run run-playground-ios +``` + +### 2. Run the website + +For development mode and live-reloading: + +``` +cd website +npm install +npm run start +``` + +To run the website in production mode with server rendering: + +``` +npm run prod +``` + +### 3. Run tests, run flow + +``` +jest +flow +``` + +Tests must pass for your changes to be accepted and merged. + +Flow is not yet passing, but your code should be flow checked and we expect that your changes do not introduce any flow errors. + + +### 4. Developing Docs + +The docs are indexed in [App.js](https://github.com/reactjs/react-navigation/blob/master/website/src/App.js), where all the pages are declared alongside the titles. To test the docs, follow the above instructions for running the website. Changing existing markdown files should not require any testing. + +The markdown from the `docs` folder gets generated and dumped into a json file as a part of the build step. To see updated docs appear in the website, re-run the build step by running `npm run build-docs` from the `react-navigation` root folder. + + +## Submitting Contributions + +### New views or unique features + +Often navigation needs are specific to certain apps. If your changes are unique to your app, you may want to fork the view or router that has changed. You can keep the source code in your app, or publish it on npm as a `react-navigation` compatible view or router. + +This library is intended to include highly standard and generic navigation patterns. But it + +### Major Changes + +Before embarking on any major changes, please file an issue describing the suggested change and motivation. We may already have thought about it and we want to make sure we all are on the same page before starting on any big changes. + +### Minor Bugfixes + +Simple bug fixes are welcomed in pull requests! Please check for duplicate PRs before posting. diff --git a/docs/guides/Custom-Navigators.md b/docs/guides/Custom-Navigators.md new file mode 100644 index 0000000000..077ff3b3b9 --- /dev/null +++ b/docs/guides/Custom-Navigators.md @@ -0,0 +1,110 @@ +# Custom Navigators + +A navigator is any React component that has a [router](/docs/routers/) on it. Here is a basic one, which uses the [router's API](/docs/routers/api) to get the active component to render: + +```js +class MyNavigator extends React.Component { + static router = MyRouter; + render() { + const { state, dispatch } = this.props.navigation; + const { routes, index } = state; + + // Figure out what to render based on the navigation state and the router: + const Component = MyRouter.getComponentForState(state); + + // The state of the active child screen can be found at routes[index] + let childNavigation = { dispatch, state: routes[index] }; + // If we want, we can also tinker with the dispatch function here, to limit + // or augment our children's actions + + // Assuming our children want the convenience of calling .navigate() and so on, + // we should call addNavigationHelpers to augment our navigation prop: + childNavigation = addNavigationHelpers(childNavigation); + + return ; + } +} +``` + +## Navigation Prop + +The navigation prop passed down to a navigator only includes `state` and `dispatch`. This is the current state of the navigator, and an event channel to send action requests. + +All navigators are controlled components: they always display what is coming in through `props.navigation.state`, and their only way to change the state is to send actions into `props.navigation.dispatch`. + +Navigators can specify custom behavior to parent navigators by [customizing their router](/docs/routers/). For example, a navigator is able to specify when actions should be blocked by returning null from `router.getStateForAction`. Or a navigator can specify custom URI handling by overriding `router.getActionForPathAndParams` to output a relevant navigation action, and handling that action in `router.getStateForAction`. + +### Navigation State + +The navigation state that is passed into a navigator's `props.navigation.state` has the following structure: + +``` +{ + index: 1, // identifies which route in the routes array is active + routes: [ + { + // Each route needs a name, which routers will use to associate each route + // with a react component + routeName: 'MyRouteName', + + // A unique id for this route, used to keep order in the routes array: + key: 'myroute-123', + + // Routes can have any additional data. The included routers have `params` + ...customRouteData, + }, + ...moreRoutes, + ] +} +``` + +### Navigation Dispatchers + +A navigator can dispatch navigation actions, such as 'Go to a URI', 'Go back'. + +The dispatcher will return `true` if the action was successfully handled, otherwise `false`. + +## Navigation Containers + +The built in navigators can automatically behave like top-level navigators when the navigation prop is missing. This functionality provides a transparent navigation container, which is where the top-level navigation prop comes from. + +When rendering one of the included navigators, the navigation prop is optional. When it is missing, the container steps in and manages its own navigation state. It also handles URLs, external linking, and Android back button integration. + +For the purpose of convenience, the built-in navigators have this ability because behind the scenes they use `createNavigationContainer`. Usually, navigators require a navigation prop in order to function. + +### `containerOptions` + +These options can be used to configure a navigator when it is used at the top level. + +An error will be thrown if a navigator is configured with `containerOptions` and also receives a `navigation` prop, because in that case it would be unclear if the navigator should handle its own state. + +- `URIPrefix` - The prefix of the URIs that the app might handle. This will be used when handling a [deep link](/docs/guides/linking) to extract the path passed to the router. + +## API for building custom navigators + +To help developers implement custom navigators, the following utilities are provided with React Navigation: + +### `createNavigator` + +This utility combines a [router](/docs/routers/) and a [navigation view](/docs/views/) together in a standard way: + +```js +const MyApp = createNavigator(MyRouter)(MyView); +``` + +All this does behind the scenes is: + +```js +const MyApp = ({ navigation }) => ( + +); +MyApp.router = MyRouter; +``` + +### `addNavigationHelpers` + +Takes in a bare navigator navigation prop with `state` and `dispatch`, and augments it with all the various functions in a screen navigation prop, such as `navigation.navigate()` and `navigation.goBack()`. These functions are simply helpers to create the actions and send them into `dispatch`. + +### `createNavigationContainer` + +If you want your navigator to be usable as a top-level component, (without a navigation prop being passed in), you can use `createNavigationContainer`. This utility will make your navigator act like a top-level navigator when the navigation prop is missing. It will manage the app state, and integrate with app-level nav features, like handling incoming and outgoing links, and Android back button behavior. diff --git a/docs/guides/Customizing-Navigation.md b/docs/guides/Customizing-Navigation.md new file mode 100644 index 0000000000..725bf879c9 --- /dev/null +++ b/docs/guides/Customizing-Navigation.md @@ -0,0 +1,53 @@ +## Customizing Navigation Views + +Modify the presentation of navigation, including styles, animations and gestures. + +## Customizing Routers + +Building a custom router allows you to change the navigation logic of your component, manage navigation state, and define behavior for URIs. + + +A router can be defined like this: + +``` +class MyNavigationAwareComponent extends React.Component { + + static router = { + + // Defines the navigation state for a component: + getStateForAction: (action: {type: string}, lastState?: any) => { + const state = lastState = { myMode: 'default' }; + if (action.type === 'MyAction') { + return { myMode: 'action' }; + } else if (action.type === 'Back') { + return { myMode: 'blockBackButton' }; + } else { + return state; + } + }, + + // Defines if a component can handle a particular URI. + // If it does, return an action to be passed to `getStateForAction` + + getActionForURI: (uri: string) => { + if (uri === 'myapp://myAction') { + return { type: 'MyAction' }; + } + return null; + }, + + }; + + render() { + // render something based on this.props.navigation.state + ... + } + + onButtonPress = () => { + this.props.navigation.dispatch({ type: 'MyAction' }); + }; + + ... + +} +``` diff --git a/docs/guides/Deep-Linking.md b/docs/guides/Deep-Linking.md new file mode 100644 index 0000000000..396d6ae143 --- /dev/null +++ b/docs/guides/Deep-Linking.md @@ -0,0 +1,107 @@ +# Deep Linking + +In this guide we will set up our app to handle external URIs. Let's start with the SimpleApp that [we created in the getting started guide](/docs/intro). + +In this example, we want a URI like `mychat://chats/Taylor` to open our app and link straight into Taylor's chat page. + +## Configuration + +Previously, we had defined a navigator like this: + +```js +const SimpleApp = StackNavigator({ + Home: { screen: HomeScreen }, + Chat: { screen: ChatScreen }, +}); +``` + +We want paths like `chat/Taylor` to link to a "Chat" screen with the `user` passed as a param. Let's re-configure our chat screen with a `path` that tells the router what relative path to match against, and what params to extract. This path spec would be `chat/:user`. + +```js +const SimpleApp = StackNavigator({ + Home: { screen: HomeScreen }, + Chat: { + screen: ChatScreen, + path: 'chat/:user', + }, +}); +``` + + +### URI Prefix + +Next, let's configure our navigation container to extract the path from the app's incoming URI. When configuring a top-level navigator, we can provide `containerOptions`: + +```js +const SimpleApp = StackNavigator({ + ... +}, { + containerOptions: { + // on Android, the URI prefix typically contains a host in addition to scheme + URIPrefix: Platform.OS == 'android' ? 'mychat://mychat/' : 'mychat://', + }, +}); +``` + +## iOS + +Let's configure the native iOS app to open based on the `mychat://` URI scheme. + +In `SimpleApp/ios/SimpleApp/AppDelegate.m`: + +``` +// Add the header at the top of the file: +#import + +// Add this above the `@end`: +- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication annotation:(id)annotation +{ + return [RCTLinkingManager application:application openURL:url + sourceApplication:sourceApplication annotation:annotation]; +} +``` + +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/xcode-linking.png) + +Now you can press play in Xcode, or re-build on the command line: + +```sh +react-native run-ios +``` + +To test the URI in iOS, open safari and type `mychat://chat/Taylor`. + +## Android + +To configure the external linking in Android, you can create a new intent in the manifest. + +In `SimpleApp/android/app/src/main/AndroidManifest.xml`, add the new `VIEW` type `intent-filter` inside the `MainActivity` entry: + +``` + + + + + + +``` + +Now, re-install the app: + +```sh +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://mychat/chat/Taylor" com.simpleapp +``` + +```phone-example +linking +``` diff --git a/docs/guides/Guide-Headers.md b/docs/guides/Guide-Headers.md new file mode 100644 index 0000000000..96598a34ee --- /dev/null +++ b/docs/guides/Guide-Headers.md @@ -0,0 +1,98 @@ +# Configuring the Header + +In the previous example, we created a StackNavigator to display severals screens in our app. + + +When navigating to a chat screen, we can specify params for the new route by providing them to the navigate function. In this case we want to provide the name of the person on the chat screen: + +```js +this.props.navigation.navigate('Chat', { user: 'Lucy' }); +``` + +The `user` param can be accessed from the profile screen: + +```js +class ChatScreen extends React.Component { + const { params } = this.props.navigation.state; + render() { + return Chat with {params.user}; + } +} +``` + +### Setting the Header Title + +Next, the header title can be configured to use the screen param: + +```js +class ChatScreen extends React.Component { + static navigationOptions = { + // // Title may be a simple string: + // title: 'Hello', + + // Or the title string may be a function of the navigation prop: + title: ({ navigation }) => `Chat with ${navigation.state.params.user}` + }; + ... +} +``` + +```phone-example +basic-header +``` + + +### Adding a Right Button + +Then we can add a [`header` navigation option](/docs/navigators/navigation-options#Stack-Navigation-Options) that allows us to add a custom right button: + +```js +static navigationOptions = { + header: { + right: + +); +``` + +Inside the infra: + +``` +class InfraScreen extends React.Component { + constructor() { + const {initURI, type} = this.props; + const ScreenView = ScreenRegistry[type].screen; + const router = ScreenView.router; + const deepLinkAction = router.getActionForURI(initURI); + const initAction = deepLinkAction || {type: 'init'} + const nav = router.getStateForAction(initAction); + this.state = { + nav, + }; + HybridNavigationModule.setNavOptions(this.state.nav); + } + componentWillUpdate() { + HybridNavigationModule.setNavOptions(this.state.nav); + } + dispatch = (action) => { + const {type} = this.props; + const ScreenView = ScreenRegistry[type].screen; + const {getStateForAction} = ScreenView.router; + const newNavState = getStateForAction(action, this.state.nav); + if (newNavState !== this.state.nav) { + this.setState({ nav: newNavState }); + return true; + } + if (action.type === 'URI') { + HybridNavigationModule.openURI(action.uri); + return true; + } + if (action.type === 'Back') { + HybridNavigationModule.goBack(); + return true; + } + HybridNavigationModule.openAction(action); + return true; + } + render() { + const {type} = this.props; + const ScreenView = ScreenRegistry[type].screen; + const navigation = { + dispatch: this.dispatch, + state: this.state.nav, + }; + return ; + } +} +``` + +## Setting title + +``` +MarketplaceScreen.router = { + getStateForAction(action, lastState) { + return lastState || {title: 'Marketplace Home'}; + }, +}; +``` +A HOC could be used to make this feel more elegant. + + +## Disabling/Enabling the right button + +``` +const TestScreen = ({ navigation }) => ( + + + Pressed {navigation.state} times + +); +TestScreen.router = { + getStateForAction(action, lastState = {}) { + let state = lastState || { + rightButtonEnabled: true, + rightButtonTitle: 'Tap Me', + pressCount: 0, + }; + if (action.type === 'ToggleMyButtonPressability') { + state = { + ...state, + rightButtonEnabled: !state.rightButtonEnabled, + }; + } else if (action.type === 'RightButtonPress') { + state = { + ...state, + pressCount: state.pressCount + 1, + }; + } + return state; + }, +}; +``` + + +## Before JS starts + +A JSON file could be defined for native to consume before JS spins up: + +``` +{ + "screens": [ + { + "type": "Profile", + "path": "/users/:id?name=:name", + "params": { + "name": "string", + "id": "number" + }, + "title": "%name%' s Profile", + "rightButtonTitle": "Message %name%" + }, + { + ... + } + ] +} +``` + +This seems like a pain to set up, so we can statically analyze our JS and autogenerate this JSON! If the JS in an app changes, there could be a way for JS to report the new routing configuration to native for use on the next cold start. diff --git a/docs/guides/Redux-Integration.md b/docs/guides/Redux-Integration.md new file mode 100644 index 0000000000..8b82f468e9 --- /dev/null +++ b/docs/guides/Redux-Integration.md @@ -0,0 +1,46 @@ +# Redux Integration + +To handle your app's navigation state in redux, you can pass your own `navigation` prop to a navigator. Your navigation prop must provide the current state, as well as access to a dispatcher to handle navigation options. + +With redux, your app's state is defined by a reducer. Each navigation router effectively has a reducer, called `getStateForAction`. The following is a minimal example of how you might use navigators within a redux application: + +``` +const AppNavigator = StackNavigator(AppRouteConfigs); + +const appReducer = combineReducers({ + nav: (state, action) => ( + AppNavigator.router.getStateForAction(action, state) + ), + ... +}); + +@connect(state => { + nav: state.nav, +}) +class AppWithNavigationState extends React.Component { + render() { + return ( + + ); + } +} + +const store = createStore(appReducer); + +class App extends React.Component { + render() { + return ( + + + + ); + } +} +``` + +Now, your navigation state is stored with redux, and you can fire navigation actions using redux. + +When a navigator is given a `navigation` prop, it relinquishes control of the state. So you are now responsible for persisting state, handling deep linking, integrating the back button, etc. \ No newline at end of file diff --git a/docs/guides/Screen-Nav-Options.md b/docs/guides/Screen-Nav-Options.md new file mode 100644 index 0000000000..6cf24e1f74 --- /dev/null +++ b/docs/guides/Screen-Nav-Options.md @@ -0,0 +1,57 @@ + +# Screen Navigation Options + +Each screen can configure several aspects about how it gets presented in parent navigators. + +#### Two Ways to specify each option + +**Static configuration:** Each navigation option can either be directly assigned: + +```js +class MyScreen extends React.Component { + static navigationOptions = { + title: 'Great', + }; + ... +``` + +**Dynamic Configuration** + +Or, each option can be a function that takes the following arguments, and returns the value of the option. + +- `navigation` - the [navigation prop](/docs/intro/navigation-prop) for the screen, with the screen's route at `navigation.state` +- `childRouter` - The child router, if the screen is a navigator + +```js +class ProfileScreen extends React.Component { + static navigationOptions = { + title: (navigation, childRouter) => { + return navigation.state.params.name + "'s Profile!"; + }, + }; + ... +``` + + +#### Generic Navigation Options + +The `title` navigation option is generic between every navigator. It is used to set the title string for a given screen. + +```js +class MyScreen extends React.Component { + static navigationOptions = { + title: 'Great', + }; + ... +``` + +Unlike the other nav options which are only utilized by the navigator view, the title option can be used by the environment to update the title in the browser window or app switcher. + + +## Tab Navigation Options + +Coming Soon + +## Stack Navigation Options + +Coming Soon diff --git a/docs/guides/Screen-Navigation-Prop.md b/docs/guides/Screen-Navigation-Prop.md new file mode 100644 index 0000000000..3472ad52d6 --- /dev/null +++ b/docs/guides/Screen-Navigation-Prop.md @@ -0,0 +1,142 @@ + +# Screen Navigation Prop + +Each screen in your app will recieve a navigation prop, which contains the following: + + +## `navigate` - Link to other screens + +Call this to link to another screen in your app. Takes the following arguments: + +- `routeName` - A destination routeName that has been registered somewhere in the app's router +- `params` - Params to merge into the destination route +- `action` - (advanced) The sub-action to run in the child router, if the screen is a navigator. + +```js +class HomeScreen extends React.Component { + render() { + const {navigate} = this.props.navigation; + + return ( + + This is the home screen of the app +