TabNavigator: detecting 'tabchanged' event

#1

Hi
I have created an Expo app based on tab template. I have all the tabs contents finished. Some tabs include flatList others just form objects, etc.

The question is: how can I detect that a tab was pressed and update the related tab content data?

Thanks a lot

Imo

#2

Hi, i don’t really understand your problem. Did you mean, how can you change screen when you press a tab ?

This is automatically managed by the TabNavigator in your MainTabNavigator.js file.
Add new screens to have new tabs in the TabNavigator:

TabNavigator({
     'home': {screen: HomeScreen},
     'myScreen': {screen: MyNewScreen}
});
#3

It is not that.
Imagine an app with 4 tabs, each tab has its associated screen.
When app in opened and when I tap each tab, constructor of each (selected) screen is called. But hen you tap a tab for the second time, the constructor of the associated screen is not called any more. I need to update related screen content data each time the associated tab is tapped

#4

Oh okay, so maybe you can use the tabBarOnPress option to detect when tab is pressed ? in you navigationOptions of you screen, do something like that :

static navigationOptions = ({ navigation }) => ({
     tabBarOnPress: ev => {
           // Your logic before jumping to the screen
           ev.jumpToIndex(ev.scene.index); // This actually allow you to jump to your screen 
     }
});

Here the docs of this option

1 Like
#5

Tell me if this solved your issue please :slight_smile:

#6
},
  {
    navigationOptions: () => ({
      headerTitleStyle: 
      {
        fontWeight: 'normal',
      },
      initialRouteName: "Access",


      tabBarOnPress: ev => {
            Alert.alert("Test","Tab selected");
           // Your logic before jumping to the screen
           ev.jumpToIndex(ev.scene.index); // This actually allow you to jump to your screen 
      }
    }),
  }

Alert is not shown…

#7

Where did you put this option ?

#8

You have to put it in your screen navigationOptions not in your TabNavigator options.

The TabNavigator navigationOptions should not be edited

export default TabNavigator(
  {
    Home: {
      screen: HomeScreen
    },
    Links: {
      screen: LinksScreen // Your screen
    },
    Settings: {
      screen: SettingsScreen
    }
  },
...

In your screen component screens/yourScreen.js

export default class LinksScreen extends React.Component {
  static navigationOptions = ({ navigation }) => ({
    title: "Links",
    tabBarOnPress: e => {
      Alert.alert("Test", "Tab selected"); // Here
      e.jumpToIndex(e.scene.index);
    }
  });
...
#9

Yes, I was setting the code on the TabNavigator navigationOptions. Sorry, this concept is completly new for me. But I like it!! :slight_smile:

Now the event is detected (I can see the alert) but I can not find a way to access the a class from static var navigationOptions…

Imagine I have a Refresh() function on the screen class, how to call it from the same place of the Alert call?

#10

Ok well, that is a little bit more tricky… If you meant to re-render the screen, you have to pass a params manually when you navigate to your screen. That will allows you to update your components state. See the example below.

in your root TabNavigator options : navigations/MainTabNavigator.js:

export default TabNavigator(
  {
    Home: {
      screen: HomeScreen
    },
    Links: {
      screen: LinksScreen,
      // Here, Add a navigationOption to this screen
      navigationOptions: ({ navigation }) => ({
        tabBarLabel: ({ tintColor }) => (
          <TouchableOpacity
            // Pass a random number or a new Date() as a param when navigate to the screen
            onPress={() => navigation.navigate("Links", { date: new Date() })}
            style={{ flex: 1, alignItems: "center", justifyContent: "center" }}
          >
            <Text>Links</Text>
          </TouchableOpacity>
        )
      })
    },
    Settings: {
      screen: SettingsScreen
    }
  },
  {
...

In your screen: screens/myScreen:

export default class LinksScreen extends React.Component {
  // Add your new random data or date to your state 
  state = {
    date: new Date()
  };
  static navigationOptions = ({ navigation }) => ({
    title: "Links",
   // Don't remove this part 
    tabBarOnPress: e => {
      e.jumpToIndex(e.scene.index);
    }
  });

  // Your component will receive new props in navigation.state.params 
  // so it will trigger this function
  componentWillReceiveProps() {
    console.log("rerender here");
    this.setState({ date: this.props.navigation.state.params.date });
  }
#11

It seems that there are some side effects:
-now tab label font is different from all the others. Is it possible to keep default font and colors?
-If i tap the bar text it successfully fires componentWillReceiveProps but if I tap tab icon it fires tabBarOnPress (static).

#12

yes because you’re now using a custom tabBarLabel you can get the tintColor see the example

So maybe you should remove the tabBarOnPress static :smile:

#13

I tried to remove tabBarOnPress but then only tapping the text works! I I tap tab icon nothing happens.

#14

@imo yes normal because in my example, my navigate actions is set in the tabBarLabel option. You can set the tabBarIcon option too.

#15

Or the best way to handle your action is to set the tabBarOnPress in your MainTabNavigator.js:

Here:

export default TabNavigator(
  {
    Home: {
      screen: HomeScreen
    },
    Links: {
      screen: LinksScreen,
      navigationOptions: ({ navigation }) => ({
        // Set the tabBarOnPress to keep the original UI and handle your onPress action
        tabBarOnPress: () => navigation.navigate("Links", { date: new Date() })
      })
    },
    Settings: {
      screen: SettingsScreen
    }
  },
  {
...

In your screen you should have only the title navigation option:

export default class LinksScreen extends React.Component {
  state = {
    date: new Date()
  };
  static navigationOptions = ({ navigation }) => ({
    title: "Links",
  });
...
1 Like
#16

Great! It works!

Thanks a lot

1 Like
#17

Another way to do it is to subscribe in your screen to a navigation event (willFocus, didFocus, willBlur or didBlur):

componentDidMount() {
this._sub = this.props.navigation.addListener(
    'willFocus',
    this.yourCallback
  );
}

componentWillUnmount() {
  this._sub.remove();
}

More details here: https://github.com/react-navigation/react-navigation/pull/3345

#18

Hi @gonzaloriestra,

Did you try your solution ?
I thought that tabs screens are not unmounted until the parent TabNavigator is not unmounted.

#19

Hi @hichamelbsi,

Yes, I tried it and it works.

All the screens are mounted at the beginning, when you load the TabNavigator (I think there was a lazy option, but is deprecated now). And yes, they keep mounted with the TabNavigator. So it will be listening to the events while the TabNavigator is on the screen.

#20

Hi

Have you tried to use the new withNavicationFocus Hoc?