Dynamic items in Drawer nested in Stack Navigator react-navigation - react-native

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

Related

React Native Drawer Navigation show headerLeft

I have a Drawer navigator which is inside a Stack navigator and I'd like to display a header. Currently I can display everything I want however because the header is defined at the Stack level the navigation inside the header is stack level not drawer level which is preventing me from opening the drawer.
Root stack
<Stack.Navigator
initialRouteName={"Splash"}
screenOptions={{}}
component={SplashScreen}
>
{ auth ?
<Stack.Screen name="Drawer" component={DrawerStack} options={({ navigation }) => ({
title: 'My App',
headerLeft: () => (
<HeaderLeft navigation={ navigation } />
),
headerRight: () => (
<HeaderRight navigation={ navigation } />
),
headerTitleAlign: 'center',
headerTintColor: 'white',
headerStyle: {
backgroundColor: '#5742f5'
},
})} />
:
<Stack.Screen name="Auth" component={AuthStack} options={{
headerShown: false
}}/>
}
</Stack.Navigator>
Drawer stack
<Drawer.Navigator options={{
headerShown: true,
headerLeft: () => (
<HeaderLeft navigation={ navigation } />
),
}}>
<Drawer.Screen
name="Conversations"
options={{
title: props.title,
}}
component={ChatListScreen}
/>
<Drawer.Screen
name="ChatRoom"
options={{
drawerLabel: () => null,
title: null,
drawerIcon: () => null
}}
component={ChatRoomScreen}
/>
</Drawer.Navigator>
Note in the drawer navigator the line with headerLeft does nothing and is there to show where I attempted to put it thinking it would work. I did think it might be overlaying the stack one so I commented out the stack one and it didn't work.
HeaderLeft
export default function HeaderLeft ({ navigation }) {
const openMenu = () => {
navigation.toggleDrawer();
}
return (
<View style={styles.header}>
<Icon name='menu' onPress={openMenu} size={28} style={styles.icon} color="white"/>
</View>
)
}
My question is how can I refactor this to enable me to have the HeaderLeft component work to open the drawer. I will be adding more screens so ideally something I don't have to pass to each screen but if that is what works I am good with it too.
Options in DrawerStack not work. I modified it:
<Drawer.Navigator
screenOptions={{
headerLeft: () => <HeaderLeft />,
}}>
// ...
</Drawer.Navigator>
And a little change in HeaderLeft:
import { useNavigation } from '#react-navigation/native';
function HeaderLeft() {
const navigation = useNavigation();
const openMenu = () => {
navigation.toggleDrawer();
};
// render your Button
}
Demo: https://snack.expo.dev/#pqv2210/0d613b

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 />;
}

Can't call a nested navigation screen from a deep component in react native

