Skip to content

Commit

Permalink
Breaking: Replace containerOptions with just props (#1100)
Browse files Browse the repository at this point in the history
  • Loading branch information
grabbou authored and satya164 committed Apr 19, 2017
1 parent ba98f7a commit a252b46
Show file tree
Hide file tree
Showing 9 changed files with 166 additions and 151 deletions.
8 changes: 2 additions & 6 deletions docs/api/navigators/Navigators.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
16 changes: 7 additions & 9 deletions docs/guides/Deep-Linking.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 3 additions & 11 deletions src/TypeDefinition.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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: *,
};

/**
Expand Down
170 changes: 92 additions & 78 deletions src/createNavigationContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,102 +12,130 @@ 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.
* This allows to use e.g. the StackNavigator and TabNavigator as root-level
* 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,
};

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,
};
}
invariant(
typeof containerOptions === 'undefined',
'containerOptions.URIPrefix has been removed. Pass the uriPrefix prop to the navigator instead',
);

class NavigationContainer extends React.Component {
class NavigationContainer extends React.Component<void, Props<T>, State> {
state: State;
props: Props;
props: Props<T>;

subs: ?{
remove: () => void,
} = null;

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())
: null,
};
}

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) {
_urlToPathAndParams(url: string) {
const params = {};
const delimiter = this.props.uriPrefix || '://';
let path = url.split(delimiter)[1];
if (!path) {
path = url;
}
return {
path,
params,
};
}

_handleOpenURL = (url: string) => {
const parsedUrl = this._urlToPathAndParams(url);
if (parsedUrl) {
const { path, params } = parsedUrl;
const action = Component.router.getActionForPathAndParams(path, params);
if (action) {
this.dispatch(action);
}
}
};

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];
Expand All @@ -116,7 +144,6 @@ export default function createNavigationContainer<T: *>(
prevNavigationState !== navigationState
&& typeof this.props.onNavigationStateChange === 'function'
) {
// $FlowFixMe state is always defined, either this.state or props
this.props.onNavigationStateChange(prevNavigationState, navigationState);
}
}
Expand All @@ -126,25 +153,12 @@ export default function createNavigationContainer<T: *>(
this.subs && this.subs.remove();
}

_handleOpenURL = ({ url }: { url: string }) => {
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);
}
}
};

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: ');
Expand All @@ -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,
});
}
Expand Down
31 changes: 18 additions & 13 deletions src/navigators/DrawerNavigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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: {
Expand All @@ -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;
Loading

0 comments on commit a252b46

Please sign in to comment.