react-nativereact-navigationreact-navigation-bottom-tab

React navigation tab navigator - Always pass the same route params when pressing tab


I'm new here! I'm working with react navigation, and I love it!... but... I've run into an issue that I figure is user error and not a bug.

What are the details of your problem?

I made a minimal example in a snack here: https://snack.expo.dev/@jsimeroth/userprofilenav

There's a navigation flow in my app where my initial route params no longer get passed to the component on tab navigator presses/clicks. This happens when I have a tab navigator nested inside a stack navigator, and I navigate to a non-tab stack screen then back to a tab screen.

Desired behavior

User 1 is the logged in user, so I would like to always navigate back to their profile when the profile tab is clicked, even after viewing another user's profile.

What can I do to achieve the desired behavior?

Actual behavior

Currently, after navigating to another user's profile page, there is no way to get back to user 1's profile. Clicking the profile tab just takes you back to the new user's profile.

Platform(s)

I've seen it on both web and ios, haven't tried others. For some reason, this snack doesn't run on ios though so try web.

What did you try?

I tried adding an event listener, but am probably doing that wrong because I could never get it to fire even when using a basic console.log. Something I haven't tried but considered was building my own tab navigator instead of using the pre-built material one, but I have a feeling that's not necessary.

Code

Code is a bit long to show the example, so check out the snack! But also adding it here just in case that's preferred.

import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
import { Card, Button } from 'react-native-paper';
import { NavigationContainer, useNavigation } from '@react-navigation/native';
import { createMaterialBottomTabNavigator } from '@react-navigation/material-bottom-tabs';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';

// make navigator components
const Tab = createMaterialBottomTabNavigator();
const Stack = createNativeStackNavigator();

// basic icon
function ColoredIcon(name, color) {
  return <Icon name={name} color={color} size={20} />;
}

// SCREEN COMPONENTS
// Tab navigator screens
export function Profile({navigation, route}) {
const user = route.params.userId
  return (
    <View style={styles.container}>
      <Text>This is user: {user}</Text>
      {user === 1 ? (
        <Text>
          User 1 is the logged in user and I would like to see this user's profile page whenever I click the profile bottom tab. {'\n\n'}
          However, navigate to the communities tab...
        </Text>
      ) : (
        <Text>
          So far so good, you're seeing the desired user's profile. But now there is no way to get back to the default/logged in/current user. {'\n\n'}
          TLDR: how do I make it so that clicking the profile tab always sends the userId I provided in the initial params, while still allowing the profile component to render other users if navigated to in a different way?
        </Text>
      )}
    </View>
  )
}

export function CommunityList() {
  const navigation = useNavigation();
  return (
    <View style={styles.container}>
      <Button 
        onPress={() => {
          navigation.navigate('Community', {community: 1})}
        }
        mode='contained'
      >
        See community 1
      </Button>
      <Text>
        Select a community...
      </Text>
    </View>
  )
}

// Stack screens
export function Community() {
  const navigation = useNavigation();
  return (
    <View style={styles.container}>
      <Button 
        onPress={() => {
          navigation.navigate('Profile', {userId: 2})}
        }
        mode='contained'
      >
        See user 2 profile
      </Button>
      <Text>
        This community has posts by different users. Click the above user to see their profile...
      </Text>
    </View>
  )
}

// Tab navigator (primary stack screen)
export function TabNav() {
  const userId = 1;
  return (
    <Tab.Navigator>
        <Tab.Screen
          name='Profile'
          component={Profile}
          initialParams={{ userId: userId }}
          options={{
            tabBarLabel: 'Profile',
            tabBarIcon: () => ColoredIcon('home', 'black'),
          }}
        />
        <Tab.Screen
          name='CommunityList'
          component={CommunityList}
          options={{
            tabBarLabel: 'Communities',
            tabBarIcon: () => ColoredIcon('account-group', 'black'),
          }}
        />
      </Tab.Navigator>
  );
}

// Stack navigator
export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name='TabNav' component={TabNav} />
        <Stack.Screen name='Community' component={Community} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    paddingTop: Constants.statusBarHeight,
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
});

EDIT: updated to clarify the desired behavior


Solution

  • Somebody on discord pointed me in the right direction on this one. By setting a tabPress listener on the profile tab you can specify the route params you want sent whenever the tab is pressed. This enables the desired behavior of always viewing the default user's profile whenever the profile tab is clicked.

    In my case the code is:

              listeners={({ navigation }) => ({
                tabPress: (e) => {
                  e.preventDefault();
                  navigation.navigate('Profile', { userId: 1 });
                },
            })}
    

    In context of the tab navigator component that looks like this:

    // Tab navigator (primary stack screen)
    export function TabNav() {
      const userId = 1;
      return (
        <Tab.Navigator>
            <Tab.Screen
              name='Profile'
              component={Profile}
              initialParams={{ userId: userId }}
              listeners={({ navigation }) => ({
                tabPress: (e) => {
                  e.preventDefault();
                  navigation.navigate('Profile', { userId: 1 });
                },
            })}
              options={{
                tabBarLabel: 'Profile',
                tabBarIcon: () => ColoredIcon('home', 'black'),
              }}
            />
    // ... etc
          </Tab.Navigator>
      );
    }
    

    Hope this helps somebody else out there too!