I have followed this tutorial and created Drawer and Main Tab Navigations for my react native expo app.
Now I need a screen which should not be listed in Drawer or Tab and which is needed to be called from a deep component where navigations props are not being sent.
I tried useNavigation Hook but got error where react native is unable to find any such screen name.
PFB the tentative sample codes:
Main Tab called from App.js
const Tab = createMaterialBottomTabNavigator();
const HomeStack = createStackNavigator();
const ProfileStack = createStackNavigator();
const ListStack = createStackNavigator();
const MainTabScreen = () => (
<Tab.Navigator
initialRouteName="Home"
activeColor="#fff"
barStyle={{ backgroundColor: '#009387' }}
>
<Tab.Screen
name="Home"
component={HomeStackScreen}
options={{
tabBarLabel: 'Home',
tabBarColor: '#009387',
tabBarIcon: ({ color }) => (
<Icon name="home" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Profile"
component={ProfileStackScreen}
options={{
tabBarLabel: 'Profile',
tabBarColor: '#1f65ff',
tabBarIcon: ({ color }) => (
<Icon name="aperture" color={color} size={26} />
),
}}
/>
</Tab.Navigator>
);
export default MainTabScreen;
const HomeStackScreen = ({navigation}) => (
<HomeStack.Navigator screenOptions={{
headerStyle: {
backgroundColor: '#009387',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold'
}
}}>
<HomeStack.Screen name = "Home" component = {Home}
options = {{
title: 'Overview',
headerLeft: () => (
<Icon.Button name="menu" size={25}
backgroundColor="#009387" onPress={()=>navigation.openDrawer()}
></Icon.Button>
)
}}
/>
</HomeStack.Navigator>
);
const ProfileStackScreen = ({navigation}) => (
<ProfileStack.Navigator screenOptions={{
headerStyle: {
backgroundColor: '#d02860',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold'
}
}}>
<ProfileStack.Screen name = "Profile" component = {Profile}
options = {{
headerLeft: () => (
<Icon.Button name="menu" size={25}
backgroundColor="#d02860" onPress={()=>navigation.openDrawer()}
></Icon.Button>
)
}}
/>
</ProfileStack.Navigator>
);
const ListStackScreen = ({navigation}) => (
<NotificationsStack.Navigator screenOptions={{
headerStyle: {
backgroundColor: '#694fad',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold'
}
}}>
<ListStack.Screen name = "List" component = {List}
options = {{
headerLeft: () => (
<Icon.Button name="menu" size={25}
backgroundColor="#694fad" onPress={()=>navigation.openDrawer()}
></Icon.Button>
)
}}
/>
</NotificationsStack.Navigator>
);
useNavigation component section:
import { useNavigation } from '#react-navigation/native';
.
.
.
const SomeStuff = ({item}) => {
......
<ListButton title={Count} screenName="ListStackScreen" />
.....
}
.
.
function ListButton({ title, screenName }) {
const navigation = useNavigation();
return (
<Button
title={`${title} Total`}
onPress={() => navigation.navigate(screenName)}
/>
);
}
also tried:
navigation.navigate('HomeDrawer',{screen: screenName})
I need to call the above ListStackScreen from a deep component. I tried using navigation.navigate(ListStackScreen) but it doesn't work as explained above.
Please let me know how to use the screen without displaying it in any Drawer or Tab visually.
Edit: Update after trying the given answer
I do have this in the main app.js also:
<Drawer.Screen name="HomeDrawer" component={MainTabScreen} />
This setup should allow you to navigate between HomeStackScreen and ListStackScreen.
const Stack = createStackNavigator();
function HomeStack() {
return (
<Stack.Navigator
initialRouteName="HomeStackScreen">
<Stack.Screen
name="HomeStackScreen"
component={HomeStackScreen}
/>
<Stack.Screen
name="ListStackScreen"
component={ListStackScreen}
/>
</Stack.Navigator>
);
}
<NavigationContainer>
<Tab.Navigator
initialRouteName="HomeStack">
<Tab.Screen
name="HomeStack"
component={HomeStack}
}}
/>
…
You can nest stacknavigator into Drawer, the inner stacknavigator screen won‘t be shown in Drawer.
I have created an sample

Can't pass parameters through React Navigation TabBar

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,
}

Statically setting stack navigator's header title

Suppose I have 2 navigators as follow:
const StackNavigator = () => {
const theme = useTheme();
return (
<Stack.Navigator
initialRouteName="BottomTabs"
headerMode="screen"
screenOptions={{
header: Header,
}}>
<Stack.Screen
name="BottomTabs"
component={BottomTabs}
options={({route, navigation}) => {
console.log('get bottom tab title', route, navigation)
const routeName = route.state
? route.state.routes[route.state.index].name
: 'NOTITLE';
return {headerTitle: routeName};
}}
/>
</Stack.Navigator>
);
};
This Stack navigator loads BottomTabs which is another navigator:
const BottomTabs = props => {
const theme = useTheme();
const tabBarColor = theme.dark
? overlay(6, theme.colors.surface)
: theme.colors.surface;
return (
<Tab.Navigator
initialRouteName="TaskList"
shifting={true}
activeColor={theme.colors.primary}
inactiveColor={color(theme.colors.text)
.alpha(0.6)
.rgb()
.string()}
backBehavior={'initialRoute'}
sceneAnimationEnabled={true}>
<Tab.Screen
name="TaskList"
component={TaskListScreen}
options={{
tabBarIcon: ({focused, color}) => (
<FeatherIcons color={color} name={'check-square'} size={23} />
),
tabBarLabel: 'InboxLabel',
tabBarColor,
title: 'Inbo title',
}}
/>
<Tab.Screen
name="Settings"
component={SettingsScreen}
options={{
tabBarIcon: ({focused, color}) => (
<FeatherIcons color={color} name={'settings'} size={23} />
),
tabBarLabel: 'SeetingsLabel',
tabBarColor,
title: 'Settings title',
}}
/>
</Tab.Navigator>
);
};
I want to change Stack header title based on the screen loaded from BottomTabs. trying to pass the title option to individual screen in BottomTabs didn't work.
How can I change Stack navigator's title based on the screen loaded from the child?
You can customize headerTitle like this:
<Stack.Screen
name="BottomTabs"
component={BottomTabs}
options={({route}) => {
let title;
const routeName = route.state
? route.state.routes[route.state.index].name
: route.params && route.params.screen
? route.params.screen
: 'TaskList';
switch (routeName) {
case 'TaskList':
title = 'Tasks screen';
break;
case 'Settings':
title = 'Settings screen';
break;
default:
return routeName;
}
return {headerTitle: title};
}}
/>
important note: route.state is undefined before any navigate. after that, stack creats it's on navigation state and your screen name prop is available.
Reference