Hi I am still new to react-native and has been trying to create an app.
My stuck is that I don't know why useEffect doesn't work when switching screens by react-navigation-bottom-tab.
Below is my HomeScreen.js where I wrot useEffect to fetch all data from Firestore.
As you can see I wrote two useEffects because I thought it'd work.
The first one was thought to fetch data when I switch to Home from like ProfileScreen.
import {
View,
FlatList,
StyleSheet,
} from "react-native";
import { RideCard } from "./RideCard";
import { OneTouchFilter } from "./OneTouchFilter";
import { useFirestoreContext } from "../../contexts/FirestoreContext";
import { useEffect } from "react";
export const HomeScreen = ({ navigation }) => {
console.log("HomeScreen.js useEffect");
const { selectedBoardType, cityFromText, cityToText, fetchRides, rides } =
useFirestoreContext();
useEffect(() => {
fetchRides();
}, []);
useEffect(() => {
fetchRides();
}, [selectedBoardType, cityFromText, cityToText]);
return (
<View style={styles.inner}>
<OneTouchFilter />
<FlatList
data={rides}
renderItem={(itemData) => (
<RideCard
ride={itemData.item}
index={itemData.index}
id={itemData.item.id}
numOfRides={rides.length}
/>
)}
/>
</View>
);
};
App.js
this is a file where react-navigation-bottom-tab placed in.
import React, { useContext } from "react";
import { StyleSheet } from "react-native";
import { NavigationContainer, DefaultTheme } from "#react-navigation/native";
import { createStackNavigator } from "#react-navigation/stack";
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs";
import { HomeScreen } from "./screens/Home/HomeScreen";
import { PostScreen } from "./screens/Post/PostScreen";
import { ProfileScreen } from "./screens/Profile/ProfileScreen";
import Ionicons from "react-native-vector-icons/Ionicons";
import { NativeBaseProvider } from "native-base";
// create another file for contexts Provider
import { AuthContextProvider } from "./contexts/AuthContext";
import { FirestoreContextProvider } from "./contexts/FirestoreContext";
import { FormContextProvider } from "./contexts/FormContext";
const MyTheme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
background: "white",
},
};
export default function App() {
const Stack = createStackNavigator();
// タブ移動の設定を新規追加
// createBottomTabNavigator ... タブ移動を設定する関数
const Tab = createBottomTabNavigator();
// 新規追加
// - 移動を関数に持たせて、タブ移動の設定で利用
// - 意図 ... タブ移動の箇所のコードが読みにくくなるため
const Home = () => {
return (
<Stack.Navigator>
<Stack.Screen
options={{ headerShown: false }}
name="Home"
component={HomeScreen}
/>
</Stack.Navigator>
);
};
const Post = () => {
return (
<Stack.Navigator
headerMode="screen"
screenOptions={{ headerShown: false }}
>
<Stack.Screen name="Post" component={PostScreen} />
{/* <Stack.Screen name="詳細" component={DetailsScreen} /> */}
</Stack.Navigator>
);
};
const Profile = () => {
return (
<Stack.Navigator
headerMode="screen"
screenOptions={{ headerShown: false }}
>
<Stack.Screen name="Profile" component={ProfileScreen} />
{/* <Stack.Screen name="詳細" component={DetailsScreen} /> */}
</Stack.Navigator>
);
};
return (
<AuthContextProvider>
<FirestoreContextProvider>
<FormContextProvider>
<NativeBaseProvider>
<NavigationContainer theme={MyTheme}>
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
// icon swithcer which depends on the route name
if (route.name === "Home") {
iconName = focused ? "ios-home" : "ios-home";
} else if (route.name === "Post") {
iconName = focused ? "ios-add" : "ios-add";
} else if (route.name === "Profile") {
iconName = focused ? "md-person" : "md-person";
}
return (
<Ionicons name={iconName} size={size} color={color} />
);
},
})}
tabBarOptions={{
activeTintColor: "rgb(0, 110, 182)",
inactiveTintColor: "gray",
}}
>
<Tab.Screen name="Home" component={Home} />
<Tab.Screen name="Post" component={Post} />
<Tab.Screen name="Profile" component={Profile} />
</Tab.Navigator>
</NavigationContainer>
</NativeBaseProvider>
</FormContextProvider>
</FirestoreContextProvider>
</AuthContextProvider>
);
}
It's because, even though you switch screens, the other screen is not unmounted --- it's still in memory but not visible. This is a design decision of react-navigation intended for better performance (it doesn't have to reload the screen when you go back, as it's already there). Since it is not unmounted, when the user returns to it, it just triggers a rerender of the already-instantiated component, so the effect does not run.
What you need to use instead is useFocusEffect which is an effect bound to if the screen is in focus.
Related
I want to achieve a native stack navigator for each tab. However I want different Stack navigator files for each tab. So I created different files but i'm getting a warning in the console:
Found screens with the same name nested inside one another. Check:
Home, Home > Home
This can cause confusing behavior during navigation. Consider using
unique names for each screen instead. at
node_modules/#react-navigation/core/src/BaseNavigationContainer.tsx:364:14 in React.useEffect$argument_0
Also the the Home screen is nested inside Home screen.I tried changing the name of the Home from Home to Homescreen component still the issue persists.
Here are the functions in my files:
Home.js
function Home({ navigation }) {
return (
<View style={styles.screen}>
<Text>Home screen!</Text>
<Button title="Details" onPress={() => navigation.navigate("Details")} />
</View>
);
}
Details.js
const Details = (props) => {
return (
<View>
<Text>Details will appear here!</Text>
</View>
);
};
HomeStackScreen.js
import { createNativeStackNavigator } from "#react-navigation/native-stack";
import HomeScreen from "../screens/Home";
import DetailsScreen from "../components/Details";
const HomeStack = createNativeStackNavigator();
function HomeStackScreen() {
return (
<HomeStack.Navigator>
<HomeStack.Screen name="Home" component={HomeScreen} />
<HomeStack.Screen name="Details" component={DetailsScreen} />
</HomeStack.Navigator>
);
}
AppNavigator.js
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs";
import { NavigationContainer } from "#react-navigation/native";
import HomeStackScreen from "./HomeStackScreen";
const Tab = createBottomTabNavigator();
function AppNavigator() {
return (
<NavigationContainer>
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
if (route.name === "Home") {
iconName = focused ? "ios-home" : "ios-home-outline";
}
{/*other routes*/}
})}
>
<Tab.Screen name="Home" component={HomeStackScreen} />
{/*other tabs*/}
</Tab.Navigator>
</NavigationContainer>
);
}
I want to use BottomNavigation to navigation between screens actually its working fine with BottomNavigation.SceneMap({...})
but the BottomNavigation its being showing in every screens, i only want to show once the user is logged. after click on login button
import React from 'react'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import userReducer from './src/reducers/user'
import { NavigationContainer } from '#react-navigation/native'
import { BottomNavigation, Text } from 'react-native-paper'
import { createStackNavigator } from '#react-navigation/stack'
import { theme } from './src/core/theme'
import {
StartScreen,
Dashboard,
GroupScreen,
InviteScreen,
CreateGroup,
} from './src/screens'
const Stack = createStackNavigator()
const store = createStore(userReducer)
export default function App() {
const [index, setIndex] = React.useState(0)
const [routes] = React.useState([
{ key: 'music', title: 'Music', icon: 'queue-music' },
{ key: 'albums', title: 'Albums', icon: 'album' },
])
const one = () => (
<NavigationContainer>
<Stack.Navigator
initialRouteName="StartScreen"
>
<Stack.Screen name="StartScreen" component={StartScreen} />
<Stack.Screen name="Dashboard" component={Dashboard} />
</Stack.Navigator>
</NavigationContainer>
)
const two = () => (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="InviteScreen" component={InviteScreen} />
</Stack.Navigator>
</NavigationContainer>
)
const renderScene = BottomNavigation.SceneMap({
music: one,
albums: two,
})
return (
<Provider store={store} theme={theme}>
<BottomNavigation
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderScene={renderScene}
/>
</Provider>
)
}
EDIT by answers:
i did this when i pressed the button login i need to redirect to dashboard but dashboard is in mytabs
function MyTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Dashboard" component={Dashboard} />
</Tab.Navigator>
)
}
does not render nothing, i just to copy any example from here https://reactnavigation.org/docs/bottom-tab-navigator/ any of those render in my local, i am using EXPO
for example this
import * as React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
function HomeScreen() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Home!</Text>
</View>
);
}
function SettingsScreen() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Settings!</Text>
</View>
);
}
const Tab = createBottomTabNavigator();
function MyTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
);
}
export default function App() {
return (
<NavigationContainer>
<MyTabs />
</NavigationContainer>
);
}
the wrong is display:none
You need to put your BottomNavigation in another stack that has loginScreen side by side.
Try using createBottomTabNavigator
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
const Tab = createBottomTabNavigator();
function MyTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
);
}
<NavigationContainer>
<Stack.Navigator initialRouteName="loginScreen">
<Stack.Screen name="loginScreen" component={LoginScreen} />
<Stack.Screen name="main" component={MyTabs} />
</Stack.Navigator>
</NavigationContainer>
Take what you already have with MyTabs() and create a LoginScreen and then do the following. You will need to detect whether the user is present. If there is a user, then show the main screen, if not show the login screen.
<>
<Stack.Navigator screenOptions={{ headerShown: false }}>
{user ? (
<Stack.Screen name="Main" component={MainStack} />
) : (
<Stack.Screen name="Login" component={LoginStack} />
)}
</Stack.Navigator>
</>
The best way is to do like this:
<NavigationContainer>
{user !== null ? <RootNavigator /> : <AuthNavigator />}
</NavigationContainer>
The user is a useState.
The NavigationContainer has 2 options. If the user is logged it shows the RootNAvigator with the bottomNavigation, if not it shows the authentication flow.
I am a bit new to react native and I am having difficulty navigate between 2 different tab navigators after login and logout in react native screens of my app that all use function component screens
Can someone advice me on how to do this.
In my Navigation directory(which handle navigation flow), I have 2 files index.js and main.js
navigation/index.js
navigation/main.js
Once the user launches the app, the user sees screens in the navigation/index.js file.
After the user either signs up or signs in, the users see screens in the navigation/main.js file
I have implemented everything else in the app except navigate between navigators after login and logout in the app
I initially put all screens in the same navigator file but I started having issues after pressing the back button continuously
see the code below
:::::::::EDITS:::::::::::
No longer using Navigation/main.js
Now using only Navigation/index.js and implementing what was suggested by #Tolga Berk Ordanuç
Still need more help though because
It worked partially
Now
After logging in by clicking login I get the error
" ERROR The action 'NAVIGATE' with payload {"name":"BottomTabNavigator"} was not handled by any navigator.
Do you have a screen named 'BottomTabNavigator'?"
And when I close the app completely by hitting back numerous times,
I am logged in
But now second error, when I click logout
I get below error
ERROR The action 'NAVIGATE' with payload {"name":"Walkthrough"} was not handled by any navigator.
Do you have a screen named 'Walkthrough'?
home.js
import React, {useState} from 'react';
import {View} from 'react-native';
import {useTheme} from '#config';
import {Header, Icon, Button} from '#components';
import {useDispatch} from 'react-redux';
export default function Home({navigation, route}) {
const {colors} = useTheme();
const dispatch = useDispatch();
const [loading, setLoading] = useState(false);
const logout = () => {
setLoading(true);
dispatch(setIsLoggedIn(false));
navigation.navigate('SignIn');
setLoading(false);
};
return (
<View style={{flex: 1}}>
<Header
title="sign in"
renderLeft={() => {
return (
<Icon
name="times"
size={20}
color={colors.primary}
enableRTL={true}
/>
);
}}
onPressLeft={() => {
navigation.goBack();
}}
/>
<View>
<Button style={{marginTop: 20}} full loading={loading} onPress={logout}>
Log Out
</Button>
</View>
</View>
);
}
Navigation/main.js
import React from 'react';
import {createStackNavigator} from '#react-navigation/stack';
import {useTheme} from '#config';
import {useSelector} from 'react-redux';
import {designSelect} from '#selectors';
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
import {BaseColor, useFont} from '#config';
import {Icon} from '#components';
/* Main Stack Navigator */
/* Modal Screen only affect iOS */
import Savings from 'Savings';
import Loans from 'Loans';
import Home from 'Home';
import Profile from 'Profile';
/* Stack Screen */
import Setting from 'Setting';
import Feedback from 'Feedback';
import ChangePassword from 'ChangePassword';
import ProfileEdit from 'ProfileEdit';
import ContactUs from 'ContactUs';
import AboutUs from 'AboutUs';
import Support from 'Support';
const MainStack = createStackNavigator();
export default function Main() {
return (
<MainStack.Navigator
screenOptions={{
headerShown: false,
}}
initialRouteName="BottomTabNavigator">
<MainStack.Screen
name="BottomTabNavigator"
component={BottomTabNavigator}
/>
<MainStack.Screen name="Support" component={Support} />
<MainStack.Screen name="SavingsTemp" component={Savings} />
<MainStack.Screen
name="BottomTabNavigator"
component={BottomTabNavigator}
/>
<MainStack.Screen name="Setting" component={Setting} />
<MainStack.Screen name="Feedback" component={Feedback} />
<MainStack.Screen name="ChangePassword" component={ChangePassword} />
<MainStack.Screen name="ProfileEdit" component={ProfileEdit} />
<MainStack.Screen name="ContactUs" component={ContactUs} />
<MainStack.Screen name="AboutUs" component={AboutUs} />
</MainStack.Navigator>
);
}
function BottomTabNavigator() {
const {colors} = useTheme();
const font = useFont();
const BottomTab = createBottomTabNavigator();
const design = useSelector(designSelect);
/**
* Main follow return Home Screen design you are selected
* #param {*} design ['basic', 'real_estate','event', 'food']
* #returns
*/
const exportHome = () => {
return Home;
};
return (
<BottomTab.Navigator
initialRouteName="Home"
screenOptions={{
headerShown: false,
}}
tabBarOptions={{
showIcon: true,
showLabel: true,
activeTintColor: colors.primary,
inactiveTintColor: BaseColor.grayColor,
style: {borderTopWidth: 1},
labelStyle: {
fontSize: 12,
fontFamily: font,
paddingBottom: 4,
},
}}>
<BottomTab.Screen
name="Home"
component={exportHome(design)}
options={{
title: 'home',
tabBarIcon: ({color}) => {
return <Icon color={color} name="home" size={20} solid />;
},
}}
/>
<BottomTab.Screen
name="Savings"
component={Savings}
options={{
title: 'Savings',
tabBarIcon: ({color}) => {
return <Icon color={color} name="bookmark" size={20} solid />;
},
}}
/>
<BottomTab.Screen
name="Loans"
component={Loans}
options={{
title: 'Loans',
tabBarIcon: ({color}) => {
return <Icon color={color} name="clipboard-list" size={20} solid />;
},
}}
/>
<BottomTab.Screen
name="Profile"
component={Profile}
options={{
title: 'Profile',
tabBarIcon: ({color}) => {
return <Icon solid color={color} name="user-circle" size={20} />;
},
}}
/>
</BottomTab.Navigator>
);
}
Navigation/index.js
import React, {useEffect, useState} from 'react';
import {StatusBar, Platform} from 'react-native';
import {NavigationContainer} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
import {DarkModeProvider, useDarkMode} from 'react-native-dark-mode';
import {useTheme, BaseSetting} from '#config';
import i18n from 'i18next';
import {initReactI18next} from 'react-i18next';
import {useSelector} from 'react-redux';
import {languageSelect, designSelect} from '#selectors';
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
import {BaseColor, useFont} from '#config';
import {useTranslation} from 'react-i18next';
import {Icon} from '#components';
/* Main Stack Navigator */
/* Modal Screen only affect iOS */
import Loading from 'Loading';
import SignIn from 'SignIn';
import SignUp from 'SignUp';
import ResetPassword from 'ResetPassword';
import Walkthrough from 'Walkthrough';
import Home from 'Home';
import Profile from 'Profile';
import Feedback from 'Feedback';
import ChangePassword from 'ChangePassword';
import ProfileEdit from 'ProfileEdit';
import ContactUs from 'ContactUs';
import AboutUs from 'AboutUs';
import Tab2 from 'Tab2';
import Tab3 from 'Tab3';
import BottomTabNavigator from './BottomTabNavigator';
import {store} from '../store';
const RootStack = createStackNavigator();
export default function Navigator() {
const language = useSelector(languageSelect);
const {theme, colors} = useTheme();
const isDarkMode = useDarkMode();
const [isLoggedInValue, setIsLoggedInValue] = useState(false);
useEffect(() => {
i18n.use(initReactI18next).init({
resources: BaseSetting.resourcesLanguage,
lng: language ?? BaseSetting.defaultLanguage,
fallbackLng: BaseSetting.defaultLanguage,
});
if (Platform.OS === 'android') {
StatusBar.setBackgroundColor(colors.primary, true);
}
StatusBar.setBarStyle(isDarkMode ? 'light-content' : 'dark-content', true);
console.log('<<<<< IS LOGGED IN STATE >>>>>>');
console.log(isLoggedInValue);
console.log('<<<<< IS LOGGED IN STATE >>>>>>');
setIsLoggedInValue(store.getState().isloggedin.isLoggedIn);
}, [colors.primary, isDarkMode, language, isLoggedInValue]);
return (
<DarkModeProvider>
<NavigationContainer theme={theme}>
<RootStack.Navigator
mode="modal"
screenOptions={{
headerShown: false,
}}>
{!isLoggedInValue ? (
<>
<RootStack.Screen
name="Loading"
component={Loading}
options={{gestureEnabled: false}}
/>
<RootStack.Screen name="Walkthrough" component={Walkthrough} />
<RootStack.Screen name="SignIn" component={SignIn} />
<RootStack.Screen name="SignUp" component={SignUp} />
<RootStack.Screen
name="ResetPassword"
component={ResetPassword}
/>
</>
) : (
<>
<RootStack.Screen
name="BottomTabNavigator"
component={BottomTabNavigator}
/>
<RootStack.Screen name="Feedback" component={Feedback} />
<RootStack.Screen
name="ChangePassword"
component={ChangePassword}
/>
<RootStack.Screen name="ProfileEdit" component={ProfileEdit} />
<RootStack.Screen name="ContactUs" component={ContactUs} />
<RootStack.Screen name="AboutUs" component={AboutUs} />
</>
)}
</RootStack.Navigator>
</NavigationContainer>
</DarkModeProvider>
);
}
Signin.js
import React, {useState} from 'react';
import {
View,
TouchableOpacity,
KeyboardAvoidingView,
Platform,
Alert,
} from 'react-native';
import {useDispatch} from 'react-redux';
import {BaseStyle, useTheme} from '#config';
import {Header, SafeAreaView, Icon, Text, Button, TextInput} from '#components';
import styles from './styles';
export default function SignIn({navigation, route}) {
const {colors} = useTheme();
const [loading, setLoading] = useState(false);
const [id, setId] = useState('');
const [password, setPassword] = useState('');
const dispatch = useDispatch();
/**
* call when action onLogin
*/
const onLogin = () => {
setLoading(true);
dispatch(setIsLoggedIn(true));
navigation.navigate('Home');
setLoading(false);
};
const offsetKeyboard = Platform.select({
ios: 0,
android: 20,
});
return (
<View style={{flex: 1}}>
<Header
title="sign in"
renderLeft={() => {
return (
<Icon
name="times"
size={20}
color={colors.primary}
enableRTL={true}
/>
);
}}
onPressLeft={() => {
navigation.goBack();
}}
/>
<SafeAreaView style={BaseStyle.safeAreaView} edges={['right', 'left']}>
<KeyboardAvoidingView
behavior={Platform.OS === 'android' ? 'height' : 'padding'}
keyboardVerticalOffset={offsetKeyboard}
style={{flex: 1}}>
<View style={styles.contain}>
<TextInput onChangeText={setId} placeholder="username" value={id} />
<TextInput
style={{marginTop: 10}}
onChangeText={setPassword}
placeholder="Password"
secureTextEntry={true}
value={password}
/>
<Button
style={{marginTop: 20}}
full
loading={loading}
onPress={onLogin}>
'sign_in'
</Button>
<TouchableOpacity
onPress={() => navigation.navigate('ResetPassword')}>
<Text body1 grayColor style={{marginTop: 25}}>
'forgot_your_password'
</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</SafeAreaView>
</View>
);
}
5
navigation/BottomTabNavigator.js
export default function BottomTabNavigator() {
const {colors} = useTheme();
const font = useFont();
const design = useSelector(designSelect);
const BottomTab = createBottomTabNavigator();
const exportHome = () => {
return Home;
};
return (
<BottomTab.Navigator
initialRouteName="Home"
screenOptions={{
headerShown: false,
}}
tabBarOptions={{
showIcon: true,
showLabel: true,
activeTintColor: colors.primary,
inactiveTintColor: BaseColor.grayColor,
style: {borderTopWidth: 1},
labelStyle: {
fontSize: 12,
fontFamily: font,
paddingBottom: 4,
},
}}>
<BottomTab.Screen
name="Home"
component={exportHome(design)}
options={{
title: 'Home',
tabBarIcon: ({color}) => {
return <Icon color={color} name="home" size={20} solid />;
},
}}
/>
<BottomTab.Screen
name="Savings"
component={Tab2}
options={{
title: 'Savings',
tabBarIcon: ({color}) => {
return <Icon color={color} name="bookmark" size={20} solid />;
},
}}
/>
<BottomTab.Screen
name="Loans"
component={Tab3}
options={{
title: 'Loans',
tabBarIcon: ({color}) => {
return <Icon color={color} name="clipboard-list" size={20} solid />;
},
}}
/>
<BottomTab.Screen
name="Profile"
component={Profile}
options={{
title: 'Profile',
tabBarIcon: ({color}) => {
return <Icon solid color={color} name="user-circle" size={20} />;
},
}}
/>
</BottomTab.Navigator>
);
}
I couldn't see the second tab navigator in your code but i saw that you create navigators inside a component which is in my opinion and experience is not what you supposed to do. you should create your navigators seperately not inside another component. in the documentation of react-navigation they show it this way so they should know what they are doing i guess :D
Login and Main app screens used to managed by switch navigators but with react-navigation 5 they dumped the switch navigators and instead support ternary operators in the navigator components like this
const RootStack = createStackNavigator<RootStackParamList>();
export const RootNavigator = () => {
const isLoggedIn = useReduxSelector(({ authState }) => authState.isLoggedIn);
return (
<RootStack.Navigator screenOptions={{ headerShown: false }}>
{!isLoggedIn ? (
<RootStack.Screen
name="AuthNavigator"
options={{ headerShown: false }}
component={AuthNavigator}
/>
) : (
<>
<RootStack.Screen name="TabNavigator" component={TabNavigator} />
<RootStack.Screen
name="DetailsNavigator"
component={DetailsNavigator}
/>
<RootStack.Screen
name="UserStackNavigator"
component={UserStackNavigator}
/>
<RootStack.Screen name="Announcements" component={Announcements} />
<RootStack.Screen name="Read" component={Read} />
<RootStack.Screen
name="Search"
options={{
...TransitionPresets.FadeFromBottomAndroid,
gestureEnabled: false,
}}
component={Search}
/>
</>
)}
</RootStack.Navigator>
);
};
After logging in and dispatching the appropriate action for your redux to change isLoggedIn state Navigator automatically strips out the Login page and fallbacks to what is under it. and at this point user cannot go to login or register screens even if they go back as many times as they want because we ripped the screens out of the navigation tree.
If you want to return to Login screens you just log out the user and navigation tree again contains these screens and you can navigate to them by navigation.navigate function with respective keys of your screens.
And also if you're curious what my tab navigator looks like it's just like this
const Tab = createBottomTabNavigator<TabParamList>();
//
const tabNavigatorScreenOptions: (props: {
route: RouteProp<TabParamList, keyof TabParamList>;
navigation: unknown;
}) => BottomTabNavigationOptions = ({ route }) => ({
tabBarIcon: (params) => <TabBarIcon {...params} route={route} />,
tabBarButton: (props) => (
<Pressable
{...props}
style={[props.style, { marginTop: Layout.dimensions.xs_h }]}
/>
),
// tabBarLabel: (props) => <TabBarLabel {...props} route={route} />,
});
export const TabNavigator = () => {
const debug = useReduxSelector(({ info }) => info.debug);
// Logger.debug = debug;
return (
<Tab.Navigator
tabBarOptions={{ adaptive: true, showLabel: false }}
screenOptions={tabNavigatorScreenOptions}
>
<Tab.Screen name="Discover" component={Discover} />
<Tab.Screen name="Library" component={LibraryNavigator} />
<Tab.Screen name="Settings" component={SettingsNavigator} />
{debug && <Tab.Screen name="TestNavigator" component={TestNavigator} />}
</Tab.Navigator>
);
};
Code is in typescript but that shouldn't be a problem about the logic.
if you have any questions further i am happy to help!
Happy coding my fellow react-native developer!
I'm new to react native and using I18n to translate my multi language app according to user choice.
My main goal here is to show screen on first entry to the app that will decide the user language by his choice.
import React from 'react';
import Login from './login.js';
import Register from './register.js';
import Dashboard from './dashboard.js';
import { NavigationContainer, useNavigation } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { Text, View, Button } from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
const HAS_LAUNCHED = 'hasLaunched';
const ENGLISH = 'en';
const HEBREW = 'he';
function setAppLaunched(en) {
const navigation = useNavigation();
AsyncStorage.setItem(HAS_LAUNCHED, 'true');
AsyncStorage.setItem(en ? ENGLISH : HEBREW, 'true');
navigation.navigate('Login');
}
async function checkIfFirstLaunch() {
try {
const hasLaunched = await AsyncStorage.getItem(HAS_LAUNCHED);
if (hasLaunched === null) {
return (
<View>
<Text>Choose Language</Text>
<Button onPress={() => setAppLaunched(false)} title="Hebrew"/>
<Button onPress={() => setAppLaunched(true)} title="English"/>
</View>
);
}
return false;
} catch (error) {
return false;
}
}
console.log(checkIfFirstLaunch());
const Stack = createStackNavigator();
export default function App() {
return (
<>
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}} initialRouteName="Login">
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Register" component={Register} />
<Stack.Screen name="Dashboard" component={Dashboard} />
</Stack.Navigator>
</NavigationContainer>
</>
);
}
Once checkIfFirstLaunch() is triggered AsyncStorage saves the user language and remember it to the next times he launches the app.
I have created the syntax that will detect if user launched the app for the first time, but how can I actually show that screen?
How to display first launch screen using functional components?
EDIT
export default function App() {
return (
<>
<CheckIfFirstLaunch/>
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}} initialRouteName="Login">
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Register" component={Register} />
<Stack.Screen name="Dashboard" component={Dashboard} />
</Stack.Navigator>
</NavigationContainer>
</>
);
}
If I try that it returns the following
Error: Objects are not valid as a React child (found: object with keys
{_U, _V, _W, _X}). If you meant to render a collection of children,
use an array instead.
The error is because you're rendering a Functional Component that you defined as an async function.
Now I don't know if you want to show the "Choice" and the navigation screens at the same time. If you want that, maintain the structure you have and do this.
function checkIfFirstLaunch() {
const [selected, setSelected] = useState(false);
if (selected) return null;
const verifyHasLaunched = async () => {
try {
const hasLaunched = await AsyncStorage.getItem(HAS_LAUNCHED);
setSelected(hasLaunched != null)
} catch (err) {
setSelected(false);
}
}
useEffect(verifyHasLaunched);
const selectLaunched = (value) => {
setAppLaunched(value);
setSelected(true);
};
return (
<View>
<Text>Choose Language</Text>
<Button onPress={() => selectLaunched(false)} title="Hebrew"/>
<Button onPress={() => selectLaunched(true)} title="English"/>
</View>
);
}
If you need to change the render as in render either the "Choose screen" or the Navigation component
Then you have to change both App and checkIfFirstLaunch;
function checkIfFirstLaunch({ onSelect }) {
const selectLaunched = (value) => {
setAppLaunched(value);
onSelect();
};
return (
<View>
<Text>Choose Language</Text>
<Button onPress={() => selectLaunched(false)} title="Hebrew"/>
<Button onPress={() => selectLaunched(true)} title="English"/>
</View>
);
}
export default class App extends React.Component {
state = {
selected: false;
};
async componentDidMount() {
try {
const hasLaunched = await AsyncStorage.getItem(HAS_LAUNCHED);
this.setState({ selected: hasLaunched != null })
} catch (err) {
// Something went wrong
}
}
render() {
if (!this.state.selected) return <CheckIfFirstLaunch onSelect={() => this.setState({ selected: true })} />
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}} initialRouteName="Login">
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Register" component={Register} />
<Stack.Screen name="Dashboard" component={Dashboard} />
</Stack.Navigator>
</NavigationContainer>
);
}
}
I would like to be able to hide the tabs on a screen using React Native Navigation v5.
I've been reading the documentation but it doesn't seem like they've updated this for v5 and it refers to the < v4 way of doing things.
here is my code:
import Home from './components/Home';
import SettingsScreen from './components/Settings';
import * as React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { createStackNavigator } from '#react-navigation/stack';
const SettingsStack = createStackNavigator();
const ProfileStack = createStackNavigator();
function SettingsStackScreen() {
return (
<SettingsStack.Navigator>
<SettingsStack.Screen name="Settings" component={SettingsScreen} />
</SettingsStack.Navigator>
)
}
function ProfileStackScreen() {
return (
<ProfileStack.Navigator>
<ProfileStack.Screen name="Home" component={Home} />
</ProfileStack.Navigator>
)
}
const Tab = createBottomTabNavigator();
export default function App() {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Home" component={ProfileStackScreen} />
<Tab.Screen name="Settings" component={SettingsStackScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}
Things I have tried:
Accessing the options of the function and hiding that way.
Passing tabBarVisible as a prop to the Screen.
What I am asking for is, what is the correct way of hiding tabs on screens in React Navigation v5.
tabbarvisible-option-is-no-longer-present in react navigation v5 upwards. You can achieve the same behavior by specifying
tabBarStyle: { display: 'none' } in options of the screen you want to hide bottom tab
export default function App() {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Home" component={ProfileStackScreen} />
<Tab.Screen options={{tabBarStyle:{display:'none'}}} name="Settings" component={SettingsStackScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}
Let's suppose that you want to hide tabs when you are entering Settings. Just add navigation in your constructor:
function SettingsStackScreen({ navigation }) {
navigation.setOptions({ tabBarVisible: false })
return (
<SettingsStack.Navigator>
<SettingsStack.Screen name="Settings" component={SettingsScreen} />
</SettingsStack.Navigator>
)
}
This code should work.
You will have to restructure your navigation by having your Tab Navigator nested in the Stack Navigator. Following the details here hiding-tabbar-in-screens
This way it's still also possible to have a Stack Navigator nested in yourTab Navigator. SettingsStack
With this when the user is on the Setting screen and also the Update detail screen, the tab bar is visible but on the Profile screen, the tab bar is not.
import Home from './components/Home';
import Settings from './components/Settings';
import UpdateDetails from './components/UpdateDetails';
import Profile from './components/Profile';
import * as React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { createStackNavigator } from '#react-navigation/stack';
const Stack = createStackNavigator();
const StackSettings = createStackNavigator();
const Tab = createBottomTabNavigator();
function SettingsStack() {
return (
<StackSettings.Navigator>
<StackSettings.Screen name="Settings" component={Settings} />
<StackSettings.Screen name="UpdateDetails" component={UpdateDetails} />
</StackSettings.Navigator>
)
}
function HomeTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={Home} />
<Tab.Screen name="Settings" component={SettingsStack} />
</Tab.Navigator>
);
}
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeTabs} />
<Stack.Screen name="Profile" component={Profile} />
</Stack.Navigator>
</NavigationContainer>
);
}
I had this issue and couldn't find solution even in official docs ( the issues in github resulted to broken links) after some trials and research I found a solution for me To make it happen from the bottom tab navigator component
<Tab.Navigator tabBarOptions={stackOptions} >
<Tab.Screen
name={"market"}
component={MarketNavigator}
options={navigation => ({
// tabBarIcon: ,
tabBarVisible: navigation.route.state.index > 0 ? false : true
})}
/>
</Tab.Navigator>
Hope it helps someone!
2022 Answer - How to hide Bottom Tabs in React Navigation V6
Step 1 - Hiding tab bar in specific screens
Sometimes we may want to hide the tab bar in specific screens in a native stack navigator nested in a tab navigator. Let's say we have 5 screens: Home, Feed, Notifications, Profile and Settings, and your navigation structure looks like this:
function HomeStack() {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Stack.Navigator>
);
}
function App() {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeStack} />
<Tab.Screen name="Feed" component={Feed} />
<Tab.Screen name="Notifications" component={Notifications} />
</Tab.Navigator>
);
}
With this structure, when we navigate to the Profile or Settings screen, the tab bar will still stay visible over those screens.
Step 2 - Solution to Hide Bottom Tabs
Now if we want to show the tab bar only on the Home, Feed and Notifications screens, but not on the Profile and Settings screens, we'll need to change the navigation structure. The way to achieve this is to nest the BottomTabs() as the first route of the stack.
Move your Tabs into a separate function BottomTabs()...
Other routes should be in the main App.js return function...
Make "BottomTabs" the First Route as seen below under the App() return function...
<Stack.Screen name="BottomTabs" component={BottomTabs} />
function BottomTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={Home} />
<Tab.Screen name="Feed" component={Feed} />
<Tab.Screen name="Notifications" component={Notifications} />
</Tab.Navigator>
);
}
function App() {
return (
<Stack.Navigator>
<Stack.Screen name="BottomTabs" component={BottomTabs} />
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Stack.Navigator>
);
}
After re-organizing the navigation structure, now if you navigate to the Profile or Settings screens, the bottom tab bar won't be visible over the screen anymore.
You have API reference exactly for this.
Read: tabBarVisible
The above answer will help you to remove the bottom tabs from the root navigation.If you want to remove bottom tabs from a specific screen like Home Screen or Settings Screen you need to change navigation options dynamically.
For changing navigation options dynamically you will need the concept of:
React.Context
useNavigationState
Context - will dynamically change the navigationOption value i.e. either to hide the bottom Tabs or not. We can choose MobX or Redux to do the same.
UseNavigationState - will help context to know at which screen the user is.
We need to create Context in a separate .js file so that Home.js and Settings.js can access it in all the other screens.
import * as React from 'react';
import { View, Text } from 'react-native'
import { NavigationContainer, useNavigationState, useRoute } from '#react-navigation/native';
const Tab = createBottomTabNavigator();
const Context = React.createContext();
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { createStackNavigator } from '#react-navigation/stack';
import { TouchableOpacity } from 'react-native-gesture-handler';
const SettingsStack = createStackNavigator();
const ProfileStack = createStackNavigator();
function SettingsScreen({ navigation }) {
return (
<View>
<Text>
Setting
</Text>
</View>
);
}
function Home({ navigation }) {
const rout = useNavigationState(state => state);
const { screen, setScreen } = React.useContext(Context);
setScreen(rout.index);
return (
<View>
<TouchableOpacity
onPress={() => {
navigation.navigate("Settings");
}}
>
<Text>
Home
</Text>
</TouchableOpacity>
</View>
);
}
function SettingsStackScreen({ navigation }) {
return (
<SettingsStack.Navigator>
<SettingsStack.Screen name="Settings" component={SettingsScreen} />
</SettingsStack.Navigator>
)
}
function ProfileStackScreen({ navigation }) {
const { screen, setScreen } = React.useContext(Context)
if (screen == 0) {
navigation.setOptions({ tabBarVisible: true })
} else {
navigation.setOptions({ tabBarVisible: false })
}
return (
<ProfileStack.Navigator>
<ProfileStack.Screen name="Home" component={Home} />
<ProfileStack.Screen name="Settings" component={SettingsScreen} />
</ProfileStack.Navigator>
)
}
function BottomNav({ navigation }) {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={ProfileStackScreen} />
<Tab.Screen name="Settings" component={SettingsStackScreen} />
</Tab.Navigator>
);
}
export default function App() {
const [screen, setScreen] = React.useState(0);
return (
<Context.Provider value={{ screen, setScreen }}>
<NavigationContainer>
<BottomNav />
</NavigationContainer>
</Context.Provider>
);
}
Here the screen is a flag that checks the index of the navigation and removes the bottom navigation for all the screen stacked in ProfileStackScreen.
Use You Looking for Nested Screen Visible then Tab Bar Options Should be hide than Use this Simple Condition in StackNavigator Funtions.
function HistoryStack({navigation, route}) {
if (route.state.index === 0) {
navigation.setOptions({tabBarVisible: true});
} else {
navigation.setOptions({tabBarVisible: false});
}
return (
<Historys.Navigator initialRouteName={Routes.History}>
<Historys.Screen
name={Routes.History}
component={History}
options={{headerShown: false}}
/>
<Historys.Screen
name={Routes.HistoryDetails}
component={HistoryDetails}
options={{headerShown: false}}
/>
</Historys.Navigator>
);
}
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs"; // version 5.6.1
import { createStackNavigator } from "#react-navigation/stack"; // version 5.6.2
From my inspection navigation.routes.state.index will have a value when you navigation/push to a second screen so I create a function
const shouldTabBarVisible = (navigation) => {
try {
return navigation.route.state.index < 1;
} catch (e) {
return true;
}
};
and call it in BottomTab.Screen options
<BottomTab.Navigator
initialRouteName='Home'
tabBarOptions={{
activeTintColor: "#1F2B64",
showLabel: false,
}}
>
<BottomTab.Screen
name='Home'
component={HomeNavigator}
options={(navigation) => ({
tabBarIcon: ({ color }) => <TabBarIcon name='home' color={color} />,
tabBarVisible: shouldTabBarVisible(navigation),
})}
/>
</BottomTab.Navigator>
just follow as what the documentation suggests: https://reactnavigation.org/docs/hiding-tabbar-in-screens/