Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to call component methods on press of tab using tabBarOnPress #2725

Closed
sanjayholla94 opened this issue Oct 10, 2017 · 15 comments
Closed

Comments

@sanjayholla94
Copy link

Current Behavior

I'm trying to call component methods inside tabBarOnPress by setting it via navigation.setParams and the function call throws the following error the first time it is pressed: Cannot read property 'callServer' of undefined. This works from the second time the same tab is pressed.
This is because the navigation state params is set/updated after the first call to tabBarOnPress. How do I make the server request the first time a tab is pressed?

class TabScreen extends Component {
  static navigationOptions = ({navigation}) => {
    return {
      tabBarOnPress: (tab, jumpToIndex) => {
        if(!tab.focused){
          jumpToIndex(tab.index);
          navigation.state.params.callServer(); //this throws an error the first time each tab is pressed
        }
      },
    };
  }

  callServer = () => {
    console.log("Call server");
    //Code to make ajax call
  }

  componentDidMount(){
    this.props.navigation.setParams({callServer: this.callServer});
  }
}

Expected Behavior

Should be able to call components the first time a tab is pressed

Your Environment

software version
react-navigation 1.0.0-beta.13
react-native 0.47.1
node 6.10.3
npm or yarn npm v4.6.1
@PatNeedham
Copy link

Depending on where your TabScreen lives within your full app, you may want to utilize screen tracking to check which tab is currently active. That way, instead of declaring callServer within your TabScreen class, you can declare it in your top-most level app file and run it inside onNavigationStateChange. Say your TabScreen belongs to a TabNavigator which is the top-most level, then you can try this:

// gets the current screen from navigation state
function getCurrentRouteName(navigationState) {
  if (!navigationState) {
    return null;
  }
  const route = navigationState.routes[navigationState.index];
  // dive into nested navigators
  if (route.routes) {
    return getCurrentRouteName(route);
  }
  return route.routeName;
}

function callServer() {
    console.log("Call server");
    //Code to make ajax call
  }

const AppNavigator = TabNavigator({
  Tab1: { screen: TabScreen },
  ...
});

export default () => (
  <AppNavigator
    onNavigationStateChange={(prevState, currentState) => {
      const currentScreen = getCurrentRouteName(currentState);
      const prevScreen = getCurrentRouteName(prevState);

      if (prevScreen !== currentScreen && currentScreen === 'TabScreen') {
        callServer()
      }
    }}
  />
);

@sanjayholla94
Copy link
Author

I've already tried that. I'll give you an example of what I'm trying to achieve.
I've a dynamic number of tabs which are rendered by using an array of objects tabArrayList.
callServer fetches the data from the server and sets the state. This causes a re render of the tabs and the it doesn't load/select the pressed tab. Instead it defaults to the first tab.

That's why I was trying to use tabBarOnPress instead to achieve the same functionality.

Using your example:

// gets the current screen from navigation state
function getCurrentRouteName(navigationState) {
  if (!navigationState) {
    return null;
  }
  const route = navigationState.routes[navigationState.index];
  // dive into nested navigators
  if (route.routes) {
    return getCurrentRouteName(route);
  }
  return route.routeName;
}

function callServer() {
    console.log("Call server");
    //Code to make ajax call
    this.setState({data: response.data}); //this causes a re render of the tabs
}

function generateTabs(){
  //tabArrayList is a dynamically generated array of objects.
  //Each object contains title of tab and other information
  return tabArrayList.reduce((routes,index, tabArrayList) => {
    routes[index] = {
      screen: TabScreen,
      navigationOptions: {
        title: tabArrayList[index].title //sets the title for each tab
      }
    };
    return routes;
  },{});
  
}

const AppNavigator = TabNavigator(generateTabs());

export default () => (
  <AppNavigator
    onNavigationStateChange={(prevState, currentState) => {
      const currentScreen = getCurrentRouteName(currentState);
      const prevScreen = getCurrentRouteName(prevState);

      if (prevScreen !== currentScreen) {
        callServer(); //need to call server on press of every tab
      }
    }}
  />
);

@sanjayholla94
Copy link
Author

I was able to solve my problem but I'll keep this issue open as it's still not possible to call component methods inside tabBarOnPress.

