From cf88560b4c47b131387dd05f28ae729868e4dee7 Mon Sep 17 00:00:00 2001 From: Mike Grabowski Date: Wed, 19 Apr 2017 10:40:50 +0200 Subject: [PATCH] Breaking: Replace `containerOptions` with just `props` (#1100) --- .../docs/api/navigators/Navigators.md | 8 +- .../docs/guides/Deep-Linking.md | 16 +- .../react-navigation/src/TypeDefinition.js | 14 +- .../src/createNavigationContainer.js | 180 ++++++++++-------- .../src/navigators/DrawerNavigator.js | 31 +-- .../src/navigators/StackNavigator.js | 36 ++-- .../src/navigators/TabNavigator.js | 35 ++-- .../src/navigators/createNavigator.js | 4 +- .../react-navigation/src/views/SceneView.js | 3 +- 9 files changed, 171 insertions(+), 156 deletions(-) diff --git a/packages/react-navigation/docs/api/navigators/Navigators.md b/packages/react-navigation/docs/api/navigators/Navigators.md index 5ed0351f..3ae197b6 100644 --- a/packages/react-navigation/docs/api/navigators/Navigators.md +++ b/packages/react-navigation/docs/api/navigators/Navigators.md @@ -52,10 +52,6 @@ For the purpose of convenience, the built-in navigators have this ability becaus Sometimes it is useful to know when navigation state managed by the top-level navigator changes. For this purpose, this function gets called every time with the previous state and the new state of the navigation. -### `containerOptions` +### `uriPrefix` -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. \ No newline at end of file +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. diff --git a/packages/react-navigation/docs/guides/Deep-Linking.md b/packages/react-navigation/docs/guides/Deep-Linking.md index 8e493281..286ca457 100644 --- a/packages/react-navigation/docs/guides/Deep-Linking.md +++ b/packages/react-navigation/docs/guides/Deep-Linking.md @@ -30,17 +30,15 @@ const SimpleApp = StackNavigator({ ### 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`: +Next, let's configure our navigation container to extract the path from the app's incoming URI. ```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://', - }, -}); +const SimpleApp = StackNavigator({...}); + +// on Android, the URI prefix typically contains a host in addition to scheme +const prefix = Platform.OS == 'android' ? 'mychat://mychat/' : 'mychat://'; + +const MainApp = () => ; ``` ## iOS diff --git a/packages/react-navigation/src/TypeDefinition.js b/packages/react-navigation/src/TypeDefinition.js index c0ba6585..782ea159 100644 --- a/packages/react-navigation/src/TypeDefinition.js +++ b/packages/react-navigation/src/TypeDefinition.js @@ -186,15 +186,6 @@ export type NavigationUriAction = { uri: string, }; -export type NavigationContainerOptions = { - // This is used to extract the path from the URI passed to the app for a deep link - URIPrefix?: string, -}; - -export type NavigationContainerConfig = { - containerOptions?: NavigationContainerOptions, -}; - export type NavigationStackViewConfig = { mode?: 'card' | 'modal', headerMode?: HeaderMode, @@ -301,9 +292,10 @@ export type NavigationScreenProp = { setParams: (newParams: NavigationParams) => boolean, }; -export type NavigationNavigatorProps = { - navigation: NavigationProp, +export type NavigationNavigatorProps = { + navigation: NavigationProp, screenProps: *, + navigationOptions: *, }; /** diff --git a/packages/react-navigation/src/createNavigationContainer.js b/packages/react-navigation/src/createNavigationContainer.js index 9b77cf00..38b6b7a8 100644 --- a/packages/react-navigation/src/createNavigationContainer.js +++ b/packages/react-navigation/src/createNavigationContainer.js @@ -12,12 +12,22 @@ import addNavigationHelpers from './addNavigationHelpers'; import type { NavigationRoute, NavigationAction, - NavigationContainerOptions, - NavigationProp, NavigationState, NavigationScreenProp, + NavigationNavigatorProps, } from './TypeDefinition'; +type NavigationContainerProps = { + uriPrefix?: string, + onNavigationStateChange?: (NavigationState, NavigationState) => void, +}; + +type Props = NavigationContainerProps & NavigationNavigatorProps; + +type State = { + nav: ?NavigationState, +}; + /** * Create an HOC that injects the navigation and manages the navigation state * in case it's not passed from above. @@ -25,35 +35,17 @@ import type { * components. */ export default function createNavigationContainer( - Component: ReactClass<*>, - containerConfig?: NavigationContainerOptions + Component: ReactClass>, + containerOptions?: {}, ) { - type Props = { - navigation: NavigationProp, - onNavigationStateChange?: (NavigationState, NavigationState) => void, - }; + invariant( + typeof containerOptions === 'undefined', + 'containerOptions.URIPrefix has been removed. Pass the uriPrefix prop to the navigator instead', + ); - type State = { - nav: ?NavigationState, - }; - - function urlToPathAndParams(url: string) { - const params = {}; - const URIPrefix = containerConfig && containerConfig.URIPrefix; - const delimiter = URIPrefix || '://'; - let path = url.split(delimiter)[1]; - if (!path) { - path = url; - } - return { - path, - params, - }; - } - - class NavigationContainer extends React.Component { + class NavigationContainer extends React.Component, State> { state: State; - props: Props; + props: Props; subs: ?{ remove: () => void, @@ -61,23 +53,11 @@ export default function createNavigationContainer( static router = Component.router; - _isStateful: () => boolean = () => { - const hasNavProp = !!this.props.navigation; - if (hasNavProp) { - invariant( - !containerConfig, - 'This navigator has a container config AND a navigation prop, so it is ' + - 'unclear if it should own its own state. Remove the containerConfig ' + - 'if the navigator should get its state from the navigation prop. If the ' + - 'navigator should maintain its own state, do not pass a navigation prop.' - ); - return false; - } - return true; - } - - constructor(props: Props) { + constructor(props: Props) { super(props); + + this._validateProps(props); + this.state = { nav: this._isStateful() ? Component.router.getStateForAction(NavigationActions.init()) @@ -85,50 +65,46 @@ export default function createNavigationContainer( }; } - componentDidMount() { + _isStateful(): boolean { + return !this.props.navigation; + } + + _validateProps(props: Props) { if (this._isStateful()) { - this.subs = BackAndroid.addEventListener('backPress', () => - this.dispatch(NavigationActions.back()) - ); - Linking.addEventListener('url', this._handleOpenURL); - Linking.getInitialURL().then((url: string) => { - if (url) { - console.log('Handling URL:', url); - const parsedUrl = urlToPathAndParams(url); - if (parsedUrl) { - const { path, params } = parsedUrl; - const action = Component.router.getActionForPathAndParams(path, params); - if (action) { - this.dispatch(action); - } - } - } - }); + return; } + + const { + navigation, screenProps, navigationOptions, onNavigationStateChange, + ...containerProps + } = props; + + const keys = Object.keys(containerProps); + + invariant( + keys.length === 0, + 'This navigator has both navigation and container props, so it is ' + + `unclear if it should own its own state. Remove props: "${keys.join(', ')}" ` + + 'if the navigator should get its state from the navigation prop. If the ' + + 'navigator should maintain its own state, do not pass a navigation prop.', + ); } - componentDidUpdate(prevProps: Props, prevState: State) { - const [prevNavigationState, navigationState] = this._isStateful() - ? [prevState.nav, this.state.nav] - : [prevProps.navigation.state, this.props.navigation.state]; - - if ( - prevNavigationState !== navigationState - && typeof this.props.onNavigationStateChange === 'function' - ) { - // $FlowFixMe state is always defined, either this.state or props - this.props.onNavigationStateChange(prevNavigationState, navigationState); + _urlToPathAndParams(url: string) { + const params = {}; + const delimiter = this.props.uriPrefix || '://'; + let path = url.split(delimiter)[1]; + if (!path) { + path = url; } + return { + path, + params, + }; } - componentWillUnmount() { - Linking.removeEventListener('url', this._handleOpenURL); - this.subs && this.subs.remove(); - } - - _handleOpenURL = ({ url }: { url: string }) => { - console.log('Handling URL:', url); - const parsedUrl = urlToPathAndParams(url); + _handleOpenURL = (url: string) => { + const parsedUrl = this._urlToPathAndParams(url); if (parsedUrl) { const { path, params } = parsedUrl; const action = Component.router.getActionForPathAndParams(path, params); @@ -138,13 +114,51 @@ export default function createNavigationContainer( } }; + componentWillReceiveProps(nextProps: *) { + this._validateProps(nextProps); + } + + componentDidMount() { + if (!this._isStateful()) { + return; + } + + this.subs = BackAndroid.addEventListener( + 'backPress', + () => this.dispatch(NavigationActions.back()), + ); + + Linking.addEventListener('url', ({ url }: { url: string }) => { + this._handleOpenURL(url); + }); + + Linking.getInitialURL().then((url: string) => url && this._handleOpenURL(url)); + } + + componentDidUpdate(prevProps: Props, prevState: State) { + const [prevNavigationState, navigationState] = this._isStateful() + ? [prevState.nav, this.state.nav] + : [prevProps.navigation.state, this.props.navigation.state]; + + if ( + prevNavigationState !== navigationState + && typeof this.props.onNavigationStateChange === 'function' + ) { + this.props.onNavigationStateChange(prevNavigationState, navigationState); + } + } + + componentWillUnmount() { + Linking.removeEventListener('url', this._handleOpenURL); + this.subs && this.subs.remove(); + } + dispatch = (action: NavigationAction) => { const { state } = this; if (!this._isStateful()) { return false; } const nav = Component.router.getStateForAction(action, state.nav); - if (nav && nav !== state.nav) { if (console.group) { console.group('Navigation Dispatch: '); @@ -168,7 +182,7 @@ export default function createNavigationContainer( if (this._isStateful()) { if (!this._navigation || this._navigation.state !== this.state.nav) { this._navigation = addNavigationHelpers({ - dispatch: this.dispatch.bind(this), + dispatch: this.dispatch, state: this.state.nav, }); } diff --git a/packages/react-navigation/src/navigators/DrawerNavigator.js b/packages/react-navigation/src/navigators/DrawerNavigator.js index b38bc920..e85bfa1b 100644 --- a/packages/react-navigation/src/navigators/DrawerNavigator.js +++ b/packages/react-navigation/src/navigators/DrawerNavigator.js @@ -15,13 +15,12 @@ import NavigatorTypes from './NavigatorTypes'; import type { DrawerViewConfig } from '../views/Drawer/DrawerView'; import type { - NavigationContainerConfig, NavigationRouteConfigMap, NavigationTabRouterConfig, } from '../TypeDefinition'; export type DrawerNavigatorConfig = - & NavigationContainerConfig + & { containerConfig?: void } & NavigationTabRouterConfig & DrawerViewConfig; @@ -48,11 +47,12 @@ const DrawerNavigator = ( drawerPosition, ...tabsConfig } = mergedConfig; + const contentRouter = TabRouter(routeConfigs, tabsConfig); const drawerRouter = TabRouter({ DrawerClose: { - screen: createNavigator(contentRouter, routeConfigs, config, NavigatorTypes.DRAWER)((props: *) => - + screen: createNavigator(contentRouter, routeConfigs, config, NavigatorTypes.DRAWER)( + (props: *) => , ), }, DrawerOpen: { @@ -61,15 +61,20 @@ const DrawerNavigator = ( }, { initialRouteName: 'DrawerClose', }); - return createNavigationContainer(createNavigator(drawerRouter, routeConfigs, config, NavigatorTypes.DRAWER)((props: *) => - - ), containerConfig); + + const navigator = createNavigator(drawerRouter, routeConfigs, config, NavigatorTypes.DRAWER)( + (props: *) => ( + + ), + ); + + return createNavigationContainer(navigator, containerConfig); }; export default DrawerNavigator; diff --git a/packages/react-navigation/src/navigators/StackNavigator.js b/packages/react-navigation/src/navigators/StackNavigator.js index a13602af..6a38334a 100644 --- a/packages/react-navigation/src/navigators/StackNavigator.js +++ b/packages/react-navigation/src/navigators/StackNavigator.js @@ -8,20 +8,21 @@ import StackRouter from '../routers/StackRouter'; import NavigatorTypes from './NavigatorTypes'; import type { - NavigationContainerConfig, NavigationStackRouterConfig, NavigationStackViewConfig, NavigationRouteConfigMap, } from '../TypeDefinition'; export type StackNavigatorConfig = - & NavigationContainerConfig + & { containerOptions?: void } & NavigationStackViewConfig & NavigationStackRouterConfig; -export default (routeConfigMap: NavigationRouteConfigMap, stackConfig: StackNavigatorConfig = {}) => { +export default ( + routeConfigMap: NavigationRouteConfigMap, + stackConfig: StackNavigatorConfig = {}, +) => { const { - containerOptions, initialRouteName, initialRouteParams, paths, @@ -39,16 +40,21 @@ export default (routeConfigMap: NavigationRouteConfigMap, stackConfig: StackNavi paths, navigationOptions, }; + const router = StackRouter(routeConfigMap, stackRouterConfig); - return createNavigationContainer(createNavigator(router, routeConfigMap, stackConfig, NavigatorTypes.STACK)(props => ( - - )), containerOptions); + const navigator = createNavigator(router, routeConfigMap, stackConfig, NavigatorTypes.STACK)( + (props: *) => ( + + ), + ); + + return createNavigationContainer(navigator, stackConfig.containerOptions); }; diff --git a/packages/react-navigation/src/navigators/TabNavigator.js b/packages/react-navigation/src/navigators/TabNavigator.js index a45188f9..a18d23fe 100644 --- a/packages/react-navigation/src/navigators/TabNavigator.js +++ b/packages/react-navigation/src/navigators/TabNavigator.js @@ -12,24 +12,22 @@ import NavigatorTypes from './NavigatorTypes'; import type { TabViewConfig } from '../views/TabView/TabView'; import type { - NavigationContainerConfig, NavigationRouteConfigMap, NavigationTabRouterConfig, } from '../TypeDefinition'; export type TabNavigatorConfig = + & { containerOptions?: void } & NavigationTabRouterConfig - & TabViewConfig - & NavigationContainerConfig; + & TabViewConfig; const TabNavigator = ( routeConfigs: NavigationRouteConfigMap, - config: TabNavigatorConfig = {} + config: TabNavigatorConfig = {}, ) => { // Use the look native to the platform by default const mergedConfig = { ...TabNavigator.Presets.Default, ...config }; const { - containerOptions, tabBarComponent, tabBarPosition, tabBarOptions, @@ -38,18 +36,23 @@ const TabNavigator = ( lazyLoad, ...tabsConfig } = mergedConfig; + const router = TabRouter(routeConfigs, tabsConfig); - return createNavigationContainer(createNavigator(router, routeConfigs, config, NavigatorTypes.STACK)((props: *) => - - ), containerOptions); + const navigator = createNavigator(router, routeConfigs, config, NavigatorTypes.STACK)( + (props: *) => ( + + ), + ); + + return createNavigationContainer(navigator, tabsConfig.containerOptions); }; const Presets = { diff --git a/packages/react-navigation/src/navigators/createNavigator.js b/packages/react-navigation/src/navigators/createNavigator.js index 5d3fe82f..3b173a93 100644 --- a/packages/react-navigation/src/navigators/createNavigator.js +++ b/packages/react-navigation/src/navigators/createNavigator.js @@ -11,7 +11,7 @@ import type { } from '../TypeDefinition'; import type { - NavigatorType + NavigatorType, } from './NavigatorTypes'; /** @@ -20,7 +20,7 @@ import type { const createNavigator = (router: NavigationRouter<*, *, *>, routeConfigs: NavigationRouteConfigMap, navigatorConfig: any, navigatorType: NavigatorType) => (View: NavigationNavigator<*, *, *, *>) => { class Navigator extends React.Component { - props: NavigationNavigatorProps; + props: NavigationNavigatorProps<*>; static router = router; diff --git a/packages/react-navigation/src/views/SceneView.js b/packages/react-navigation/src/views/SceneView.js index a8e16077..e15d6770 100644 --- a/packages/react-navigation/src/views/SceneView.js +++ b/packages/react-navigation/src/views/SceneView.js @@ -7,13 +7,14 @@ import type { NavigationScreenProp, NavigationRoute, NavigationAction, + NavigationNavigatorProps, } from '../TypeDefinition'; type Props = { screenProps?: {}; navigation: NavigationScreenProp; navigationOptions: *, - component: ReactClass<*>; + component: ReactClass>; }; export default class SceneView extends PureComponent {