TabNavigator: detecting 'tabchanged' event

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

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}
});

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

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
https://reactnavigation.org/docs/tab-navigator.html#tabbaronpress

1 Like

Tell me if this solved your issue please :slight_smile:

},
  {
    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…

Where did you put this option ?

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);
    }
  });
...

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?

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 });
  }

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

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:

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

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

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

Great! It works!

Thanks a lot

1 Like

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

Hi @gonzaloriestra,

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

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.

Hi

Have you tried to use the new withNavicationFocus Hoc?