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

Ability to define a custom onPress for a tab #314

Closed
alphasp opened this issue Feb 13, 2017 · 51 comments · Fixed by #1335
Closed

Ability to define a custom onPress for a tab #314

alphasp opened this issue Feb 13, 2017 · 51 comments · Fixed by #1335
Labels

Comments

@alphasp
Copy link

alphasp commented Feb 13, 2017

=== by @grabbou

This is a catch-all issue for TabBar onPress, that is:

  • ability to define a custom press handler
  • press tab to reset active stack

=== original issue below ===

Given following nested tab setup

  MainTab
    --> Home
       --> Profile
          --> Related
  SettingTab
    --> ...

How to back/reset to Home screen from Related screen on press MainTab icon in tabs when MainTab is focused?

const MainTab = StackNavigator({
  Home: {
    screen: MyHomeScreen,
  },
  Profile: {
    screen: MyProfileScreen,
  },
  Related: {
    screen: MyRelatedScreen,
  }
});

const StacksInTabs = TabNavigator({
  MainTab: {
    screen: MainTab,
  },
  SettingsTab: {
    screen: SettingsTab,
  },
});

@grabbou
Copy link

grabbou commented Feb 13, 2017

CC: @satya164

@satya164
Copy link
Member

satya164 commented Feb 13, 2017

Just do navigation.goBack() or navigation.dispatch as you would usually do in a stack navigator to go to the home screen.

@satya164 satya164 reopened this Feb 13, 2017
@satya164
Copy link
Member

Ah I see you mean when pressing tab icon. We should allow adding a listener to tab press so you can perform custom actions. cc @ericvicenti

@makirby
Copy link

makirby commented Feb 13, 2017

I was also wondering this, is there no way of listening to the actions from the TabNavigator button press without hooking onto the actions they produce?

@ericvicenti
Copy link
Contributor

I agree, a tab button handler override sounds like a reasonable thing to add, maybe call it navigationOptions.tab.pressHandler or something.

@satya164
Copy link
Member

@ericvicenti it won't be very useful inside the static navigationOptions IMO. You can only dispatch navigation actions there. If you want any change anything else inside the component, it'll be a tedious process of passing it through params.

@ericvicenti
Copy link
Contributor

Ok, so you want it to have access to the tab navigator's props? What API would you recommend for this?

Usually I would just fire a different action other than navigate, so I think the dispatch channel would be fine for me, (plus I may want to do side effects).

@satya164
Copy link
Member

I think @browniefed wanted access to tabview's onPositionChange too, which is called every time the animation value is changed. We can't pass props to since it's a parent component, so I guess we need to have a way to attach listeners to the navigator.

plus I may want to do side effects

yeah, if you are not using redux and say just want to fetch some data, or component state, or say scroll to top, you need to be able to listen to press event from inside the component.

@ericvicenti
Copy link
Contributor

FWIW, I think the best practice for onPositionChange is not to call a callback tons of times but instead to pass around an animated value that represents the position

@browniefed
Copy link

I don't disagree but I'd like to potentially do logic to determine if I need to trigger Animated.setValue, etc. Hence a callback would be preferred.

@jankcat
Copy link

jankcat commented Feb 17, 2017

I am fairly new to this new (and so far awesome) nav project so take my words with a grain of salt, but the basic behaviour described by OP is definitely a common behaviour among production apps currently in the market. Facebook, Instagram, Reddit (official app), Twitter, and AirBNB are just a few popular examples of this behaviour.

The common "tap flow" between these apps, when you are currently on the tab you are tapping:

  • Tapping the tab when you are not on the root view of the tab: Pop to root view
  • Tapping the tab when you are on the root view of the tab: Scroll to beginning (usually the top) of the root view, if it is a scrollview.

Just some thoughts to keep in mind when architecting a solution :)

@ayalpani
Copy link

I need this too! Any ideas for a workaround?

@ayalpani
Copy link

Dear @satya164

will this one be approached any time soon? Otherwise I may be forced to build my own TabNavigator. And I'd really prefer not to :) Thanks!

@andrew-d-jackson
Copy link

I'm running into this problem now, it would be really helpful if the tabBarComponent took navigator as a prop so I can dispatch actions.

@satya164
Copy link
Member

it would be really helpful if the tabBarComponent took navigator as a prop so I can dispatch actions

I'm pretty sure it gets navigation as a prop right now

