I have an app that has a bottom tab navigator inside a drawer navigator as my Home Screen and I also have a stack navigator comprising of my auth screens (including login screen).
I am unable to navigate from my login screen to my Home stack screen after a successful login.
I get the error - the action navigate with payload was not handled by any navigator.
Please see my code. How can I resolve this
Navigation file - Navigation.js
const MainTabScreen = () => (
<Tab.Navigator
initialRouteName="Main"
activeColor="#fff"
>
<Tab.Screen
name="Main"
component={Main}
options={{
tabBarLabel: 'Home',
tabBarColor: "#009387",
tabBarIcon: ({ color }) => (
<Icon name="md-home" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarLabel: 'Profile',
tabBarColor: "#694fad",
tabBarIcon: ({ color }) => (
<Icon name="md-contact" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Explore"
component={ExploreScreen}
options={{
tabBarLabel: 'Account',
tabBarColor: "#d02860",
tabBarIcon: ({ color }) => (
<Icon name="md-planet" color={color} size={26} />
),
}}
/>
</Tab.Navigator>
)
// HOME STACK SCREEN
const DrawerScreen = () => (
<Drawer.Navigator drawerContent={props => <DrawerContent {...props} /> } >
<Drawer.Screen name='MainTab' component={MainTabScreen} />
</Drawer.Navigator>
);
const AuthStack = createStackNavigator();
const AuthStackScreen = ({navigation}) => (
<AuthStack.Navigator headerMode='none'>
<AuthStack.Screen name="SplashScreen" component={SplashScreen} />
<AuthStack.Screen name="SignInScreen" component={SignInScreen} /> // LOGIN SCREEN
<AuthStack.Screen name="SignUpScreen" component={SignUpScreen} />
</AuthStack.Navigator>
);
const RootStack = createStackNavigator();
const RootStackScreen = () => {
const [isLoading, setIsLoading] = React.useState(true);
const [user, setUser] = React.useState(null);
React.useEffect(() => {
// Fetch the token from storage then navigate to our appropriate place
const bootstrapAsync = async () => {
let userToken;
try {
userToken = await AsyncStorage.getItem('FBIdToken');
console.log("toen", userToken)
if(userToken !== null) {
setIsLoading(!isLoading);
setUser(userToken);
} else {
setIsLoading(!isLoading);
}
} catch (e) {
console.log(error)
}
// After restoring token, we may need to validate it in production apps
};
bootstrapAsync();
}, []);
return (
<RootStack.Navigator
headerMode="none"
screenOptions={{ animationEnabled: false }}
mode="modal"
>
{isLoading ? (
<RootStack.Screen name="Loading" component={LoadingScreen} />
) : user ? (
<RootStack.Screen name="DrawerScreen" component={DrawerScreen} /> //HOME STACK ) : (
<RootStack.Screen name="AuthStackScreen" component={AuthStackScreen} />
)}
</RootStack.Navigator>
);
};
export default RootStackScreen;
App.js
<Provider store={store}>
<NavigationContainer ref={navigationRef}>
<RootStackScreen />
</NavigationContainer>
</Provider>
Login.js
loginHandle = () => {
this.props.navigation.navigate("DrawerScreen")
};
I suspect that in your RootStackScreen component, your condition isLoading is true so the route to your DrawerScreen does not exists when you navigate. Try removing the condition to be sure. Then if this isthe reason, yu could try applying your condition to the component you pass to your DrawerScreen instead, something like :
<RootStack.Screen name="DrawerScreen" component={isLoading ? LoadingScreen : DrawerScreen} />
Related
I have a React Native app that I am adding navigation to. I have two different stacks I want to navigate between. I have searched SO and came up with the two versions which I have in my below code. The setup is that I have a main stack navigator which has a drawer stack and a normal stack (Auth) that I'd like to be able to go between. The main things are redirecting on login and logout from the header of the Drawer stack.
Below is my code, I am looking for help to figure out how to enable the mentioned navigation that I need the app to do. In theory, this is a common thing so I am pretty sure it should be supported and documented though I can't seem to find anything documented on it.
Error message
The action 'NAVIGATE' with payload {"name":"Auth","params":{}} was not handled by any navigator.
Do you have a screen named 'Auth'?
HeaderRight
export default function HeaderRight ({ navigation }) {
const { removeAuth } = useContext(AuthContext);
const logout = () => {
removeAuth()
// Note both are uncommented for sharing and are not actually meant to be when running the app
navigation.navigate('Auth', { screen: 'Login' }); // { screen: 'Login' }
navigation.navigate(
'Auth',
{},
NavigationActions.navigate({
routeName: 'Login'
})
)
}
return (
<View style={styles.header}>
<Icon name='logout' onPress={logout} size={28} style={styles.icon} color="white" />
</View>
)
}
Root navigation
<Stack.Navigator
initialRouteName={"Splash"}
screenOptions={{}}
component={SplashScreen}
>
{ checkAuth ?
<Stack.Screen name="Drawer" component={DrawerStack} options={({ navigation }) => ({
title: 'My App',
headerRight: () => (
<HeaderRight navigation={navigation}/>
),
headerTitleAlign: 'center',
headerTintColor: 'white',
headerStyle: {
backgroundColor: '#5742f5'
},
})} />
:
<Stack.Screen name="Auth" component={AuthStack} options={{
headerShown: false
}}/>
}
</Stack.Navigator>
drawer stack
export const DrawerStack = (props) => {
return (
<Drawer.Navigator
initialRouteName="Lists"
>
<Drawer.Screen
name="Conversations"
options={{
title: props.title,
}}
component={ListScreen}
/>
<Drawer.Screen
name="Room"
options={{
drawerLabel: () => null,
title: null,
drawerIcon: () => null,
}}
component={RoomScreen}
/>
</Drawer.Navigator>
);
};
auth stack
export const AuthStack = () => {
return (
<Stack.Navigator
initialRouteName={'Login'}
>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Forgot" component={ForgotScreen} />
</Stack.Navigator>
);
};
Edit
I think I have found the potential cause for this but I have no clue about the solution to it.
I have this in my root navigator (same as above)
{ checkAuth ?
<Stack.Screen name="Drawer" component={DrawerStack} options={({ navigation }) => ({
title: 'My App',
headerLeft: () => (
<HeaderLeft />
),
headerRight: () => (
<HeaderRight navigation={navigation}/>
),
})} />
:
<Stack.Screen name="Auth" component={AuthStack} options={{
headerShown: false
}}/>
}
This means that the stack/screen for Auth is only being rendered/exists when the drawer stack isn't shown. How can I refactor my root navigator to fix this?
I am thinking I need to somehow force the checkAuth function to re-render based on different actions taken or to watch a certain local storage.
This is the code for checkAuth
const checkAuth = () => {
try {
const authData = globalStorage.getString('accessToken')
if(authData !== null && authData !== undefined) {
setAuth(authData)
return true
} else {
return false
}
} catch(e) {
console.error(e)
return false
}
}
Context on what globalStorage is
import { MMKV } from 'react-native-mmkv'
export const globalStorage = new MMKV()
I have my navigators arranged like this.
Bottom Navigator
2. Stack Navigator
3. Material Top Tabs Navigator
4. Home Screen
I have created headerRight button in StackNavigator's ScreenComponent like this.
export default HomeDashboardNavigation = ({route, navigation}) => {
return (
<DashboardStack.Navigator>
<DashboardStack.Screen name="TopTabNavigator" component={TopTabNavigator}
options={{
headerShown: true,
headerShadowVisible: false,
headerStyle:{backgroundColor:'#FEF8F0'},
headerTitle: (props) => <LogoTitle {...props} />,
headerTitleAlign: 'center',
headerRight: () => (
<TouchableOpacity onPress={() => {
// Need to pass a boolean value to HomeScreen
}}
>
<HintComponent />
</TouchableOpacity>
),
}}
/>
</DashboardStack.Navigator>
)
}
My TopTabNavigator looks like this:
const TopTabNavigator = ({route, navigation}) => {
return (
<Tab.Navigator tabBar={props => <TabBar {...props} />} >
<Tab.Screen name="HomeScreen" component={HomeScreen} />
<Tab.Screen name="Second" component={SecondScreen} />
<Tab.Screen name="Third" component={ThirdScreen} />
<Tab.Screen name="Fourth" component={FourthScreen} />
</Tab.Navigator>
)
}
My HomeScreen is:
const HomeScreen = (props) => {
const isHeaderRightClicked = false; //Need to get the value here
return (
<ScrollView>
</ScrollView>
);
}
I am using react native navigation 6. I tried using setParams and setting route.params and tried to get is in HomeScreen using useEffect on props.navigation but it never gets executed.
I don't know how to pass value from onPress event to the screen. HomeScreen is embedded in TopTabNavigator which is a Screen component of StackNavigator whose header button click sets the value.
Could you please suggest a good way to do this.
Try something like my example below. Here a useState boolean hook [isClicked, setIsClicked] is defined, where the onPress-function changes this boolean on every press. The state isClicked in then passed through the TopTabNavigator to the HomeScreen where it can be used.
HomeDashboardNavigation file:
export default HomeDashboardNavigation = ({ route, navigation }) => {
const [isClicked, setIsClicked] = useState(false);
return (
<DashboardStack.Navigator>
<DashboardStack.Screen
name="TopTabNavigator"
options={{
headerShown: true,
headerShadowVisible: false,
headerStyle: { backgroundColor: "#FEF8F0" },
headerTitle: (props) => <LogoTitle {...props} />,
headerTitleAlign: "center",
headerRight: () => (
<TouchableOpacity
onPress={() => {
setIsClicked((state) => !state);
}}
>
<HintComponent />
</TouchableOpacity>
),
}}
>
{(props) => <TopTabNavigator isClicked={isClicked} {...props} />}
</DashboardStack.Screen>
</DashboardStack.Navigator>
);
};
TopTabNavigator file
const TopTabNavigator = ({ isClicked, route, navigation }) => {
return (
<Tab.Navigator tabBar={(props) => <TabBar {...props} />}>
<Tab.Screen name="HomeScreen">
{(props) => <HomeScreen isClicked={isClicked} {...props} />}
</Tab.Screen>
<Tab.Screen name="Second" component={SecondScreen} />
<Tab.Screen name="Third" component={ThirdScreen} />
<Tab.Screen name="Fourth" component={FourthScreen} />
</Tab.Navigator>
);
};
HomeScreen file
export const HomeScreen = (props) => {
const isHeaderRightClicked = props.isClicked;
return <ScrollView></ScrollView>;
};
Im following a react native app tutorial that has stack navigation only but I'm trying to also add nested navigation so I have a tab bar at the bottom that leads to additional pages I'm building but can't seem to figure out how to implement in my current code. Here is my code so far not sure if i'm on the right track or way off
{
DashBoard: DashBoardScreen,
Articles: ArticlesScreen,
Article: ArticleScreen,
Search: SearchScreen
},
{
initialRouteName: 'DashBoard',
defaultNavigationOptions: ({navigation}) => ({
headerStyle, headerTitleStyle,
headerRight: (<SearchBtn onPress={() => navigation.navigate('Search')}/>)
})
}
);
const Tab = createBottomTabNavigator();
const TabNavigator = () => (
<Tab.Navigator>
<Tab.Screen name="News" component={DashBoard}/>
<Tab.Screen name="Game" component={Game}/>
</Tab.Navigator>
)```
Here's how you can achieve this:
const BottomTab = createBottomTabNavigator();
const Stack = createStackNavigator();
const AppStack = () => {
return (
<BottomTab.Navigator>
<BottomTab.Screen name="Screen1" component={Screen1} />
<BottomTab.Screen name="Screen2" component={Screen2} />
</BottomTab.Navigator>
);
};
const MainStack = () => {
return (
<Stack.Navigator headerMode="none">
<Stack.Screen name="App" component={AppStack} />
<Stack.Screen name="Auth" component={AuthStack} />
</Stack.Navigator>
);
};
I'm building an app with the typical Navigator, which contains a Welcome Screen, Login Screen, Signups, etc. When a user is authenticated, I'm then switching to a Drawer Navigation. However, I'm not entirely sure I'm implementing the Drawer Navigator correctly.
Sample rootNavigator:
const RootNavigator = () => {
const app = useSelector(state => state.app);
const isLoggedIn = app.user ? true : false;
const showWelcome = app.showWelcome;
if (isLoggedIn) {
return (
<Navigator headerMode="none">
<Screen name="Home" component={AppDrawerNavigator} />
</Navigator>
);
}
return (
<Navigator
headerMode="none"
initialRouteName={showWelcome ? 'Walkthrough' : 'Welcome'}>
<Screen name="Walkthrough" component={WalkthroughScreen} />
<Screen name="Welcome" component={WelcomeScreen} />
<Screen name="Login" component={LoginScreen} />
<Screen name="Signup" component={SignupScreen} />
<Screen name="ForgotPassword" component={ForgotPasswordScreen} />
<Screen name="ResetPassword" component={ResetPasswordScreen} />
</Navigator>
);
};
Then, when a user is logged in, I'm returning the following for the AppDrawerNavigator:
const AppDrawerNavigator = () => {
const insets = useSafeAreaInsets();
return (
<SafeAreaView
mode="padding"
style={[
styles.safeArea,
{ paddingTop: insets.top, paddingBottom: insets.bottom },
]}
edges={['right', 'left']}>
<Drawer.Navigator
screenOptions={{
drawerType: 'front',
gestureEnabled: true,
swipeEnabled: false,
}}
drawerContent={props => <HomeDrawer {...props} />}>
{/* Screens */}
<Drawer.Screen name="Landing" component={LandingScreen} />
<Drawer.Screen name="Profile" component={ProfileScreen} />
</Drawer.Navigator>
</SafeAreaView>
);
};
The downside that I'm noticing with this type of implementation is on any of the screens where I try to detect the current view, the route returned is "Home", which appears to be an issue of how I've implemented the drawer.
What is the best way to go about this problem; how I should render the Drawer Navigation?
I have the main Stack navigator with multiple screens and one of those screens is a nested Drawer navigator with dynamically created screens.
<Provider store={store}>
<NavigationContainer onStateChange={onStateChange}>
<Stack.Navigator initialRouteName="Launch">
<Stack.Screen name="Launch" component={Launch} />
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="ChatBox" component={ChatBox} /> /* <- Here is the drawer navigator screen */
</Stack.Navigator>
</NavigationContainer>
</Provider>
As you can see in the example gif the ChatComponent is always re-rendering when drawer actions were. It does even if the current screen hasn't changed in the drawer's list.
<Drawer.Navigator>
{userChannels.map((channel, index) => {
const chatName = userChannels[index].friendlyName;
const screen = React.memo(() => (
<View style={{ flex: 1 }}>
<Header text={chatName} />
<ChatComponent
channel={channel}
loadEarlier={loadEarlier}
isLoadingEarlier={isLoadingEarlier}
onLoadEarlier={onLoadEarlier}
savedMessages={savedMessages}
onSend={onSend}
isTyping={isTyping}
user={{
_id: me.member.sid || 0,
}}
onInputTextChanged={async () => {
currentChannel &&
(await currentChannel.typing());
}}
enterChannel={() =>
enterChannel(channel.uniqueName)
}
/>
</View>
));
return (
<Drawer.Screen
key={index}
name={chatName || String(index)}
component={screen}
/>
);
})}
</Drawer.Navigator>
The header component is:
export default (props) => {
const { text } = props;
const navigation = useNavigation();
const logout = async () => {
try {
await AsyncStorage.removeItem('accessToken');
await AsyncStorage.removeItem('refreshToken');
} catch (e) {
//
}
navigation.navigate('Login');
};
return (
<View style={styles.header}>
<TouchableOpacity
onPress={() => navigation.dispatch(DrawerActions.openDrawer())}>
<Icon name="menu" color="#FFF" size={18} />
</TouchableOpacity>
{text && <Text style={styles.headerText}>{text}</Text>}
<Text style={styles.headerText} onPress={logout}>
Logout
</Text>
</View>
);
};
The chat component is:
const ChatComponent = React.memo((props) => {
const {
channel,
loadEarlier,
isLoadingEarlier,
onLoadEarlier,
savedMessages,
onSend,
isTyping,
user,
onInputTextChanged,
enterChannel,
} = props;
useEffect(() => {
console.log('render ChatComponent');
enterChannel();
}, []);
return (
<GiftedChat
inverted={false}
loadEarlier={loadEarlier}
isLoadingEarlier={isLoadingEarlier}
onLoadEarlier={onLoadEarlier}
messages={savedMessages}
onSend={onSend}
user={user}
onInputTextChanged={onInputTextChanged}
isTyping={isTyping}
renderBubble={renderBubble}
renderTime={renderTime}
renderInputToolbar={renderInputToolbar}
/>
);
});
Here is the onStateChange of NavigationContainer
How to avoid unnecessary renders of a ChatComponent?