Basic simple React Navigation v5 navigating between screens - react-native

I've spent 2 days search, reading and find lots of v3 and v4 class based examples for how to handle navigation in React Native Navigation.
All I want to achieve is to move between 2 of my screens using react native navigation. My App.js contains the Tab navigator and that works fine. The tab opens up a component (screen) called Mens and from there I want to be able to open up a PDP page that passes in properties of an article ID.
I have tried numerous ways of wiring up the application to allow this; I've read all the react native documentation and tried a number of approaches;
Created a seperate file to include the naviagtion stack;
import * as React from 'react'
import { NavigationContainer } from '#react-navigation/native'
import { createStackNavigator } from '#react-navigation/stack'
import News from './news';
import Mens from './mens'
import Watch from './watch'
const Stack = createStackNavigator()
function MainStackNavigator() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="News" component={News} />
<Stack.Screen name="Mens" component={Mens} />
</Stack.Navigator>
</NavigationContainer>
)
}
export default MainStackNavigator
But when I try to use one of the screens, I get an error. The onpress I try is;
<TouchableOpacity onPress={() => navigation.navigate('Mens')}>
I have also tried to move the NavigationContainer / Stack Navigator code into the News component, but I haven't manage to make that work.
The flow that I want is simple enough; App.js has my tabs, 5 tabs that navigate to the main screens and then on each of those, people can click on a list item in a flat list (which displays a summary) to read the full article.
The news.js file content is below;
import React, { useEffect, useState } from 'react';
import { ActivityIndicator, FlatList, Image, ListItem, Text, View, StyleSheet, ScrollView, TouchableOpacity, Alert} from 'react-native';
import Moment from 'moment';
import MatchReports from './matchreports.js';
import navigation from '#react-navigation/native';
const News = (props) => {
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState([]);
function chkValue(val) {
if(val.length == 0){
val = 'https://derbyfutsal.files.wordpress.com/2019/07/banner-600x300.png';
}else{
val = val;
}
return val;
}
useEffect(() => {
fetch('https://xxxx')
.then((response) => response.json())
.then((json) => {
setData(json)
})
.catch((error) => console.error(error))
.finally(() => setLoading(false));
}, []);
return (
<ScrollView>
<View style={styles.body}>
<Text style={styles.titlesnopadding}>Watch</Text>
<View style={{height:200}}>
<Watch />
</View>
<Text style={styles.titles}>Match reports</Text>
<View style={{height:100}}>
<MatchReports typeOfProfile='Men'/>
</View>
<Text style={styles.titles}>Latest News</Text>
{isLoading ? <ActivityIndicator/> : (
<FlatList
data={data}
keyExtractor={({ id }, index) => id}
renderItem={({ item }) => (
<View>
<TouchableOpacity onPress={() => navigation.navigate('Mens')}>
<Image style={styles.img} source={{ uri: chkValue(item.jetpack_featured_media_url) }} />
<View>
<Text style={styles.textbck}>{item.title.rendered.replace(/<\/?[^>]+(>|$)/g, "")}</Text>
<Text style={styles.summary}>{item.excerpt.rendered.replace(/<\/?[^>]+(>|$)/g, "")}{"\n"}{Moment(item.date, "YYYYMMDD").fromNow()}</Text>
</View>
</TouchableOpacity>
</View>
)}
/>
)}
</View>
</ScrollView>
);
};
Any help is appreciated, as I've read so many using class instead of functional programming and out of date navigation that it's been challenging working it out.
EDIT:
I had missed the props.navigation.navigate('Mens'), which works fine now. Though its only half solves my problem.
I have the following inside my app.js;
import 'react-native-gesture-handler';
import React from 'react';
import { View, StyleSheet, Image } from 'react-native';
import News from './components/news';
import Shop from './components/shop';
import Mens from './components/mens';
import Fixtures from './components/fixtures';
import Ladies from './components/ladies';
import Pdp from './components/pdp'
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
const Tab = createBottomTabNavigator();
const App = () => {
return (
<View style={styles.header}>
<View>
</View>
<View style={styles.body}>
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="News" component={News} />
<Tab.Screen name="Mens" component={Mens} />
<Tab.Screen
name="icon"
component={Shop}
options={{
title: '',
tabBarIcon: ({size,focused,color}) => {
return (
<Image
style={{ marginTop:20,width: 80, height: 80 }}
source={{
uri:
'https://derbyfutsal.files.wordpress.com/2020/05/derby-futsal-logo-2019.png',
}}
/>
);
},
}}
/>
<Tab.Screen name="Ladies" component={Ladies} />
<Tab.Screen name="Fixtures" component={Fixtures} />
</Tab.Navigator>
</NavigationContainer>
</View>
</View>
)
};
const styles = StyleSheet.create({
header: {
marginTop: 20,
height:0,
flex: 1
},
body: {
flex:2,
flexGrow:2,
},
nav: {
fontSize: 20,
},
});
export default App;
Anything thats been set as tab Screen in this works just fine if I reference it in my news.js screen, but I don't want to declare PDP.js in this as I don't want it to display as a tab.
Instead once a user has gone to a screen using the tab navigation, the user then clicks on a item in the flatlist and it opens up pdp.js.
In many ways, once someone has opened up the main categories (as seen on the tab navigation) and clicked on an item in the flatlist, all I want to do is;
<a href="pdp.js?id=xxxxx">