@andrew-d-jackson
Copy link

@satya164

I pretty sure it doesn't unless I am not understanding something. If I print the props my tabBarComponent gets I get:

["layout", "navigationState", "position", "jumpToIndex", "getLastPosition", "subscribe", "getLabelText", "renderIcon", "animationEnabled"]

Code I used to get that list: http://pastebin.com/EV9gdjws

@satya164
Copy link
Member

Don't know what version you're on, but we pass the prop https://github.com/react-community/react-navigation/blob/master/src/views/TabView/TabView.js#L136

So maybe try master and see

@andrew-d-jackson
Copy link

Ah, you are right, it is passed in on master but not 1.0.0-beta.5. Thanks a lot.

@arnsa
Copy link
Contributor

arnsa commented Mar 2, 2017

@satya164 when is this going to be implemented?

@maxmcd
Copy link

maxmcd commented Mar 24, 2017

This is the workaround I use. Feedback welcome.

const MainTab = StackNavigator({
  Home: {
    screen: MyHomeScreen,
  },
  Profile: {
    screen: MyProfileScreen,
  },
  Related: {
    screen: MyRelatedScreen,
  },
});

let currentIndex;

const StacksInTabs = TabNavigator(
  {
    MainTab: {
      screen: MainTab,
    },
    SettingsTab: {
      screen: SettingsTab,
    },
  },
  {
    tabBarComponent: ({ jumpToIndex, ...props }) => (
      <TabView.TabBarBottom
        {...props}
        jumpToIndex={index => {
          if (currentIndex === index && index === 0) {
            let resetTabAction = NavigationActions.navigate({
              routeName: "MainTab",
              action: NavigationActions.reset({
                index: 0,
                actions: [NavigationActions.navigate({ routeName: "Home" })],
              }),
            });
            props.navigation.dispatch(resetTabAction);
          } else {
            currentIndex = index;
            jumpToIndex(index);
          }
        }}
      />
    ),
  }
);

@jamesisaac jamesisaac mentioned this issue Apr 21, 2017
9 tasks
@jamesisaac
Copy link

@maxmcd Nice workaround, although gets caught out if you use navigate actions to change tabs programatically (currentIndex won't get updated). Had to add a function called every time tab is changed programatically to update the index...

Would be great to have real support for this built in, where we can just access a focused property directly.

@grabbou grabbou changed the title Question: How to back to first screen in nested tab on press tab icon Ability to define a custom onPress for a tab Apr 24, 2017
@danstepanov
Copy link

Any update on when this will be available?

@mleduque
Copy link

Would it be possible to have the current understanding on this problem? I tried to read the comments but it seems to be a succession of false leads (or incomplete...).
I'm actually not interested on the "ability to define a custom onPress for a tab" because I don't use a TabNavigaor but DrawerNavigator (the fact that DrawerNavigator is based on TabNavigator is an implementation detail), and I don't want onPress, I just want to correctly reset StackNavigators nested inside a DrawerNavigator.
So, what's the (current) best way to attain that goal?

@mleduque
Copy link

No?

@jitinmaher
Copy link

Is this a right way to capture the on press event.

const TestNavScreen = TabNavigator({
  Test: {
    screen: TestScreen,
    navigationOptions: {
      tabBarIcon: ({ tintColor }) => (
        <TouchableWithoutFeedback onPress={() => { console.log('Got IT!'); }}>
          <Image
            source={require('../../assets/img/test.png')}
            style={[styles.icon, { tintColor }]}
          />
        </TouchableWithoutFeedback>
        ),
    }
  },
  Notifications: {
    screen: NotificationsScreen,
  },
});

This worked for me !

@cooperka
Copy link

@jitinmaher I believe that will only put an onPress handler on a very small portion of the tab (just the icon itself), and it will likely also steal the tap event and prevent changing the tab. That doesn't seem like a good solution. You can check out #1335 for a working PR.

@harbi
Copy link

harbi commented Jul 16, 2017

Any update on this?

@tobiassern
Copy link

tobiassern commented Jul 24, 2017

I experimented a little with this and came up with this pretty clean solution. The tab bar button will function as the back button when it is the active tab.

It requires that you also imports the following:
import { TabBarBottom, NavigationActions } from 'react-navigation';

const tabBarConfiguration = {
    tabBarComponent: ({ jumpToIndex, ...props }) => (
        <TabBarBottom
            {...props}
            jumpToIndex={(index) => {
                    if(props.navigation.state.index === index) {
                        props.navigation.dispatch(NavigationActions.back());
                    } else {
                        jumpToIndex(index);
                    }
                }
            }
        />
    ),
}

@cooperka
Copy link

@tobiassernhede that's a neat trick, but there's already a real working solution: #1335

@dmr07
Copy link

dmr07 commented Jul 25, 2017

@cooperka This issue should be closed.

@cooperka
Copy link

Hi @dmr07, unfortunately I'm not a maintainer so I don't have permission to close this issue.

computerjazz referenced this issue in async-la/ttn-mobile Jul 28, 2017
If we are deep into a navigation stack and want to jump back to the home
screen, there is currently no way to do that except by pressing back
several times.

This change listens for presses on the currently active tab and will
reset the stack back to the home route.
@dzpt
Copy link

dzpt commented Aug 8, 2017

@maxmcd you saved my life today 👍

@spencercarli
Copy link
Member

Hi! In an effort to get the hundreds of issues on this repo under control I'm closing issues tagged as questions. Please don't take this personally - it's simply something we need to do to get the issues to a manageable point. If you still have a question I encourage you to re-read the docs, ask on StackOverflow, or ask on the #react-navigation Reactiflux channel. Thank you!

@opera9900
Copy link

TabNavigator({
    ****
}, {
    tabBarComponent: ({jumpToIndex, ...props}) => (
        <TabBarBottom
            {...props}
            getOnPress={() => {
                return (scene) => {
                    const {route, index, focused} = scene;
                    if (index !== 3 /* ~ ~ */) {
                        jumpToIndex(index);
                    } else {
                        // HANDLE IT !!
                    }
                };
            }}
        />
    ),
    tabBarPosition: 'bottom'
})

Worked for me!!

@pensierinmusica
Copy link

@spencercarli can you please tag this issue as feature-request and reopen it? Thanks!

@cooperka
Copy link

cooperka commented Oct 2, 2017

Hi @pensierinmusica, this was already solved in #1335. The feature request has been accepted.

@pensierinmusica
Copy link

@cooperka oops I missed that, thx, awesome then! :)

