├── .gitignore ├── README.md ├── cover.png ├── package.json ├── packages ├── components │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── App.tsx │ │ ├── AppHeader │ │ │ ├── index.d.ts │ │ │ ├── index.native.tsx │ │ │ └── index.web.tsx │ │ ├── Link │ │ │ ├── Link.models.ts │ │ │ ├── index.d.ts │ │ │ ├── index.native.tsx │ │ │ └── index.web.tsx │ │ ├── Router │ │ │ ├── index.d.ts │ │ │ ├── index.native.tsx │ │ │ └── index.web.tsx │ │ ├── models │ │ │ └── router.ts │ │ ├── screens │ │ │ ├── About.tsx │ │ │ ├── CodeSharing.tsx │ │ │ ├── WebSupport.tsx │ │ │ └── styles.ts │ │ └── utils │ │ │ ├── navigation │ │ │ ├── index.d.ts │ │ │ ├── index.native.tsx │ │ │ └── index.web.tsx │ │ │ └── router │ │ │ ├── index.d.ts │ │ │ ├── index.native.ts │ │ │ └── index.web.ts │ └── tsconfig.json ├── mobile │ ├── .buckconfig │ ├── .eslintrc.js │ ├── .flowconfig │ ├── .gitattributes │ ├── .gitignore │ ├── .prettierrc.js │ ├── .watchmanconfig │ ├── __tests__ │ │ └── App-test.js │ ├── android │ │ ├── .project │ │ ├── .settings │ │ │ └── org.eclipse.buildship.core.prefs │ │ ├── app │ │ │ ├── BUCK │ │ │ ├── build.gradle │ │ │ ├── build_defs.bzl │ │ │ ├── debug.keystore │ │ │ ├── proguard-rules.pro │ │ │ └── src │ │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ │ └── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── myprojectname │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ └── MainApplication.java │ │ │ │ └── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ └── values │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ └── settings.gradle │ ├── app.json │ ├── babel.config.js │ ├── index.js │ ├── ios │ │ ├── Podfile │ │ ├── Podfile.lock │ │ ├── myprojectname-tvOS │ │ │ └── Info.plist │ │ ├── myprojectname-tvOSTests │ │ │ └── Info.plist │ │ ├── myprojectname.xcodeproj │ │ │ ├── project.pbxproj │ │ │ └── xcshareddata │ │ │ │ └── xcschemes │ │ │ │ ├── myprojectname-tvOS.xcscheme │ │ │ │ └── myprojectname.xcscheme │ │ ├── myprojectname.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ ├── myprojectname │ │ │ ├── AppDelegate.h │ │ │ ├── AppDelegate.m │ │ │ ├── Base.lproj │ │ │ │ └── LaunchScreen.xib │ │ │ ├── Images.xcassets │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Info.plist │ │ │ └── main.m │ │ └── myprojectnameTests │ │ │ ├── Info.plist │ │ │ └── myprojectnameTests.m │ ├── metro.config.js │ ├── package.json │ └── tsconfig.json └── web │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── config-overrides.js │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.css │ ├── index.html │ └── manifest.json │ ├── src │ ├── index.tsx │ └── react-app-env.d.ts │ ├── tsconfig.json │ └── yarn.lock ├── patches └── @react-native-community+cli-platform-ios+3.0.0.patch ├── tsconfig.base.json ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | *.jsbundle 2 | *.tsbuildinfo 3 | .DS_Store 4 | .history 5 | .jest 6 | .vscode 7 | node_modules 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Navigation on React Native [Web] 2 | 3 | This is the source code from [this blog post](https://dev.to/ythecombinator/driving-towards-a-universal-navigation-strategy-in-react-j60). 4 | 5 | ![cover](cover.png) 6 | 7 | ### Table of Contents 8 | 9 | - [How to run](#how-to-run) 10 | - [API](#api) 11 | - [Roadmap](#roadmap) 12 | 13 | ### How to run 14 | 15 | _Requirements: [React Native](https://facebook.github.io/react-native/docs/getting-started.html#native) (last tested on react-native@0.61)_ 16 | 17 | - `$ git clone git@github.com:ythecombinator/react-native-web-monorepo-navigation.git` 18 | - `$ cd react-native-web-monorepo-navigation` 19 | - `$ yarn` 20 | - `$ cd packages/mobile/ios` 21 | - `$ pod install` 22 | - `$ cd -` 23 | - `$ yarn workspace web start` 24 | - `$ yarn workspace mobile start` 25 | - Run the project 26 | - [iOS] Via Xcode 27 | - `yarn xcode` (open the project on Xcode) 28 | - Press the Run button 29 | - [Android] Via Android Studio 30 | - `yarn studio` (open the project on Android Studio) 31 | - Press the Run button 32 | - Via CLI 33 | - _You may need to launch your device emulator before the next command_ 34 | - `$ yarn android` or `$ yarn ios` 35 | 36 | ### [API](#api) 37 | 38 | #### `useNavigation()` 39 | 40 | `useNavigation` is a hook which gives access to the `navigation` object. It includes: 41 | 42 | - `navigate`: Go to another route 43 | - `replace`: Replace the current route with a new one 44 | - `goBack`: Close active route and move back in the stack 45 | 46 | #### `useRoute()` 47 | 48 | `useRoute` is a hook which gives access to the `route` object. It includes: 49 | 50 | - `name`: Name of the route. Defined in navigator component hierarchy 51 | - `params`: Set of params which is defined while navigating – e.g. `navigate('Twitter', { user: 'Dan Abramov' })` 52 | 53 | #### `` 54 | 55 | Provides declarative, accessible navigation around your application. It has the following props: 56 | 57 | - `path`: A string representing the path to link to 58 | - `params`: An object of key/value pairs of route parameters 59 | 60 | ### Roadmap 61 | 62 | #### API 63 | 64 | - [x] `useRoute` hook 65 | - [x] `useNavigation` hook 66 | - [x] `Link` componentt 67 | - [ ] `Redirect` component 68 | 69 | #### Miscellaneous 70 | 71 | - [ ] Move API to a library that can be installed as an npm package 72 | -------------------------------------------------------------------------------- /cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ythecombinator/react-native-web-monorepo-navigation/aac1894fa0f1e8ba6fc7b7eca9a835ed6906e04f/cover.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "myprojectname", 3 | "version": "0.0.1", 4 | "private": true, 5 | "workspaces": { 6 | "packages": [ 7 | "packages/*" 8 | ] 9 | }, 10 | "scripts": { 11 | "postinstall": "patch-package", 12 | "android": "yarn workspace mobile android", 13 | "compile": "tsc -b --incremental", 14 | "ios": "yarn workspace mobile ios", 15 | "studio": "yarn workspace mobile studio", 16 | "xcode": "yarn workspace mobile xcode" 17 | }, 18 | "dependencies": { 19 | "@react-native-community/masked-view": "^0.1.5", 20 | "@react-navigation/bottom-tabs": "^5.0.0-alpha.22", 21 | "@react-navigation/core": "^5.0.0-alpha.25", 22 | "@react-navigation/native": "^5.0.0-alpha.14", 23 | "@react-navigation/stack": "^5.0.0-alpha.37", 24 | "react-native": "0.61.3", 25 | "react-native-gesture-handler": "^1.5.2", 26 | "react-native-reanimated": "^1.4.0", 27 | "react-native-safe-area-context": "^0.6.1", 28 | "react-native-screens": "^2.0.0-alpha.22", 29 | "react-router-dom": "^5.1.2" 30 | }, 31 | "devDependencies": { 32 | "@types/react": "16.9.11", 33 | "@types/react-native": "0.60.22", 34 | "@types/react-router-dom": "^5.1.3", 35 | "concurrently": "5.0.0", 36 | "patch-package": "^6.2.0", 37 | "typescript": "3.6.4" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/components/.gitignore: -------------------------------------------------------------------------------- 1 | *.jsbundle 2 | *.tsbuildinfo 3 | .DS_Store 4 | .history 5 | .jest 6 | .vscode 7 | build 8 | coverage 9 | dist 10 | node_modules 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* -------------------------------------------------------------------------------- /packages/components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "components", 3 | "version": "0.0.1", 4 | "private": true, 5 | "dependencies": { 6 | "query-string": "^6.9.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/components/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { SafeAreaView, ScrollView, StatusBar, StyleSheet, Text, View } from 'react-native' 4 | 5 | import { AppHeader } from './AppHeader' 6 | import { Router } from './Router' 7 | 8 | export function App() { 9 | return ; 10 | } 11 | 12 | const styles = StyleSheet.create({ 13 | scrollView: { 14 | backgroundColor: "white" 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /packages/components/src/AppHeader/index.d.ts: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react' 2 | 3 | export const AppHeader: FunctionComponent; 4 | -------------------------------------------------------------------------------- /packages/components/src/AppHeader/index.native.tsx: -------------------------------------------------------------------------------- 1 | import { Header as AppHeader } from 'react-native/Libraries/NewAppScreen' 2 | 3 | export { AppHeader }; 4 | -------------------------------------------------------------------------------- /packages/components/src/AppHeader/index.web.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { StyleSheet, Text, View } from 'react-native' 4 | 5 | export function AppHeader() { 6 | return ( 7 | 8 | Welcome to React Native Web + Monorepo 9 | 10 | ); 11 | } 12 | 13 | const styles = StyleSheet.create({ 14 | container: { 15 | alignItems: "center", 16 | justifyContent: "center", 17 | width: "100%", 18 | height: 200 19 | }, 20 | text: { 21 | fontSize: 36, 22 | fontWeight: "600" 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /packages/components/src/Link/Link.models.ts: -------------------------------------------------------------------------------- 1 | import { ReactChild } from 'react' 2 | 3 | import { Route } from '../models/router' 4 | 5 | export interface LinkProps { 6 | children?: ReactChild; 7 | path: Route["path"]; 8 | params?: Route["params"]; 9 | } 10 | -------------------------------------------------------------------------------- /packages/components/src/Link/index.d.ts: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react' 2 | 3 | import { LinkProps } from './Link.models' 4 | 5 | export const Link: FunctionComponent 6 | -------------------------------------------------------------------------------- /packages/components/src/Link/index.native.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactChild, useCallback } from 'react' 2 | 3 | import { useNavigation } from '@react-navigation/core' 4 | import { TouchableOpacity } from 'react-native' 5 | 6 | import { LinkProps } from './Link.models' 7 | 8 | const Link = (props: LinkProps) => { 9 | const { path, params } = props; 10 | const navigation = useNavigation(); 11 | 12 | const navigate = useCallback(() => { 13 | navigation.navigate(path, params); 14 | }, [path, params]); 15 | 16 | return ( 17 | 18 | {props.children} 19 | 20 | ); 21 | }; 22 | 23 | export { Link }; 24 | -------------------------------------------------------------------------------- /packages/components/src/Link/index.web.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactChild } from 'react' 2 | 3 | import { stringify } from 'query-string' 4 | import { Link as DOMLink } from 'react-router-dom' 5 | 6 | import { LinkProps } from './Link.models' 7 | 8 | const Link = (props: LinkProps) => { 9 | const { path, params: qsParams = {} } = props; 10 | 11 | const params = `?${stringify(qsParams)}`; 12 | 13 | return ( 14 | {props.children} 15 | ); 16 | }; 17 | 18 | export { Link }; 19 | -------------------------------------------------------------------------------- /packages/components/src/Router/index.d.ts: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react' 2 | 3 | export const Router: FunctionComponent 4 | -------------------------------------------------------------------------------- /packages/components/src/Router/index.native.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, useState } from 'react' 2 | 3 | import { createBottomTabNavigator } from '@react-navigation/bottom-tabs' 4 | import { NavigationNativeContainer } from '@react-navigation/native' 5 | import { createStackNavigator } from '@react-navigation/stack' 6 | 7 | import { About as AboutScreen } from '../screens/About' 8 | // Screens 9 | import { CodeSharing as CodeSharingScreen } from '../screens/CodeSharing' 10 | import { WebSupport as WebSupportScreen } from '../screens/WebSupport' 11 | import { routes } from '../utils/router' 12 | 13 | // Screens 14 | const About = createStackNavigator(); 15 | const Features = createStackNavigator(); 16 | const Main = createBottomTabNavigator(); 17 | 18 | const AboutNavigator = () => { 19 | return ( 20 | 21 | 26 | 27 | ); 28 | }; 29 | 30 | const FeaturesNavigator = () => { 31 | return ( 32 | 33 | 38 | 43 | 44 | ); 45 | }; 46 | 47 | const MainNavigator = () => { 48 | return ( 49 | 50 | 51 | 56 | 61 | 62 | 63 | ); 64 | }; 65 | 66 | const Router = MainNavigator; 67 | 68 | export { Router }; 69 | -------------------------------------------------------------------------------- /packages/components/src/Router/index.web.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom' 4 | 5 | import { About as AboutScreen } from '../screens/About' 6 | import { CodeSharing as CodeSharingScreen } from '../screens/CodeSharing' 7 | import { WebSupport as WebSupportScreen } from '../screens/WebSupport' 8 | import { routes } from '../utils/router' 9 | 10 | // Screens 11 | const Router = () => { 12 | return ( 13 | 14 | 15 | {/* About routes */} 16 | 17 | 18 | 19 | {/* Feature routes */} 20 | 21 | 22 | 23 | 24 | 25 | 26 | {/* Default route */} 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | }; 34 | 35 | export { Router }; 36 | -------------------------------------------------------------------------------- /packages/components/src/models/router.ts: -------------------------------------------------------------------------------- 1 | export interface NavigationParams { 2 | [id: string]: T; 3 | } 4 | 5 | export type Navigate = (key: string, params?: NavigationParams) => {}; 6 | 7 | export interface Route { 8 | path: string; 9 | params?: NavigationParams; 10 | } 11 | 12 | export interface RouteConfig extends Pick { 13 | name: string; 14 | } 15 | -------------------------------------------------------------------------------- /packages/components/src/screens/About.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { SafeAreaView, ScrollView, StyleSheet, Text, View } from 'react-native' 4 | 5 | import { AppHeader } from '../AppHeader' 6 | import { styles } from './styles' 7 | 8 | export function About() { 9 | return ( 10 | <> 11 | 12 | 13 | 17 | 18 | 19 | About 20 | 21 | A universal navigation strategy for codebases containing both 22 | React and React Native developed by Matheus Albuquerque ( 23 | ythecombinator). 24 | 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /packages/components/src/screens/CodeSharing.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Button, SafeAreaView, ScrollView, Text, TouchableOpacity, View } from 'react-native' 4 | 5 | import { AppHeader } from '../AppHeader' 6 | import { Link } from '../Link' 7 | import { useNavigation, useRoute } from '../utils/navigation' 8 | import { routes } from '../utils/router' 9 | import { styles } from './styles' 10 | 11 | export function CodeSharing() { 12 | const { params } = useRoute(); 13 | const { goBack, navigate, replace } = useNavigation(); 14 | 15 | console.log(params); 16 | 17 | return ( 18 | <> 19 | 20 | 21 | 25 | 26 | 27 | 28 | Code sharing using Monorepo 29 | 30 | 31 | Edit{" "} 32 | 33 | packages/components/App.tsx 34 | 35 | to change this screen and then come back to see your edits (in 36 | the phone or the browser). 37 | 38 | 39 | {/* Navigate using */} 40 | 44 | 45 | Link to "Web support via react-native-web" 46 | 47 | 48 | 49 | {/* Navigate using navigate() */} 50 | 54 |