Currently the second argument received by a navigation options item is the previous config returned from a parent navigation options, and the options passed to getScreenConfig
.
IMO it's a confusing behaviour, because fundamentally they are different types of objects, one is the configuration for navigator, other is arbitrary options the navigator can pass to the configuration function. For example, the tab navigator can provide a focused
argument for the icon, which doesn't make sense in a config.
The way this works is, only the global navigator config receives the options passed to the getScreenConfig
, and it has to explicitly merge it with, say, header config and return it. Which means if you have a static header configuration for screens, and then add a header configuration at the global level to be used as default, it breaks the screen configuration because it doesn't receive the options anymore and you've to explicitly merge the options in the global navigator config. Same for the route navigation config, which will break if you add a screen config.

Personally I think the confusing behaviour and the learning curve associated with it is not worth getting rid of one extra function.
global config - the navigationOptions
passed in the second argument to the navigator
screen config - the static navigationOptions
in the screens
route config - the navigationOptions
specified for individual routes in the first argument to the navigator
Flow types for both,
type NavigationOptionItem<T> =
| T
| (navigation: NavigationScreenProp<*,*>, T) => T
type IconOptions = {
focused: boolean;
tintColor: boolean;
}
These are the current api and the proposals we discussed:
Current API
Options is the result of previous header function/previous header object
header: (navigation, options) => ({
...options,
icon: ({ focused, tintColor }) => <Icon name={'settings' + focused ? '' : '-outline'} color={tintColor} />
});
Flow type,
type HeaderOptionItem = NavigationOptionItem<{
title: string;
icon: IconOptions => React.Element<*>;
}>
How it is used,
const { icon } = router.getScreenConfig(...);
return icon({ focused, tintColor });
Pros
router.getScreenConfig
doesn't deal with options passed to icon, so it's pretty generic, navigators can do whatever they want
- the icon function can be declared inline, so much simpler to use
- we can support both function and plain element, so no one needs to use a function if not needed
Cons
- Function inside object inside function, though it's function inside object if you don't need the outer function
Proposed API #1
Options is the result of previous header function/previous header object merged with params passed to getScreenConfig
header: (navigation, options) => ({
...options,
icon: <Icon name={'settings' + options.focused ? '' : '-outline'} color={options.tintColor} />
});
Flow type,
type HeaderOptionItemConfig = {
title: string;
icon: React.Element<*>;
}
// not sure if `HeaderOptionItemConfig & ?IconOptions` is valid, but wanted to denote that the merged object may or may not contain icon options even if the navigator always passes it
type HeaderOptionItem = NavigationOptionItem<HeaderOptionItemConfig & ?IconOptions>
How it is used,
return router.getScreenConfig(.., { focused, tintColor }).icon;
Pros
- Only two arguments to learn
Cons
- Automatically merging 2 different kinds of objects can be confusing, for example
focused
doesn't make sense inside navigationOptions.header
. For some things like tintColor
, it could be irrelevant if tintColor
might make sense in the config, but see next one for that
getScreenConfig
has to handle the options, limited to objects only
- What takes precedence?
navigationOptions
or the params passed to getScreenConfig
? Can I override the other?
Proposed API #2
Options is the result of previous header function/previous header object
header: (navigation, options) => ({
...options,
activeTintColor: 'orange',
inactiveTintColor: 'blue',
activeIcon: <Icon name='settings' color='blue' />,
inactiveIcon: <Icon name='settings-outline' color='orange' />
});
Flow type,
type HeaderOptionItem = NavigationOptionItem<{
title: string;
activeTintColor: string;
inactiveTintColor: string;
activeIcon: React.Element<*>;
inactiveIcon: React.Element<*>;
}>
How it is used,
return router.getScreenConfig(..).activeIcon;
return router.getScreenConfig(..).inactiveIcon;
Pros
router.getScreenConfig
doesn't deal with options passed to icon
Cons
- I can configure the colors of text with
activeTintColor
, but for the icon, I have to type it again, and I made a typo
- I have to write almost similar component twice, can make it a function to simpify, but then it's no better than current API where I can use a function, and even write it inline
- This assumes that the navigator doesn't need to pass options, and this assumption will likely break in future
Proposed API #3
Previous result is the result of previous header function/previous header object
header: (navigation, params, previousResult) => ({
...previousResult,
icon: <Icon name={'settings' + params.focused ? '' : '-outline'} color={params.tintColor} />
});
Flow type,
type NavigationOptionItem<T, U> =
| T
| (navigation: NavigationScreenProp<*,*>, T, U) => T
type HeaderOptionItem = NavigationOptionItem<{
title: string;
icon: React.Element<*>;
}, IconOptions>
How it is used,
return router.getScreenConfig(.., { focused, tintColor }).icon;
Pros
- No automatic merging between incompatible types
Cons
- 3 arguments to learn (should be an object most prolly)
getScreenConfig
has to handle the options, limited to objects only, e.g. - getScreenConfig(..., () => {})
is not possible (likely not a significant issue)
cc @skevy @mkonicek