https://reactnavigation.org/docs/navigation-actions/#navigate
import { CommonActions } from '#react-navigation/native';
navigation.dispatch(
CommonActions.navigate({
name: 'Profile',
params: {
user: 'jane', // props.route.params.user
},
})
);

Related

Having two tab navigation bars in React Navigation

I want to create an app, that has both a fixed bottom and top tab navigation bar.
See image:
After I finished the bottom navigation bar I tried the following in my App.js file:
return(
<NavigationContainer>
<Tab.Navigator> //top navbar
<Tab.Screen />
...
</Tab.Navigator>
<Tab.Navigator> //bottom navbar
<Tab.Screen />
...
</Tab.Navigator>
</NavigationContainer>
)
However, I get the error, that another navigator is already registered in this container and that I should not have multiple navigators under a single NavigationContainer.
I found multiple guides about nesting tab and stack navigators, but how do I nest multiple tab navigators, that both update the central screen?
AFAIK that is not possible without writing a custom navigator. Navigators need to be nested and need to have separate routes, so one tab navigator would need to be nested inside (as a tab of) the other.
Writing a custom navigator is something you definitely could consider. Here is a snack that modifies the example from react-navigation documentation:
https://snack.expo.io/#mlisik/thoughtful-stroopwafels
In the snack, the first two tabs are displayed on top, and remaining on the bottom. You would need to further modify them to match the appearance you are after with some custom options, perhaps reusing internal components from react-navigation.
It is by no means a complete solution, but should give you an idea of what is possible.
For completeness, I include the code here:
// App.js
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createNavigator } from './Navigator';
const Nav = createNavigator()
const Screen1 = () => <View style={{flex: 1, backgroundColor: 'red'}} />
const Screen2 = () => <View style={{flex: 1, backgroundColor: 'green'}} />
const Screen3 = () => <View style={{flex: 1, backgroundColor: 'yellow'}} />
const Screen4 = () => <View style={{flex: 1, backgroundColor: 'brown'}} />
export default function App() {
return (
<NavigationContainer>
<Nav.Navigator>
<Nav.Screen name="Tab 1" component={Screen1} />
<Nav.Screen name="Tab 2" component={Screen2} />
<Nav.Screen name="Tab 3" component={Screen3} />
<Nav.Screen name="Tab 4" component={Screen4} />
</Nav.Navigator>
</NavigationContainer>
)
}
// Navigator.js
// this is only slightly modified from https://reactnavigation.org/docs/custom-navigators#usenavigationbuilder
import * as React from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import {
NavigationHelpersContext,
useNavigationBuilder,
createNavigatorFactory,
TabRouter,
TabActions,
} from '#react-navigation/native';
function TabButton({ route, descriptors, navigation, state }) {
return (
<TouchableOpacity
key={route.key}
onPress={() => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!event.defaultPrevented) {
navigation.dispatch({
...TabActions.jumpTo(route.name),
target: state.key,
});
}
}}
style={{ flex: 1 }}
>
<Text>{descriptors[route.key].options.title || route.name}</Text>
</TouchableOpacity>
)
}
function Navigator({
initialRouteName,
children,
screenOptions,
tabBarStyle,
contentStyle,
}) {
const { state, navigation, descriptors } = useNavigationBuilder(TabRouter, {
children,
screenOptions,
initialRouteName,
});
const renderTab = (route) => (
<TabButton
route={route}
descriptors={descriptors}
state={state}
navigation={navigation}
/>
)
return (
<NavigationHelpersContext.Provider value={navigation}>
<View style={[{ flexDirection: 'row' }, tabBarStyle]}>
{state.routes.slice(0, 2).map(renderTab)}
</View>
<View style={[{ flex: 1 }, contentStyle]}>
{descriptors[state.routes[state.index].key].render()}
</View>
<View style={[{ flexDirection: 'row' }, tabBarStyle]}>
{state.routes.slice(2).map(renderTab)}
</View>
</NavigationHelpersContext.Provider>
);
}
export const createNavigator = createNavigatorFactory(Navigator);