@vlimag
Copy link

vlimag commented Jan 5, 2018

All you need to do is set a custom onPress event for the TabBarIcon, as follows:

import Icon from 'react-native-vector-icons/FontAwesome';
 class YourClass extends Component {
   static navigationOptions = ({ navigation, screenProps }) => ({
 title: YourTitle',
 headerTintColor: '#fff',
 headerStyle: {
    backgroundColor: '#000', 
    height: 70,
    elevation: null
  },
 tabBarIcon: ({ tintColor }) => (
   <Icon name = {"user-o"} color={tintColor} size={20} onPress={()=>navigation.goBack()}/>
   ),
 });
 render(){
     return (
      <View><Text>yourScreen</Text></View>
     );
 }

Hope it helps!

@topherCarpediem
Copy link

@cooperka That's really helps. Thanks

@jaimebiernaski
Copy link

One more solution...

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

....

componentDidMount() {
    this.props.navigation.setParams({
      ...this.props.navigation.state.params,
      setCurrentScreen: scene => {
        this._setCurrentScreen(scene);
      }
    });
  }

.... 

render(){...}

_setCurrentScreen = (scene) => {
  console.log('Current Scene:', scene)
}

@magichim
Copy link

magichim commented May 8, 2018

@jamesisaac this solution is more exquisite! really thanks to me!

@tsalama
Copy link

tsalama commented Apr 10, 2022

For anyone looking for an easier solution using React Navigation 6:

import React, { useRef } from 'react';
import { useScrollToTop } from '@react-navigation/native';
import { StyleSheet, View, RefreshControl } from 'react-native';

export default function Home({ route, navigation }) {
	const scrollRef = useRef(null);
	useScrollToTop(scrollRef); // This will trigger scrollToTop on the tab bar icon press!

	return (
		<ScrollView ref={scrollRef}>
		...
		</ScrollView>
	);
}

@github-actions
Copy link

Hey! This issue is closed and isn't watched by the core team. You are welcome to discuss the issue with others in this thread, but if you think this issue is still valid and needs to be tracked, please open a new issue with a repro.

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

Successfully merging a pull request may close this issue.