Breaking: Replace containerOptions with just props (#1100)

This commit is contained in:
Mike Grabowski
2017-04-19 10:40:50 +02:00
parent 5a5905f592
commit cf88560b4c
9 changed files with 171 additions and 156 deletions

View File

@@ -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.
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.

View File

@@ -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 = () => <SimpleApp uriPrefix={prefix} />;
```
## iOS

View File

@@ -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<S, A> = {
setParams: (newParams: NavigationParams) => boolean,
};
export type NavigationNavigatorProps = {
navigation: NavigationProp<NavigationRoute, NavigationAction>,
export type NavigationNavigatorProps<T> = {
navigation: NavigationProp<T, NavigationAction>,
screenProps: *,
navigationOptions: *,
};
/**

View File

@@ -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<T> = NavigationContainerProps & NavigationNavigatorProps<T>;
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<T: *>(
Component: ReactClass<*>,
containerConfig?: NavigationContainerOptions
Component: ReactClass<NavigationNavigatorProps<T>>,
containerOptions?: {},
) {
type Props = {
navigation: NavigationProp<T, NavigationAction>,
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<void, Props<T>, State> {
state: State;
props: Props;
props: Props<T>;
subs: ?{
remove: () => void,
@@ -61,23 +53,11 @@ export default function createNavigationContainer<T: *>(
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<T>) {
super(props);
this._validateProps(props);
this.state = {
nav: this._isStateful()
? Component.router.getStateForAction(NavigationActions.init())
@@ -85,50 +65,46 @@ export default function createNavigationContainer<T: *>(
};
}
componentDidMount() {
_isStateful(): boolean {
return !this.props.navigation;
}
_validateProps(props: Props<T>) {
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<T: *>(
}
};
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<T>, 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<T: *>(
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,
});
}

View File

@@ -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: *) =>
<DrawerScreen {...props} />
screen: createNavigator(contentRouter, routeConfigs, config, NavigatorTypes.DRAWER)(
(props: *) => <DrawerScreen {...props} />,
),
},
DrawerOpen: {
@@ -61,15 +61,20 @@ const DrawerNavigator = (
}, {
initialRouteName: 'DrawerClose',
});
return createNavigationContainer(createNavigator(drawerRouter, routeConfigs, config, NavigatorTypes.DRAWER)((props: *) =>
<DrawerView
{...props}
drawerWidth={drawerWidth}
contentComponent={contentComponent}
contentOptions={contentOptions}
drawerPosition={drawerPosition}
/>
), containerConfig);
const navigator = createNavigator(drawerRouter, routeConfigs, config, NavigatorTypes.DRAWER)(
(props: *) => (
<DrawerView
{...props}
drawerWidth={drawerWidth}
contentComponent={contentComponent}
contentOptions={contentOptions}
drawerPosition={drawerPosition}
/>
),
);
return createNavigationContainer(navigator, containerConfig);
};
export default DrawerNavigator;

View File

@@ -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 => (
<CardStackTransitioner
{...props}
headerComponent={headerComponent}
headerMode={headerMode}
mode={mode}
cardStyle={cardStyle}
onTransitionStart={onTransitionStart}
onTransitionEnd={onTransitionEnd}
/>
)), containerOptions);
const navigator = createNavigator(router, routeConfigMap, stackConfig, NavigatorTypes.STACK)(
(props: *) => (
<CardStackTransitioner
{...props}
headerComponent={headerComponent}
headerMode={headerMode}
mode={mode}
cardStyle={cardStyle}
onTransitionStart={onTransitionStart}
onTransitionEnd={onTransitionEnd}
/>
),
);
return createNavigationContainer(navigator, stackConfig.containerOptions);
};

View File

@@ -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: *) =>
<TabView
{...props}
tabBarComponent={tabBarComponent}
tabBarPosition={tabBarPosition}
tabBarOptions={tabBarOptions}
swipeEnabled={swipeEnabled}
animationEnabled={animationEnabled}
lazyLoad={lazyLoad}
/>
), containerOptions);
const navigator = createNavigator(router, routeConfigs, config, NavigatorTypes.STACK)(
(props: *) => (
<TabView
{...props}
tabBarComponent={tabBarComponent}
tabBarPosition={tabBarPosition}
tabBarOptions={tabBarOptions}
swipeEnabled={swipeEnabled}
animationEnabled={animationEnabled}
lazyLoad={lazyLoad}
/>
),
);
return createNavigationContainer(navigator, tabsConfig.containerOptions);
};
const Presets = {

View File

@@ -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;

View File

@@ -7,13 +7,14 @@ import type {
NavigationScreenProp,
NavigationRoute,
NavigationAction,
NavigationNavigatorProps,
} from '../TypeDefinition';
type Props = {
screenProps?: {};
navigation: NavigationScreenProp<NavigationRoute, NavigationAction>;
navigationOptions: *,
component: ReactClass<*>;
component: ReactClass<NavigationNavigatorProps<NavigationRoute>>;
};
export default class SceneView extends PureComponent<void, Props, void> {