react navigation v3 set screenProps through AppContainer - reactjs

I need to pass theme settings to navigation options of each screen. Settings are stored in redux. In old version I would write something like that:
const AppNavigator = StackNavigator({
Home: { screen: MainList },
Settings: { screen: Settings },
NewEvent: { screen: NewEvent },
Contacts: { screen: Contacts },
Map: { screen: Map},
})
And in render function...
<AppNavigator
screenProps={{
settings: this.props.settings,
headerTintColor: '#ffffff'
}}
/>
...where props.settings is a part of redux state. Which is used inside navigationOptions
static navigationOptions = ({ navigation, screenProps }) => {
let settings = screenProps.settings;
let theme = settings? settings.theme: null;
return {
title: strings.account,
headerTintColor: screenProps.headerTintColor,
headerStyle: {backgroundColor: theme? theme.def.primaryColor: null}
};
};
But how should I do this now when I must wrap my Navigators with createAppContainer? Using navigation.setParams makes a visible delay in emulator so its not a proper solution for me... Keep in mind theme can be changed at any time!

Well) Turnsout you can achive that by creating a custom navigator like this:
class NavWrapper extends Component {
static router = AppNavigator.router;
render() {
const { navigation } = this.props;
return <AppNavigator
navigation={navigation}
screenProps={{
settings: this.props.settings,
headerTintColor: '#ffffff'
}}
/>;
}
}
const AppNavWrapper = connect(state => ({settings: state.settings}))(NavWrapper);
const AppContainer = createAppContainer(AppNavWrapper);
And then in your root component
render() {
return (
<AppContainer/>
);
}
Hope that helps :)

Related

Where should propTypes for navigationOptions inside createBottomTabNavigator be defined?

I just applied airbnb, prettier, react/prettier and all the linting. I still cannot get around this error[1] because I do not understand correctly where should propTypes should be declared for "inner functions" as this one.
I am not giving those parameters, nor am I defining them. They come from createBottomTabNavigator as I can read in the doc. So, how should I have a say in what props are required for tabBarIcon and which are not in the destructuring of these?[2]
UPDATE
[1] The error is a linting error. The code executes just fine.
[2] We can of course fiddle around a bit to get it working and avoid the errors but my aim is to understand how it works and why is it giving the errors back, being that snippet the official example.
export const HomeStackNavigator = createStackNavigator(
...
);
export const TabNavigator = createBottomTabNavigator(
{
Home: {
screen: HomeStackNavigator,
},
Settings: {
screen: SettingsStackNavigator,
},
},
{
initialRouteName: 'Home',
defaultNavigationOptions: ({ navigation }) => ({
tabBarIcon: ({ focused, horizontal, tintColor }) => {
// Getting error here ^^^^^^^^^^^^^^^^^^^^^^^^
// 'focused' is missing in props validationeslint(react/prop-types)
const { routeName } = navigation.state;
let IconComponent = Ionicons;
let iconName;
if (routeName === 'Home') {
iconName = 'ios-home';
IconComponent = HomeIconWithBadge;
} else if (routeName === 'Settings') {
iconName = 'ios-cog';
}
return (
<IconComponent //
name={iconName}
color={tintColor}
size={25}
/>
);
},
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}),
tabBarOptions: {
// activeTintColor: 'tomato',
keyboardHidesTabBar: true,
},
}
);
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
appState: AppState.currentState,
isStoreLoading: false,
store: createStore(rootReducer)
};
}
...
componentDidMount(){...}
...
render() {
const { store, isStoreLoading } = this.state;
if (isStoreLoading) {
return <Text>Loading...</Text>;
}
return (
<Provider store={store}>
<AppContainer />
</Provider>
);
}
}
If you really want to define prop-types for an inner function like this, you need to move it outside of the navigator.
const MyTabBarIcon = ({ focused, horizontal, tintColor, navigation }) => {
// [...]
}
MyTabBarIcon.propTypes = {
focused: PropTypes.bool.isRequired,
tintColor: PropTypes.string.isRequired,
navigation: PropTypes.object.isRequired,
horizontal: PropTypes.bool,
}
Then your TabNavigator becomes:
// [...]
defaultNavigationOptions: ({ navigation }) => ({
tabBarIcon: props => <MyTabBarIcon {...props} navigation={navigation} />,
// [...]
});
// [...]

