How to jump to the specific initial tab route dynamically route name? - reactjs

I have problem jump to the specific tab navigation base on my dynamic initial route name.
Here is the scenario:
If the user just want to login, The initial route of Auth tab navigation must be on ProductBrowsing
If the user first time in the application and user register in the app, The initial route of Auth tab navigation must be on AddProduct.
Problem: When I login the screen not changing the screen stuck on login the navigation replace is not working.
Handler Login:
const HandlerLoginBtn = async () => {
const user_data = {
mobile_number: username,
password: password
}
await dispatch(LoginAuthentication(user_data)).then((res) => {
if(res?.payload?.message == 'Login Success') {
if(redirection == 'ProductBrowsing') {
navigation.replace('ProductBrowsing')
}
else if(redirection == 'AddProduct') {
navigation.replace('AddProduct')
}
}
})
}
Auth Tab:
function AuthenticateTabNav() {
return (
<Tab.Navigator tabBarOptions={{
inactiveTintColor: '#032363',
labelStyle: { fontSize:12, fontWeight:'500'},
style: {
backgroundColor: '#ffff',
}
}}
>
<Tab.Screen
options={{
tabBarLabel: 'BUY',
tabBarIcon: ({ focused , color, size }) => (
focused ? (
<Image h="4" w="4"
source={require('./assets/tab_icons/buy/blue_buy.png')}></Image>
): (
<Image h="4" w="4"
source={require('./assets/tab_icons/buy/black_buy.png')}></Image>
)
),
}}
name="ProductBrowsing"
component={ProductBrowsingScreen}
/>
<Tab.Screen
options={{
tabBarLabel: 'SELL',
tabBarIcon: ({ focused , color, size }) => (
focused ? (
<Image h="4" w="4"
source={require('./assets/tab_icons/sell/blue_sell.png')}></Image>
): (
<Image h="4" w="4"
source={require('./assets/tab_icons/sell/black_sell.png')}></Image>
)
),
}}
name="AddProduct"
component={AddProductScreen}
/>
</Tab.Navigator>
)
}
Navigation:
const AuthenticatedNav = () => {
const {redirection} = useSelector((state) => state.user)
const [firstScreen, setFirstScreen] = useState('');
console.log(firstScreen)
useEffect(() => {
setFirstScreen(redirection)
}, [])
return (
<Stack.Navigator initialRouteName={firstScreen == '' ? 'ProductBrowsing' : 'AddProduct'} headerMode='none'>
<Stack.Screen component={AuthenticateTabNav} name={firstScreen == '' ? 'ProductBrowsing' : 'AddProduct' } />
</Stack.Navigator>
)
}
Redux State:
builder.addCase(LoginAuthentication.fulfilled, (state, action) => {
if(action.payload?.message == 'Invalid Username Or Password') {
state.auth_access = []
state.isLoggedIn = false
state.isLoading = false
state.redirection = ''
}else {
state.auth_access = action.payload
state.isLoggedIn = true
state.redirection = 'ProductBrowsing'
}
})

I've made a full sample here https://snack.expo.dev/O3vW24UCl
If I understand correctly, you want to navigate from one tab to another depending on the user login state.
Changing the initialRouteName on the main Stack is not useful as you are moving within the tabs in your case.
What you want to do is the same but on the Tab.Navigator.
const Tabs = () => {
const isUserLoggedIn = useSelector((state) => {
return state.isUserLoggedIn;
});
return (
<Tab.Navigator
initialRouteName={isUserLoggedIn ? "AddProduct" : "ProductBrowsing"}
>
<Tab.Screen
options={{
tabBarLabel: 'BUY',
}}
name="ProductBrowsing"
component={ProductBrowsingScreen}
/>
<Tab.Screen
options={{
tabBarLabel: 'SELL',
}}
name="AddProduct"
component={AddProductScreen}
/>
</Tab.Navigator>
);
};
Obviously, in the sample the state is not persisted.

Related

Passing a param using initialParam to another screen but the value of the param received is empty

I've been working three weeks on a project using react-native, I've never used this langage before so it's still new for me.
When I pass "test" as a param to Publication screen, route.params.post displays the right value which is "test" but when I pass {user}, it displays an empty value (route.params.post = {}).
I have no problem when I pass {user} as param to the leftHeader and RightHeaderComponent, I receive the object which is not empty there.
I don't understand the issue here, why do I get an empty value when I pass {user} to another screen using initialParams ?
export default function AppRouterComponent() {
const [user, setUser] = useState({});
useEffect(() => {
getUser()
.then(data => {
return data;
})
.then(data => {
setUser(data);
})
.catch(err => {
console.log(123123);
});
}, []);
const dimensions = useWindowDimensions();
return (
<Drawer.Navigator
initialRouteName="home"
screenOptions={{
drawerType: dimensions.width >= 768 ? "permanent" : "front",
headerLeft: () => <LeftHeaderComponent object = {user}/>,
headerTitle: () => null,
headerRight: () => <RightHeaderComponent object = {user}/>,
}}
drawerContent={(props) => DrawnerWithHeader(props)}
>
<Drawer.Screen name="home" component={HomeScreen}/>
<Drawer.Screen name="View/Edit user info" component={UserInfo} initialParams={{ post: user}}/>
<Drawer.Screen name="user preferences" component={UserSettings} />
<Drawer.Screen name="Publication" component={PublicationScreen} initialParams={{ post: "test"}}/>
</Drawer.Navigator>
);
}
/*The other screen, when I use the debugger to check route.params.post value, it displays an empty object*/
export default function UserInformationScreen({navigation, route}) {
return (
<Card>
<UserInformationComponent object = {route.params.post}/>
</Card>
);
}

React-native: conditional rendering with loginState

I am trying to create a login for a React Native app.
But the conditional rendering is not working, really don't know what I am doing wrong. (I am new to react native)
The isLoggedIn state checks if the user has a valid refreshToken. The token class works only thing that isn't working is the rendering after logging in.
InsightsApiDriver
isValidRefreshToken() {
return (
this.refreshToken !== undefined &&
new Date().getTime() - this.refreshToken.datetime.getTime() <
1 * 60 * 1000
);
}
MainContainer
const [isLoading, setIsLoading] = useState(true);
const [isLoggedIn, setIsLoggedIn] = useState(undefined);
useEffect(() => {
SecureStore.getItemAsync("refreshToken")
.then((tokenJson) => (tokenJson ? JSON.parse(tokenJson) : undefined))
.then((tokenObj) => {
if (tokenObj !== undefined) {
tokenObj.datetime = new Date(tokenObj.datetime);
}
global.AppInsightDriver = new InsightsApiDriver(tokenObj);
setIsLoading(false);
setIsLoggedIn(global.AppInsightDriver.isValidRefreshToken());
});
}, []);
return (
<NavigationContainer>
{console.log(isLoggedIn)}
{isLoggedIn ? (
<Tab.Navigator
initialRouteName={routes.session}
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
switch (route.name) {
case routes.session:
iconName = focused ? "speedometer" : "speedometer-outline";
break;
case routes.settings:
iconName = focused ? "settings" : "settings-outline";
break;
case routes.about:
iconName = focused ? "help-circle" : "help-circle-outline";
break;
case routes.login:
iconName = focused ? "log-in" : "log-in-outline";
default:
iconName = focused ? "log-in" : "log-in-outline";
break;
}
return <Icon name={iconName} size={size} color={color} />;
},
tabBarActiveTintColor: "#FFC62C",
tabBarInactiveTintColor: "white",
headerStyle: { backgroundColor: "#1F2341" },
headerTitleAlign: "center",
headerTintColor: "#FFC62C",
headerTitle: () => (
<HeaderTitle
SmartgridoneLogo={SmartgridoneLogo}
routeName={route.name}
/>
),
tabBarStyle: {
backgroundColor: "#1F2341",
},
})}
>
<Tab.Screen name={routes.session} component={SessionScreen} />
<Tab.Screen name={routes.settings} component={SettingScreen} />
<Tab.Screen name={routes.about} component={AboutScreen} />
</Tab.Navigator>
) : (
<Tab.Navigator
initialRouteName={routes.login}
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
switch (route.name) {
case routes.session:
iconName = focused ? "speedometer" : "speedometer-outline";
break;
case routes.settings:
iconName = focused ? "settings" : "settings-outline";
break;
case routes.about:
iconName = focused ? "help-circle" : "help-circle-outline";
break;
case routes.login:
iconName = focused ? "log-in" : "log-in-outline";
default:
iconName = focused ? "log-in" : "log-in-outline";
break;
}
return <Icon name={iconName} size={size} color={color} />;
},
tabBarActiveTintColor: "#FFC62C",
tabBarInactiveTintColor: "white",
headerStyle: { backgroundColor: "#1F2341" },
headerTitleAlign: "center",
headerTintColor: "#FFC62C",
headerTitle: () => (
<HeaderTitle
SmartgridoneLogo={SmartgridoneLogo}
routeName={route.name}
/>
),
tabBarStyle: {
backgroundColor: "#1F2341",
},
})}
>
<Tab.Screen name={routes.login} component={LoginScreen} />
</Tab.Navigator>
)}
</NavigationContainer>
);
The useEffect hook which you're using is more or less acting like a componentDidMount method. Which means it only runs when the screen is first mounted. Initially when you run the app, the isLoggedIn state gets set as false. But after you're logged in, this state is not being updated or atleast you haven't mentioned this in the code. If you also mention the routes.login method then it'll be easier to debug. Second time when you refresh app, the mount method sets the isLoggedIn state as true because the token is valid. So there's your issue
You need to render the whole navigation container when user re-authenticated by adding isLoggedIn on useEffect dependencies array
useEffect(() => {
SecureStore.getItemAsync("refreshToken")
.then((tokenJson) => (tokenJson ? JSON.parse(tokenJson) : undefined))
.then((tokenObj) => {
if (tokenObj !== undefined) {
tokenObj.datetime = new Date(tokenObj.datetime);
}
global.AppInsightDriver = new InsightsApiDriver(tokenObj);
setIsLoading(false);
setIsLoggedIn(global.AppInsightDriver.isValidRefreshToken());
});
}, [isLoggedIn]);
SecureStore doesnt provides an event listener for value changes so whenever refreshToken changes you need to recalculate the value of isLoggedIn. That would mean prop drilling setIsLoggedIn down to every component where the token is changed, or opting to use some form of state management. (React.useContext is nice)
With that in mind you will be using the following code at least twice, so you might as well put it in its own file (let's call it updateLogin.js), and export it as a function
updateLogin.js
// pass setIsLoggedIn as a parameter
export default updateIsLoggedIn=setIsLoggedIn=>{
SecureStore.getItemAsync("refreshToken")
.then((tokenJson) => (tokenJson ? JSON.parse(tokenJson) : undefined))
.then((tokenObj) => {
if (tokenObj !== undefined) {
tokenObj.datetime = new Date(tokenObj.datetime);
}
global.AppInsightDriver = new InsightsApiDriver(tokenObj);
setIsLoading(false);
setIsLoggedIn(global.AppInsightDriver.isValidRefreshToken());
});
}
Now MainContainer becomes:
import updateLogin from '../helpers/updateLogin';
/*
.
.
.
*/
const [isLoading, setIsLoading] = useState(true);
const [isLoggedIn, setIsLoggedIn] = useState(undefined);
useEffect(() => {
updateLogin(setIsLoggedIn);
}, []);
Then in your LoginScreen in whatever function where you write refreshToken to SecureStore after the write is finished just called updateIsLoggedIn(setIsLoggedIn).
Dont forget to either prop drill setIsLoggedIn.

drawer with logout in react navigation

I am facing one issue in my drawer. I am looking for some help in this. People who are using react navigation 5 version.
i am using the latest react navigation version and my code for custom drawer is below
function CustomDrawerContent(props) {
//
return (
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
<DrawerItem label="Logout" onPress={()=>
Alert.alert(
'Log out',
'Do you want to logout?',
[
{text: 'Cancel', onPress: () => {return null}},
{text: 'Confirm', onPress: () => {
AsyncStorage.clear();
//props.navigation.navigate('Home')
}},
],
{ cancelable: false }
)
} />
</DrawerContentScrollView>
);
}
And my route.js is mentioned below.
export default function Routes(username, password) {
// eslint-disable-next-line no-shadow
const Stack = createStackNavigator();
const [loader, setloader] = useState('');
const [state, dispatch] = React.useReducer(
(prevState, action) => {
switch (action.type) {
case 'RESTORE_TOKEN':
return {
...prevState,
userToken: action.token,
isLoading: false,
};
case 'SIGN_IN':
return {
...prevState,
isSignout: false,
userToken: action.token,
};
case 'SIGN_OUT':
return {
...prevState,
isSignout: true,
userToken: undefined,
};
}
},
{
isLoading: true,
isSignout: false,
userToken: null,
},
);
React.useEffect(() => {
// Fetch the token from storage then navigate to our appropriate place
const bootstrapAsync = async () => {
let userToken;
try {
userToken = await AsyncStorage.getItem('#kiklee-user-id');
} catch (e) {
// Restoring token failed
}
// After restoring token, we may need to validate it in production apps
// This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away.
dispatch({type: 'RESTORE_TOKEN', token: userToken});
};
bootstrapAsync();
}, []);
const authContext = React.useMemo(
() => ({
signIn: async data => {
try {
let config_api_url = Config.api_login_url;
if (data.username === '' && data.password === '') {
Alert.alert(
'Error : ',
'Please enter your email address and password.',
);
} else if (data.username === '') {
Alert.alert('Error : ', 'Please enter your email address.');
} else if (
!(data.username === '') &&
Validate.isEmailValid(data.username) === true
) {
Alert.alert('Error : ', 'Please enter the correct email address.');
} else if (password.length < 5) {
Alert.alert(
'Error : ',
'Please enter your password with a minimum length of 5.',
);
} else {
// seterror(''); //empty all errors
setloader(true);
await fetch(config_api_url, {
method: 'POST',
body: JSON.stringify({
username: data.username,
password: data.password,
}),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
})
.then(response => response.text())
.then(async responseData => {
// parse the json values
var responsevalue = JSON.parse(responseData);
if (responsevalue.login_status === 'Active') {
await AsyncStorage.removeItem('#kiklee-user-id');
await AsyncStorage.setItem(
'#kiklee-user-id',
responsevalue.id.toString(),
);
// const value = await AsyncStorage.getItem('#kiklee-user-id');
dispatch({type: 'SIGN_IN', token: 'dummy-auth-token'});
setloader(false);
// eslint-disable-next-line eqeqeq
} else if (responsevalue.login_status == 'Deactive') {
Alert.alert('Error : ', 'Userid is deactive.');
setloader(false);
} else {
Alert.alert('Error : ', 'Invalid username or password.');
setloader(false);
}
})
.catch(err => {
Alert.alert(err);
});
}
} catch (e) {
// saving error
Alert.alert('Please try your login after some time.');
}
},
signOut: async () => {
AsyncStorage.removeItem('#kiklee-user-id');
dispatch({type: 'SIGN_OUT'});
},
signUp: async data => {
dispatch({type: 'SIGN_IN', token: 'dummy-auth-token'});
},
}),
[],
);
if (state.isLoading) {
// We haven't finished checking for the token yet
return <SplashScreen />;
}
// Loader
if (loader == true) {
return (
<View style={styles.container}>
<Spinner
visible={true}
textContent={'Loading...'}
textStyle={styles.spinnerTextStyle}
/>
</View>
);
}
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer>
{state.userToken == null ? (
<>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
headerShown: false,
animationTypeForReplace: state.isSignout ? 'pop' : 'push',
}}
/>
<Stack.Screen
name="Privacy"
component={Privacy}
options={{headerShown: false}}
/>
<Stack.Screen
name="ForgetPassword"
component={ForgetPassword}
options={{headerShown: true}}
/>
<Stack.Screen
name="SignUp"
component={Signup}
options={{headerShown: false}}
/>
</Stack.Navigator>
</>
) : (
<>
<Drawer.Navigator
initialRouteName="Dashboard"
drawerContent={props => CustomDrawerContent(props)}>
<Drawer.Screen name="Dashboard" component={MainRoot} />
</Drawer.Navigator>
</>
)}
</NavigationContainer>
</AuthContext.Provider>
);
}
i can call authcontext inside the custom drawer because it will display a error called "Hooks rule"
i am looking for a help only with react navigation 5.
Try defining const Stack = createStackNavigator(); above the Routes component.
Your useState and useReducer hooks need to come first.
I had the same issue ,but eventually I created a work around because I tried passing the function as props from different roots and when i logged props from the customer drawer item , it was undefined... so that lead me to a previous solution that I had used with react navigation v4.
Solution:
create your own component for signing out and other functionalities like links etc.
const DrawerMeta = () => {
const { signOut } = useContext(AuthContext);
return (
<View style={{ flex: 1, justifyContent: "flex-end" }}>
<Button
onPress={async () => {
await signOut();
}}
title="sign out"
/>
</View>
);
};
insert into your drawer component
function CustomDrawerContent(props) {
//
return (
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
<DrawerMeta />
</DrawerContentScrollView>
);
}

how to hide bottomtabs when a route 2 levels deep is displayed

so when a certain screen is displayed, i want the bottom tabbar to disappear.
I am using react navigation.
when The insight detail adjustment screen is active, I want the bottomtabs to disappear.
Currently noting is working, only when I type bottomtabs {visible:false} inside the bottomtab navigator it works, but I need it nested one level deeper.
const incidentStack = createStackNavigator({
'incident Overview': {
screen: incidentOverview,
navigationOptions: ({navigation}) => ({
title: 'Pointbreak',
headerLeft: (
<TouchableOpacity onPress ={() => navigation.openDrawer()}>
<Image style={{marginLeft: 10}} source={require('../img/menu.png')}></Image>
</TouchableOpacity>
)
})
},
'insight detail adjustment': {
screen: InsightDetailAdjustment,
navigationOptions: ({navigation}) => ({
header: (
<HeaderTitleInsightDetailAdjustment navigation={navigation}/>
),
bottomTabs: {
visible: false
}
})
}
})
const AppNavigator = createBottomTabNavigator(
{
Insights: {
screen: InsightsStack,
navigationOptions: {
tabBarIcon: ({tintColor}) => (
<Icon name='chart-line-variant' size={30} color={tintColor}/>
),
}
},
Incidents: {
screen: incidentStack,
navigationOptions: ({navigation}) => ({
tabBarVisible: () => (
if(navigation.navigate('incident detail adjustment'){
return false
}else{
return true
})
),
tabBarIcon: ({tintColor}) => (
<Icon name='bullhorn-outline' size={30} color={tintColor}/>
),
})
},
You can try this:
InsightsStack.navigationOptions = ({ navigation }) => {
// hides tabNavigation starting from 2 screen deep in InsightsStack Stack
let tabBarVisible = true;
if (navigation.state.index > 0) {
tabBarVisible = false;
}
return {
tabBarVisible
};
};

using of Custom Tabs with StackNavigator?

I have list of users , each user has its display image in the list.
What I am trying is whenever user presses the display image , get redirected to his/her profile through stackNavigation .
CustomTabView:
const CustomTabView = ({ router, navigation }) => {
const { routes, index } = navigation.state;
const ActiveScreen = router.getComponentForState(navigation.state);
const routeNav = addNavigationHelpers({
...navigation,
state: routes[index],
});
const routeOptions = router.getScreenOptions(routeNav, 'tabBar');
return (
<View style={styles.container}>
<CustomTabBar
navigation={navigation}
activeRouteName={routes[index].routeName}
icon={routeOptions.tabBarIcon}
/>
<ActiveScreen
navigation={addNavigationHelpers({
...navigation,
state: routes[index]
})}
/>
</View>
);
};
StackNavigator: // goToProfile.js // also tried placing in index.anndroid.js but didnt found a way to export
const goToProfile = StackNavigator({
Profile: {
screen: Profile,
navigationOptions: ({ navigation }) => ({
title: `${navigation.state.params.person.name.first} ${navigation.state.params.person.name.last}`
})
},
})
custom tabs: //index.android.js
const CustomTabRouter = TabRouter(
{
Chat: {
screen: Chats,
path: ""
},
Status: {
screen: Contacts,
path: "notifications"
},
Camera: {
screen: Camera,
path: "settings"
}
},
{
initialRouteName: "Chat",
},
);
const CustomTabs = createNavigationContainer(
createNavigator(CustomTabRouter)(CustomTabView)
);
Also my component :
<TouchableOpacity
onPress = { () => this.props.navigation.navigate('Profile', { item } ) } >
<Avatar
source={{ uri: item.picture.thumbnail }}
/>
</TouchableOpacity>
Your Stack Navigator will look like this:
const ChatStack= StackNavigator({
Chat:{screen: Chat,
navigationOptions: {
header: null,
}},
Profile: {
screen: Profile,
navigationOptions: ({ navigation }) => ({
title: `${navigation.state.params.person.name.first} ${navigation.state.params.person.name.last}`,
tabBarVisible: false
// depending if you want to hide the tabBar on the profile page if not remove it.
})
},
})
Then Your customTab will take this shape:
const CustomTabRouter = TabRouter(
{
Chat: {
screen: ChatStack,
},
Status: {
screen: Contacts,
path: "notifications"
},
Camera: {
screen: Camera,
path: "settings"
}
},
.........
With those changes you should be able to navigate to Profile screen with your
this.props.navigation.navigate('Profile', { item } )
You want to dispatch a navigation action, not render a new stack - Although I'm not sure your navigators are properly constructed for this to succeed...
_goToProfile = (person) => {
<goToProfile navigate={this.props.navigation} />
}
should be
_goToProfile = (person) => {
this.props.navigation.navigate('Profile', person)
}

Resources