React Navigation Nested No Route Params v5

I can't seem to get any route params in my nested navigator. The params are present in the parent navigator, but they are not reachable in the child navigator.
So the child navigator does render the correct screen, but it does not have any params in the route (namely a category or product id).
It feels like I am misusing some syntax, but I can't quite figure out which one. Here is the stack of code, edited down a bit to make it easier to read.
Snack on Expo
Thank you.
Note: These are separate files so the includes are off.
import * as React from 'react';
import { Text, View, StyleSheet, SafeAreaView } from 'react-native';
import Constants from 'expo-constants';
// You can import from local files
import AssetExample from './components/AssetExample';
// or any pure javascript modules available in npm
import { NavigationContainer } from '#react-navigation/native'
import { createDrawerNavigator, DrawerContentScrollView, DrawerItem } from '#react-navigation/drawer'
import { createStackNavigator } from '#react-navigation/stack'
import { AppearanceProvider } from 'react-native-appearance'
const Drawer = createDrawerNavigator()
const Stack = createStackNavigator()
const HomeScreen = ({ navigation, route }) => {
return(
<View style={styles.container}>
<Text>Home Screen </Text>
</View>
)
}
const CategoryScreen = ({ navigation, route }) => {
return(
<View>
<Text>Category Screen </Text>
<Text>{JSON.stringify(route)}</Text>
</View>
)
}
const ProductScreen = ({ navigation, route }) => {
return(
<View>
<Text>Product Screen </Text>
<Text>{JSON.stringify(route)}</Text>
</View>
)
}
const CustomDrawerContent = ({ props }) => {
return (
<DrawerContentScrollView {...props}>
<DrawerItem
label="Home"
onPress={() => props.navigation.navigate('Home')}
/>
<DrawerItem
label="Category 1"
onPress={() =>
props.navigation.navigate('Main', {
Screen: 'Category',
params: { id: 1 },
})
}
/>
<DrawerItem
label="Category 2"
onPress={() =>
props.navigation.navigate('Main', {
Screen: 'Category',
params: { id: 101 },
})
}
/>
</DrawerContentScrollView>
)
}
const MainNavigator = () => {
return (
<Stack.Navigator>
<Stack.Screen name="Category" component={CategoryScreen} />
<Stack.Screen name="Product" component={ProductScreen} />
</Stack.Navigator>
)
}
const ApplicationNavigator = () => {
return (
<NavigationContainer initialRouteName="Home">
<Drawer.Navigator
drawerContent={(props) => {
return <CustomDrawerContent props={props} />
}}
>
<Drawer.Screen
name="Home"
component={HomeScreen}
/>
<Drawer.Screen
name="Main"
component={MainNavigator}
/>
</Drawer.Navigator>
</NavigationContainer>
)
}
export default function App() {
return <ApplicationNavigator />
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
padding: 8,
backgroundColor: '#FFFFFF',
},
});
UPDATE
I have noted that if I initialize the params (blank, with values, whichever) outside of the custom drawer content first, the above code begins to work as expected.
Very simple and silly fix that a rubber duck could solve.
Screen !== screen. I was passing in an unknown param to navigate.

How to pass data through stack navigation to a specific screen on react native tabs?

I'm creating a bottom tabs in react native, which will have two screens Home and News.
But first, user will need to Sign In and the users data will be passed from the login screen to Home screen. How do i pass those data. By using
navigation.navigate('Home', {Name: Name});
I can successfuly retrieve the data in Homescreen, if I just use two screen(Login and Home in a stack). However, when I change to navigate to the tabs(which includes Home and News), it doesnt work with error 'Undefined is not an object(evaluating 'route.params.Name'.
May you guys show me which part did I miss?
Here's the app.js code.
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import HomeScreen from './homescreen';
import NewsScreen from './newsscreen';
import LoginScreen from './loginscreen';
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { createStackNavigator } from '#react-navigation/stack';
const Tab = createBottomTabNavigator();
function MyTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="News" component={NewsScreen} />
</Tab.Navigator>
);
}
const LoginStack = createStackNavigator();
function App() {
return (
<NavigationContainer>
<LoginStack.Navigator screenOptions={{headerShown: false}}
initialRouteName="Login">
<LoginStack.Screen name="Login"component={LoginScreen}/>
<LoginStack.Screen name="MyTabs" component={MyTabs} />
</LoginStack.Navigator>
</NavigationContainer>
);
}
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Following is the homescreen code:
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default function HomeScreen({route, navigation}) {
var Name = route.params.Name;
return (
<View style={styles.container}>
<Text>{Name}</Text>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
And finally here's the login code:
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
export default function LoginScreen({navigation}) {
const Name = 'Boy';
const login = () => {
navigation.navigate('MyTabs', {Name: 'Boy'});}
return (
<View style={styles.container}>
<Text>LoginScreen</Text>
<TouchableOpacity
onPress={login}
><Text
>LOGIN</Text>
</TouchableOpacity>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
})
I'm trying to learn how to pass data from a screen to another screen, in which the screen is located inside a tab stack. I hope you guys can understand the question and provide me with your opinion and solution. Thanks.
Output:
It needed just this little modification in MyTabs component:
function MyTabs({ navigation, route }) {
const { name } = route.params;
console.log(name);
return (
<Tab.Navigator>
<Tab.Screen
name="Home"
component={() => <HomeScreen name={route.params.name} />}
/>
<Tab.Screen name="News" component={NewsScreen} />
</Tab.Navigator>
);
}
Here is the working solution:
App.js
import * as React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { Text, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
import HomeScreen from './home';
import NewsScreen from './newscreen';
import LoginScreen from './login';
// You can import from local files
const Tab = createBottomTabNavigator();
function MyTabs({ navigation, route }) {
const { name } = route.params;
console.log(name);
return (
<Tab.Navigator>
<Tab.Screen
name="Home"
component={() => <HomeScreen name={route.params.name} />}
/>
<Tab.Screen name="News" component={NewsScreen} />
</Tab.Navigator>
);
}
const LoginStack = createStackNavigator();
function App() {
return (
<NavigationContainer>
<LoginStack.Navigator
screenOptions={{ headerShown: true }}
initialRouteName="Login">
<LoginStack.Screen name="Login" component={LoginScreen} />
<LoginStack.Screen name="MyTabs" component={MyTabs} />
</LoginStack.Navigator>
</NavigationContainer>
);
}
export default App;
LoginScreen
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
import React from "react";
export default function LoginScreen({ navigation }) {
const Name = 'Name From Login Screen';
const login = () => {
console.log("hi");
navigation.navigate('MyTabs', { name : Name });
}
return (
<View style={styles.container}>
<View style={styles.bottomView}>
<TouchableOpacity onPress={login} style={styles.button}>
<Text style={styles.textStyle}>LOGIN</Text>
</TouchableOpacity>
</View>
</View>
);
}
Home.js
import { Text, View, StyleSheet } from 'react-native';
import React from "react";
export default function HomeScreen({route, navigation, name}) {
console.log("***",name)
return (
<View style={styles.container}>
<Text style={styles.name}>{name}</Text>
</View>
);
}
Working Expo Snack example.
It's a little complicated to explain react native screens once you start combining bottomtabnavigator and stack navigator and even drawernavigation.
you may need to create stack navigation inside tab navigation like this.
//creating a separate stack within your tab
const HomeStack = createStackNavigator()
const HomeStackNavigator = () => {
return (
<HomeStack.Navigator screenOptions={{headerShown: false}}
initialRouteName="HomeScreen">
<HomeStack.Screen name="Home"component={HomeScreen}/>
</HomeStack.Navigator>
)
}
const Tab = createBottomTabNavigator();
function MyTabs() {
return (
<Tab.Navigator initialRouteName="HomeStackNavigator">
<Tab.Screen name="HomeStackNavigator" component={HomeStackNavigator} />
<Tab.Screen name="News" component={NewsScreen} />
</Tab.Navigator>
);
}
I believe, different navigators doesn't really talk to each other well. If you are using different navigator be prepare to nest stack navigators inside them.
The idea is, the parent of each display component should be a stack navigator. This will also allow you to better control your screenOptions.

react native navigation move between screens

I'm working with the latest react native navigation and trying to get to the next screen from my icon. Having no luck. I've tried to pass a function into my code and i'm getting no where. I know this is simple, i may just be mising one simple snippet to get this done. Please see below. Does anyone know how to properly write the navigation.
My issue is with the Stack Screen "ProductOverViewScreen".
import * as React from 'react';
import { createStackNavigator, createAppContainer } from '#react-navigation/stack';
import { NavigationContainer } from '#react-navigation/native';
import { HeaderButtons, Item } from 'react-navigation-header-buttons';
import 'react-native-gesture-handler';
import { Platform, Button } from 'react-native';
import { Ionicons } from '#expo/vector-icons';
import ProductsOverViewScreen from '../screens/shop/ProductOverViewScreen';
import ProductDetailScreen from '../screens/shop/ProductDetailScreen';
import CartScreen from '../screens/shop/CartScreen';
import Color from '../constants/Color';
import HeaderButton from '../components/UI/HeaderButton';
const Stack = createStackNavigator();
const newScreen = ({navigation}) => {
navigation.navigate('CartScreen');
}
function ShopNavigator(){
return(
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: Platform.OS === 'android' ? Color.primary : ''
},
headerTitleStyle: {
fontFamily: 'open-sans-bold'
},
headerBackTitleStyle: {
fontFamily: 'open-sans'
},
headerTintColor: Platform.OS === 'android' ? 'white' : Color.primary,
}}
>
<Stack.Screen
name="ProductsOverViewScreen"
component={ProductsOverViewScreen}
options={{
headerRight: ({ navigation}) => (
<Button
onPress={() => navigation.navigate('CartScreen')}
title="Cart"
color='black'
/>
),
}}
/>
<Stack.Screen
name="ProductDetailScreen"
component={ProductDetailScreen}
options={({route}) => ({title: route.params.productTitle})}
/>
<Stack.Screen
name="CartScreen"
component={CartScreen}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
export default ShopNavigator;
I figured it out. hope this helps someone else out. Add the navigation within options like below.
options={({navigation, route}) => ({
headerRight: () => {
return (
<TouchableHighlight onPress={() => {navigation.navigate('CartScreen')}}>
<Text>Test</Text>
</TouchableHighlight>
);
}
})}
/>

How to pass props to React stack navigator

I am doing navigation in the following manner Home.js -> discover-things.js(which includes a top nav-bar) -> Filter-screen.js
In Home.js,I have a stack navigator defined like this---
import { StackNavigator } from 'react-navigation';
import DiscoverScreen from './discover-things';
import TopNavBar from './discover-things/top-nav-bar';
export default StackNavigator({
DiscoverScreen: {
screen: DiscoverScreen,
navigationOptions: { header: TopNavBar },
},
});
In the top-nav-bar, I have this---
import React from 'react';
import {Alert} from 'react-native';
export default ({...props}) => ( //Here the props are undefined
<View style={styles.navbarContainer}>
<View style={[styles.titleContainer, globalStyles.verticallyBottom]}>
<Text style={styles.discoverTitle}>
Discover
</Text>
</View>
<View style={[styles.toolsContainer, globalStyles.verticallyBottom]}>
<View style={[styles.toolsInnerContainer, globalStyles.verticallyBottom]}>
<Icon name="search" size={25} style={styles.icon} />
<Icon name="sliders" size={25} style={styles.icon} onPress={() => {props.navigation.navigate('FilterScreen')}
/>
</View>
</View>
</View>
);
Here I am applying an OnClick on one of the icons, so that It can lead me to the filter screen.
In Filter Screen, I have this----
import { StackNavigator } from 'react-navigation';
import FilterScreen from './view';
export default StackNavigator({
FilterScreen: {
screen:FilterScreen,
navigationOptions: {header : null}
}
})
But on click of the icon in the filter screen, nothing happens. What am I doing wrong here and how to correctly implement it?