Passing a parameter from a screen in SwitchNavigator to another in a TabNavigator

I have been having some problems passing parameters from a screen in a switchNavigator to another in a TabNavigator
setvalue(response){
this.setState({profile :response})
console.warn(this.state.profile);
this.state.navigate('Navigators',{profile: profile})
}
The profile contains a JSON object of profile details. The navigation sends the date to the 'Navigators' screen which is just a TabNavigator
const Navigators = createAppContainer(Userstack);
export default RootStack = createSwitchNavigator(
{
Home: {
screen: Login
},
Register: {
screen: Registration
},
Navigators: {
screen: Navigators
},
},
{
initialRouteName: 'Home'
}
);
How the TabNavigator is created.
export default Userstack = createBottomTabNavigator(
{
User: {
screen: Profile
},
Discovery: {
screen: DiscoveryNavigator
},
},
{
defaultNavigationOptions: ({ navigation }) => ({
tabBarIcon: ({ tintColor }) => {
const { routeName } = navigation.state;
let IconComponent = Ionicons;
let iconName;
if (routeName === 'User') {
iconName = `md-contact`;
IconComponent = HomeIconWithBadge;
} else if (routeName === 'Discovery') {
iconName = `md-search`;
}
return <IconComponent name={iconName} size={27} color={tintColor} />;
},
}),
tabBarOptions: {
activeTintColor: '#00FA9A',
inactiveTintColor: 'black',
},
}
);
The screen I wish to access the profile information is
export default class Profile extends Component {
constructor(props){
super(props);
console.warn(props)
this.State = {
profile: this.props.navigation.params.profile
}
}
Did you try this.props.navigation.state.params?
According to Reference: https://reactnavigation.org/docs/params.html,
You can also directly access the params object with this.props.navigation.state.params. This may be null if no params were supplied, and so it's usually easier to just use getParam so you don't have to deal with that case.

double header stack navigation react-native

I am using a StackNavigator in react native app.
The problem is that in my app, it creates two headers ...
I would like to keep the upper one to go back from the screen. Is it possible without recreate the back button manually ?
Screen:
class CommandsList extends React.Component {
constructor(props){
super(props);
}
addCommand = () => {
this.props.navigation.navigate("CreateCommand");
}
render() {
return (
<SafeAreaView style={{flex:1}}>
<MyList itemsUrl="http://localhost:9000/commands"/>
<Button title="Ajouter" onPress={this.addCommand}></Button>
</SafeAreaView>
);
}
}
export default StackNavigator({
CommandsList : {
screen : CommandsList,
},
});
EDIT :
App.js
const navigationOptions = ({ navigation }) => ({headerLeft: <Icon name {'chevron-left'} onPress={ () => { navigation.goBack() }} />})
const RootStack = StackNavigator(
{
CommandsList: {
screen: CommandsList,
},
CreateCommand: {
screen: CreateCommand,
},
ListFournisseurs: {
screen: ListFournisseurs,
},
ListAffaires: {
screen: ListAffaires,
}
},
{
initialRouteName: 'CommandsList',
headerMode:'none',
navigationOptions:{navigationOptions}
}
);
According to the docs, if you want to disable the header of the StackNavigator, you can apply the config at your StackNavigatorConfig as headerMode: 'none'. It is back propagated from Child to Parent, if Parent is none then Child will also not be rendered.
Therefore for a single header, in your case you should do
export default StackNavigator({
CommandsList : {
screen : CommandsList,
},
}, {
headerMode: 'none'
});
For the back button in the Parent Stack, you can create a component as
const navigationOptions = ({ navigation }) => ({
headerLeft: <Icon name={'arrow-left'} //<== Vector Icon Here
onPress={ () => { navigation.goBack() }} />
const RootStack = StackNavigator(
RouteConfigs,
//... StackNavigatorConfigs,
navigationOptions
)

States in TabStackNavigator?

It seems like TabNavigator doesn't have it's own state. Is there any way to use state or props?
I want to show the number of unread notification on the Notification TabIcon.
export default TabNavigator(
{
...
Noti: {
screen: NotificationStackNavigator,
},
...
},
{
navigationOptions: ({ navigation }) => ({
header: null,
tabBarIcon: ({ focused }) => {
const { routeName } = navigation.state;
let iconName;
switch (routeName) {
...
case 'Noti':
iconName = 'ios-notifications';
break;
...
}
...
if(iconName == 'ios-notifications') {
return (
<IconBadge
MainElement={
<Ionicons
name={iconName}
size={30}
style={{ marginBottom: -3 }}
color={focused ? Colors.tabIconSelected : Colors.tabIconDefault}/>
}
BadgeElement={
<Text style={{color:'#FFFFFF'}}>
{{this.state.notifications}} // my goal
</Text>
}
IconBadgeStyle={{
backgroundColor: '#f64e59',
position: 'absolute',
right:-10,
top:0,
}}
/>
);
}
...
Please let me know if anything is unclear. Thanks in advance
UPDATE I'm planning to refactor my TabNavigator. Is this what you're trying to say?
export default TabNavigator(
to
const MainTabNavigator = TabNavigator(
class MainTabNavigator extends Component {
state = {notification}
export default connect(...)(MainTabNavigator);
UPDATE 2 MainTabNavigator's Top Level component is another TabNavigator. Is there any other solution?
const RootTabNavigator = TabNavigator ({
Auth: {
screen: AuthStackNavigator,
},
Welcome: {
screen: WelcomeScreen,
},
Main: {
screen: MainTabNavigator,
},
You can pass additional custom props via screenprops
const TabNav = TabNavigator({
// config
});
<TabNav
screenProps={/* this prop will get passed to the screen components as this.props.screenProps */}
/>
The full documentation is here

How to get title of screen in custom header component?

Hope it's ok.
I Can't understand how custom header it's supposed to work.
I just want to send static properties to my Header depending on which route is selected.
I have:
export default AppNavigator = StackNavigator({
Index:{
screen: BottomNavigator,
}
},{
navigationOptions: {
header: AppHeader,
},
headerMode:'float',
})
And my BottomNavigator is:
const BottomNavigator = TabNavigator({
TabMenu1: {
screen: () => <Text> Resumé </Text>,
navigationOptions: {
title: 'Resumé'
}
},
TabMenu2: {
screen: () => <Text> Sells </Text>,
navigationOptions: {
title: 'Sells'
}
},
},{
tabBarComponent: BottomNavigation
});
I'm expecting {props.title} in my custom header, is that right?
Additional info: My full route stack is:
AuthNavigator have a wrapper that is connected to redux and have:
const AuthNavigator = StackNavigator({
SignedIn: {
screen: MainNavigator
}
},{
headerMode:'none',
initialRouteName: 'SignedIn'
});
MainNavigator:
const MainNavigator = StackNavigator({
Drawer: {
screen: DrawerNav
},
}, {
headerMode:'none',
});
DrawerNav:
const DrawerNav = DrawerNavigator({
Menu1: {
screen: AppNavigator
},
}, {
contentComponent: DrawerNavigation,
drawerWidth: 300
});
AppNavigator and BottomNavigator are described above
for React Navigation 5 this works
props.scene.descriptor.options.title
You can specify title like this :
RouteName:
{
screen: RouteScreen,
navigationOptions: ({navigation}) => ({
title: navigation.state.params.title || 'StaticTitle'
})
}
Where navigation.state.params.title is the title you send as a params when you navigate (so if you want to add a variable for exemple). If you just want to use a static title, just use title: 'StaticTitle'
export const Navigator = StackNavigator(routes, {
headerMode: 'screen',
navigationOptions: ({ navigation }) => ({
header: props => <Header {...props} />,
}),
})
I did find a work around but it seems pretty verbose for what seems to be a common use case..
// Header.js
...
render() {
const { getScreenDetails, scene } = this.props
const details = getScreenDetails(scene)
return (
<View>
<Text>{details.options.title}</Text>
<View>
)
Resolved with a code extracted of Header.js file of react-navigation:
The right way to get the title inside a custom header is:
const sceneOptions = props.getScreenDetails(props.scene).options;
const sceneTitle = sceneOptions.title;
Thanks all!

Resources