@sanjayholla94 sanjayholla94 changed the title Unable to load server data on pressing a tab using tabBarOnPress Unable to call component methods on press of tab using tabBarOnPress Oct 17, 2017
@lucascarvalho
Copy link

@sanjayholla94 how did you solve your problem?

@jwrubel
Copy link

jwrubel commented Nov 18, 2017

Looks like this is already solved but FWIW a slightly different syntax from @sanjayholla94 did work fine for me:

  static navigationOptions = ({navigation}) => {
    return {
      tabBarOnPress: (tab, jumpToIndex) => {
        if(!tab.focused){
          jumpToIndex(tab.index);
          navigation.state.params.onFocus()
        }
      },
    }
  }

  componentDidMount() {
    this.props.navigation.setParams({
      onFocus: this.callServer.bind(this)
    })
  }

  callServer () {
    console.log('calling server')
    // your code here
  }

@virgilcui
Copy link

virgilcui commented Nov 24, 2017

Here works for me with redux.

  1. Let say you have the following in FileA. export the const store like below
    export const store = createStore(...);

  2. Assume you have an action (callAction) in FileB

  3. In your component class,
    import {store} from './FileA'
    import {callAction} from './FileB'

    export default class TabScreen extends Component{
    ...
    static navigationOptions = ({navigation}) => {
    return {
    tabBarOnPress: (tab, jumpToIndex) => {
    store.dispatch(callAction(tab.scene.index));
    tab.jumpToIndex(tab.scene.index)
    }
    }
    }
    }

  4. Then you can use mapStateToProps(state) and componentWillReceive(nextPros) to do whatever you need

Hope this helps!

@sanjayholla94
Copy link
Author

@lucascarvalho Hey, sorry for the late response! I was really busy with project deadlines over the past few weeks. I'll post an expo snack over the weekend and also check if it's still an issue.

@brentvatne
Copy link
Member

thanks for helping each other out here! i've made an issue on website repo, we should document this react-navigation/react-navigation.github.io#34

@MaheshNandam
Copy link

MaheshNandam commented Apr 25, 2018

Thanks to @jwrubel ,

I had changed few lines, then the below code is working for me.

static navigationOptions = ({ navigation }) => {
       return {
            tabBarOnPress: ({previousScene, scene, jumpToIndex}) => {
                const { route, index, focused} = scene;
                
                if(focused){
                    navigation.state.params.scrollToTop()
                }
                jumpToIndex(0)
            }
        }
 };

@jsina
Copy link

jsina commented Apr 28, 2018

@jwrubel how could I use your workaround when I integrating redux with navigation.?

@jwrubel
Copy link

jwrubel commented Apr 29, 2018

@jsina I'm not using the redux integration in this package but you should be able to invoke redux actions from your callback. Just a small-line change to my pseudocode above:

  static navigationOptions = ({navigation}) => {
    return {
      tabBarOnPress: (tab, jumpToIndex) => {
        if(!tab.focused){
          jumpToIndex(tab.index);
          navigation.state.params.onFocus()
        }
      },
    }
  }

  componentDidMount() {
    this.props.navigation.setParams({
      onFocus: this._onFocus.bind(this)
    })
  }

_onFocus = () => {
    // do something, and call navigation.dispatch() when you need it
  }

Is this what you're referring to?

@Liqiankun
Copy link

@jwrubel Not work for me! Some one has same issue?

@chai86
Copy link

chai86 commented Dec 23, 2018

Yes, same issue. In @jwrubel example, i tried to replace onFocus() with Foo(), and i get: Cannot read property 'Foo' of undefined

@jwrubel
Copy link

jwrubel commented Dec 23, 2018

My code above was written against version 1.0.0 of this library. There have been many changes to this library since this past August. It might not work against the current version of react-navigation. I haven't updated my library recently - sorry I cannot be of more help.

@chai86
Copy link

chai86 commented Dec 23, 2018

I am currently using version 1.1.147 (steering clear of the newer libraries). I'm trying to run my imagepicker function everytime the Tab is clicked. So everytime tabBarOnPress is in play, i want to launch my own function. Not sure how to do this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests