Can't pass parameters through React Navigation TabBar - react-native

Can't send any parameters with react-navigation 5x.
Tried everything on documentation. Don't know what's wrong.
I will share you all my route structure.
My ApplicationNavigator.js: I'm using drawer content with tabbar navigator at the same time. OrderNavigator is a tabnavigator, Dashboard is stacknavigator.
const ApplicationNavigator = () => {
const [isApplicationLoaded, setIsApplicationLoaded] = useState(false)
const applicationIsLoading = useSelector((state) => state.startup.loading)
return (
<Drawer.Navigator
drawerStyle={{ width: '100%' }}
drawerContent={(props) => <DrawerContent {...props} />}
>
<Drawer.Screen name="Dashboard" component={DashboardNavigator} />
<Drawer.Screen name="Order" component={OrderNavigator} />
</Drawer.Navigator>
)
}
export default ApplicationNavigator
My OrderNavigator.js: This is my tab.screen structure.
const ScreenA = () => {
return (
.....
<Stack.Navigator>
<Stack.Screen
options={headerStyle_1}
name="Orders"
component={IndexOrderContainer}
/>
</Stack.Navigator>
)
}
const ScreenB = () => {
....
return (
<Stack.Navigator>
<Stack.Screen
options={headerStyle_1}
name="New Order"
component={AddOrderContainer}
/>
</Stack.Navigator>
)
}
const OrderNavigator = () => {
return (
<Tab.Navigator headerMode={'none'}>
<Tab.Screen name="OrderList" component={ScreenA} />
<Tab.Screen name="NewOrder" component={ScreenB} />
</Tab.Navigator>
)
}
export default OrderNavigator
I'm trying to make redirect with code below. I'm using it with my order listing page, and trying to redirect to "order detail" page onclick. (with tab navigator.)
<TouchableOpacity
onPress={() =>
navigation.navigate('NewOrder', {
params: { user: 'jane' },
})
}
style={[styles.ListItem]}
button
key={`order${index}`}
>
And my response, when try to navigate with parameters is.
But you can see that my params is "undefined" with response.
const AddOrderContainer = ({ route, navigation }) => {
useEffect(() => {
console.log(route)
}, [route])
....
....
Object {
"key": "New Order-mafygLVPzFO1rVOhj1zVH",
"name": "New Order",
"params": undefined,
}

Related

React Navigation Authentication Flow: The action 'NAVIGATE' with payload {"name":"HomeScreen"} was not handled by any navigator

A beginner at React Native here, trying to combine this doc with AWS Amplify Authentication to implement React Navigation Authentication Flow but I can't seem to figure out what's wrong. Whenever I click on the login button, this error appears.
Navigation Code (excluding imports):
const NavigationGeneral = () => {
const [user, setUser] = useState(undefined);
const checkUser = async () => {
try {
const authUser = await Auth.currentAuthenticatedUser({bypassCache: true});
setUser(authUser);
} catch (e) {
setUser(null);
}
}
useEffect(() => {
checkUser();
}, []);
if (user === undefined) {
return (
<View style = {{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<ActivityIndicator />
</View>
)
}
return (
<Stack.Navigator screenOptions={{headerShown: false}}>
{user ? (
<>
<Stack.Screen name = "Login" component = {LoginScreen} />
<Stack.Screen name = "SignUp" component = {SignUpScreen} />
<Stack.Screen name = "ConfirmEmail" component = {ConfirmEmailScreen} />
<Stack.Screen name = "ForgotPassword" component = {ForgotPasswordScreen} />
<Stack.Screen name = "NewPassword" component = {NewPasswordScreen} />
</>
): (
<Stack.Screen name = "HomeScreen" component = {HomeTabNavigator} />
)}
</Stack.Navigator>
);
};
//* TAB NAVIGATOR FOR APP SCREENS
const HomeTabNavigator = () => {
return (
<Tab.Navigator
initialRouteName="Home"
screenOptions={{
headerShown: false,
tabBarStyle: {backgroundColor: '#0052cc'},
tabBarInactiveTintColor: '#fff',
tabBarActiveTintColor: '#fff',
tabBarActiveBackgroundColor: '#006600'
}}>
<Tab.Screen
name="Home"
component={HomeScreen}
options={{
tabBarLabel: 'Home',
tabBarIcon: ({ color, size }) => (
<Ionicons name="home-outline" color={'#fff'} size={25} />
),
}}
/>
<Tab.Screen
name="Courses"
component={CourseScreen}
options={{
tabBarLabel: 'Courses',
tabBarIcon: ({ color, size }) => (
<Ionicons name="library-outline" color={'#fff'} size={25} />
),
}}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarLabel: 'My Profile',
tabBarIcon: ({ color, size }) => (
<Ionicons name="person-outline" color={'#fff'} size={25} />
),
}}
/>
<Tab.Screen
name="Forensic Tools"
component={ForensicToolsScreen}
options={{
tabBarLabel: 'Tools List',
tabBarIcon: ({ color, size }) => (
<Ionicons name="list-outline" color={'#fff'} size={25} />
),
}}
/>
<Tab.Screen
name="Leaderboard"
component={LeaderboardScreen}
options={{
tabBarLabel: 'Leaderboard',
tabBarIcon: ({ color, size }) => (
<Ionicons name="podium-outline" color={'#fff'} size={25} />
),
}}
/>
<Tab.Screen
name="Settings"
component={SettingsScreen}
options={{
tabBarLabel: 'Settings',
tabBarIcon: ({ color, size }) => (
<Ionicons name="settings-outline" color={'#fff'} size={25} />
),
}}
/>
</Tab.Navigator>
);
};
export default NavigationGeneral;
LoginScreen.js code snippet:
const onLoginPressed = async data => {
if (loading) {
return;
}
setLoading(true);
try {
await Auth.signIn(data.username, data.password);
navigation.navigate("HomeScreen");
console.log("Login");
} catch (e) {
Alert.alert('Oops', e.message);
}
setLoading(false);
};
I understand that I'm not supposed to manually navigate using navigation.navigate() but rather to conditionally define the screens. But whenever I remove navigation.navigate("HomeScreen"); from the code, nothing happens when I press the login button. So I assume something is wrong with my conditioning in my NavigationGeneral code, I just can't seem to figure out the problem.
Some help or additional tips would be greatly appreciated, thanks in advance. Please let me know if more info is required.
When you try to navigate to the HomeScreen I assume the user is not yet set in the state and therefore the screen with the name HomeScreen does not exist yet, so the navigator has nowhere to go.
Try setting the user in the NavigationGeneral upon login, it should event redirect automatically without using navigation.navigate.
You should not trigger navigation manually when implementing authentication flow with react-navigation. After the successful login, user will be truthy value. This means conditional rendering inside of navigation will handle it automatically.
Moreover, in case of condition user===undefined do not render LoadingIndicator, instead, create a new loading state and render LoadingIndicator when the state loading becomes true.
And I believe it should be !user instead of user, since not authenticated user (user===undefined) will want to see Login, Signup etc. screens.
return (
<Stack.Navigator screenOptions={{headerShown: false}}>
// Here it should be !user
{!user ? (
<>
<Stack.Screen name = "Login" component = {LoginScreen} />
<Stack.Screen name = "SignUp" component = {SignUpScreen} />
<Stack.Screen name = "ConfirmEmail" component = {ConfirmEmailScreen} />
<Stack.Screen name = "ForgotPassword" component = {ForgotPasswordScreen} />
<Stack.Screen name = "NewPassword" component = {NewPasswordScreen} />
</>
): (
<Stack.Screen name = "HomeScreen" component = {HomeTabNavigator} />
)}
</Stack.Navigator>
);
const [checkAuthUserLoading, setCheckAuthUserLoading] = React.useState(false);
const user = useSelector((state) => state.user); // Retrieve user from redux store. (It can be mobx, zustand or any other state management.)
const checkUser = async () => {
try {
setCheckAuthUserLoading(true);
const authUser = await Auth.currentAuthenticatedUser({
bypassCache: true,
});
setCheckAuthUserLoading(false);
dispatch(setAuthUser(user)); // I would suggest to use state management such as redux or mobx instead of storing user in component state
} catch (e) {
dispatch(setAuthUserError(e)); // Here you do not need to setUser(undefined) since its already failed and will be stay as undefined
}
};
if (checkAuthUserLoading) {
return <LoadingIndicator />;
}

How to navigate to other screen after the whole app has loaded?

Good day! Currently learning Nav 5 and so far this is what I have tried. I want to navigate to other screens depending on the result of the Firebase login check.
But what happens is the app renders 3times in the Splash screen, what I want is the 3rd or the final data. How can I know when the app is done re-rendering stuff.
This is what I have tried and I am fairly new to React navigation hopefully somebody could point me on how to do this properly. Thank you!
APP.JS
useEffect(() => {
const subscriber = firebase.auth().onAuthStateChanged(user => {
let logged = false
if (user) {
logged = true
firebase.firestore().collection('users')
.doc(user.uid)
.get()
.then(firestoreDocument => {
if (!firestoreDocument.exists) {
return
}
const user = firestoreDocument.data()
console.log("subs data", user)
setUser(user)
})
.catch(error => {
alert(error)
})
} else {
setUser(null)
logged = false
}
setIsLoggedIn(logged)
setIsLogIncheckDone(true)
})
return subscriber // unsubscribe on unmount
}, [])
const getSomething = async () => {
const data = {
isLoggedIn, user, isLogInCheckDone
}
return data
}
if (!isLogInCheckDone) {
return <SplashScreen />
}
return (
<AppContext.Provider value={{ GetSomething: getSomething }}>
<ThemeContextProvider>
<ThemeContextConsumer>
{context =>
(
<NavigationContainer>
<Stack.Navigator theme={context.theme ? "dark" : "light"} >
<Stack.Screen name={"Splash"} component={SplashScreen} options={{ headerShown: false }} />
<Stack.Screen name={"Common"} component={CommonStackNav} options={{ headerShown: false }} />
<Stack.Screen name={"Auth"} component={AuthStackNav} options={{ headerShown: false }} />
<Stack.Screen name={"Dashboard"} component={DashboardStack} options={{ headerShown: false }} />
<Stack.Screen name={"Verify"} component={VerificationStackNav} options={{ headerShown: false }} />
<Stack.Screen name={"Wizard"} component={ProfileWizardStack} options={{ headerShown: false }} />
</Stack.Navigator>
<Toast ref={(ref) => Toast.setRef(ref)} />
</NavigationContainer>
)
}
</ThemeContextConsumer>
</ThemeContextProvider>
</AppContext.Provider>
)
}
SPLASHSCREEN.JS
export default function SplashScreen({ navigation, route }) {
const appContext = useContext(AppContext)
useEffect(() => {
const newData = appContext?.GetSomething()
console.log("Splashy", newData)
//TODO Navigate here
}, [appContext])
return (
<View style={styles.container}>
<LottieView source={newSource} autoPlay loop />
</View>
)
}
You can use the logged state in your app.js to render what you want. Try something like this:
return (
<NavigationContainer >
{logged ? <Screens you want to render if the user is logged /> : <SplashScreen />}
</NavigationContainer>
);

Dynamic items in Drawer nested in Stack Navigator react-navigation

i`m realy stumped about this.
I have a bottom tabs width 4 screens
BottomTabs.js
export const AllTabs = ({ navigation }) => {
return (
<Tab.Navigator initialRouteName="Home" tabBar={props => <BottomNavigation {...props} />}>
<Tab.Screen name="Home" component={HomeStack} options={{
icon: 'shopping-store'
}}/>
<Tab.Screen name="Catalog" component={CatalogStack} options={{
icon: 'pulse'
}}/>
<Tab.Screen name="Cart" component={CartStack} options={{
icon: 'shopping-basket'
}} />
<Tab.Screen name="Profile" component={ProfileStack} options={{
icon: 'person'
}}/>
</Tab.Navigator>
)
}
In first Screen named Home, in HomeStack:
export const HomeStack = () => {
return (
<Stack.Navigator screenOptions={screenOptions} >
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="SectionDrawer" component={SectionDrawer} />
<Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
)
}
I calling drawer component in second screen
Drawer
export const SectionDrawer = (props) => {
const { route, navigation } = props
const { list } = route.params
return (
<Drawer.Navigator drawerContentOptions={{ activeBackgroundColor: '#5cbbff', activeTintColor: '#ffffff' }} >
<Drawer.Screen name="Section" component={SectionScreen} options={{ route, navigation }}/>
</Drawer.Navigator>
)
}
And finally in SectionScreen i`m fetcing data with api by id
SectionScreen
export const SectionScreen = (props) => {
console.log(props);
const {route, navigation} = props
const { sectionID } = route.params
const [isLoading, setLoading] = useState(true);
const [productList, setProductList] = useState([]);
useEffect(() => {
fetch('url, {
method: 'GET'
})
.then((response) => response.json())
.then((json) => setProductList(json))
.catch((error) => console.error(error))
.finally(() => setLoading(false));
}, []);
return (
<Content padder>
<Text>section screen.</Text>
<Text>Requested id {sectionID}</Text>
{isLoading ? (
<Spinner />
) : (
<ProductCards list={productList} perLine={2} navigation={navigation}/>
)}
</Content>
)
}
I calling SectionScreen in drawer with this construction:
<CardItem cardBody button onPress={() => {
navigation.dispatch(
CommonActions.navigate({
name: 'SectionDrawer',
params: {
screen: 'SectionScreen',
params: {
title: card.title,
sectionID: card.id
}
},
})
);
}}>
All of this works ... but I need to push in drawer dynamic links fetched from api
I am tried: in Drawer
<Drawer.Navigator drawerContentOptions={{ activeBackgroundColor: '#5cbbff', activeTintColor: '#ffffff' }} >
{list.map((item, index) => <Drawer.Screen name={item.title} component={SectionScreen} key={index}/>)}
</Drawer.Navigator>
and in place where I calling SectionScreen:
<CardItem cardBody button onPress={() => {
navigation.dispatch(
CommonActions.navigate({
name: 'SectionDrawer',
params: {
list,
screen: card.title,
params: {
title: card.title,
sectionID: card.id
}
},
})
);
}}>
After this I see items that I passed in list param,
but when i try to navigate to them, i have error in SectionScreen route.param.id is not defined, and route.params is undefined too.
Also i trying to fetch data that I need in drawer, in drawer component, and draw it with
drawerContent={props => <CustomDrawerContent {...props} />}
and render them with:
{catalogSections.map((item, index) => <DrawerItem label={item.title} focused onPress={() => drawerNavigate(navigation, item)} key={index}/>)}
and it works fine but i cannot select active item from them...
help me please how right to add items to drawer and navigate always to one component with different params

React navigation 5 stack navigator and drawer navigator together

Basically I am from react-navigation v4, I am familiar handling the same with SwitchNavigator as bridge, but v5 does no support for SwitchNavigator, so bit struggling to understand the below implementation.
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
function AuthenticationStack({ navigation }) {
return (
<Stack.Navigator initialRouteName="Home" screenOptions={{headerShown: false}}>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Registration" component={Registration} />
</Stack.Navigator>
);
}
function UserStack({ navigation }) {
return (
<Stack.Navigator initialRouteName="Jobs" screenOptions={{headerShown: false}}>
<Stack.Screen name="Jobs" component={Profile} />
<Stack.Screen name="JobDetails" component={JobDetails} />
</Stack.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Drawer.Navigator drawerContent={props => <SideBar { ...props } />}>
<Drawer.Screen name="Home" component={AuthenticationStack} />
<Drawer.Screen name="Jobs" component={UserStack} />
</Drawer.Navigator>
</NavigationContainer>
);
}
export default App;
The above code works but have some problems like,
While loading the app, initially the drawer is visible and goes hidden when the app loaded.
I do not want to have drawers for Authentication screens, If I have 2 different navigation without splitting AuthenticationStack and UserStack then I am facing problem while navigating from Login to Profile and vice versa
UPDATE 1
export default() => {
const [logged, setUser] = React.useState(false);
return (
<NavigationContainer>
{
logged
?
<DrawerScreen initialParams={{ setUser }} />
:
<AuthStackScreen initialParams={{ setUser }} />
}
</NavigationContainer>
);
}
Now in login.js, I need to update setUser to true, right? If yes how can I access setUser in my login.js file
Update 2
class Login extends Component {
fetch(login_url, {
method: "POST",
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Accept-Language': 'en-US' },
body: JSON.stringify(data)
})
.then((response) => {
const success = response.ok;
const data = response.json();
return Promise.all([success, data]);
})
.then(([success, response]) => {
var data, userInfo;
if (success) {
data = {
token: response.token,
user: response.user,
}
if(_storeUserData(response)) {
_retrieveUserData().then((userInfo) => {
this.setState({
logged: true,
userInfo: userInfo,
loading:false,
email: '',
password: ''
});
navigate('Profile');
})
.catch((error) => {
// alert(error);
});
}
}
this.setState(state_data);
})
.catch((error) => {
alert('Error:'+error);
this.setState({ loading: false});
});
}
You can create something similar to the switch navigator by conditionally rendering navigators like below.
export default function App() {
const [loggedIn, setLoggedIn] = React.useState(false);
return (
<NavigationContainer>
{loggedIn ? (
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeScreen} initialParams={{ setLoggedIn }}/>
<Drawer.Screen name="Notifications" component={NotificationsScreen} />
</Drawer.Navigator>
) : (
<Stack.Navigator>
<Stack.Screen
name="Auth"
component={AuthScreen}
initialParams={{ setLoggedIn }}
/>
</Stack.Navigator>
)}
</NavigationContainer>
);
}
You can tryout this snack for a working example
https://snack.expo.io/#guruparan/switchsample

Is is possible to use navigation.toggleDrawer() in navigation options

In my navigation file , when I want to toggle drawer , get the following error :
TypeError: navigation.openDrawer is not a function.(In
'navigation.openDrawer()', 'navigation.openDrawer' is undefined)
This is my drawer:
const DrawerNavigator = () => {
return (
<Drawer.Navigator
initialRouteName="MYSHIFT"
>
<Drawer.Screen name="MYSHIFT" component={TopTabNavigator} />
</Drawer.Navigator>
)
}
And this is my container navigation :
const CareworkerNavigation = () => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Login"
component={LoginScreen}
options={{ headerShown: false }} />
<Stack.Screen
name="Main"
options={({ navigation }) => {
return {
headerLeft: () => <Button title="LEFT BUTTON" onPress={() => {
navigation.toggleDrawer(); // <--- this line throws an error
}} />
}
}}
component={DrawerNavigator} />
</Stack.Navigator>
</NavigationContainer>
)
}
export default CareworkerNavigation
Why I can not use navigation.toggleDrawer() in navigation options?
Is is possible to remove this problem ?
If you check the React Navigation docs, "You will need to make the drawer navigator the parent of any navigator where the drawer should be rendered on top of its UI."
React Navigation docs reference
To answer your question : Yes , it is possible.
And here you have a working example:
import React from 'react'
import { Button, View } from 'react-native'
import { NavigationContainer } from '#react-navigation/native'
import { createDrawerNavigator } from '#react-navigation/drawer'
import { createStackNavigator } from '#react-navigation/stack'
const Feed = () => <View />
const Notifications = () => <View />
const Profile = () => <View />
const FeedStack = createStackNavigator()
const Home = ({ navigation }) => (
<FeedStack.Navigator>
<FeedStack.Screen
name="Feed"
component={Feed}
options={props => {
const { toggleDrawer } = props.navigation // <-- drawer's navigation (not from stack)
return {
headerLeft: () => <Button title="LEFT BUTTON" onPress={toggleDrawer} />
}
}}
/>
</FeedStack.Navigator>
)
const Drawer = createDrawerNavigator()
export default props => {
return (
<NavigationContainer>
<Drawer.Navigator initialRouteName="Feed">
<Drawer.Screen
name="Feed"
component={Home}
options={{ drawerLabel: 'Home' }}
/>
<Drawer.Screen
name="Notifications"
component={Notifications}
options={{ drawerLabel: 'Updates' }}
/>
<Drawer.Screen
name="Profile"
component={Profile}
options={{ drawerLabel: 'Profile' }}
/>
</Drawer.Navigator>
</NavigationContainer>
)
}
While constructing navigation at options, you refer to the navigation of the stack, what cant perform draw actions, try to construct it on header itself
<Stack.Screen
name="Main"
options={() => {
return {
headerLeft: (navigation) => <Button title="LEFT BUTTON" onPress={() => {
navigation.toggleDrawer(); // <--- this line throws an error
}} />
}
}}
component={DrawerNavigator} />
https://github.com/react-navigation/react-navigation/issues/55