React Native navigating between two screens back & forth - react-native

I have Home screen where we search for an ItemCode, after we search ItemCode it will get details from API & screen will navigate to Screen 1(Item Details Screen) & from Screen 1 we have a search for ItemNutrients and after search it will get Nutrients & Navigate to Screen 2 (Nutrients Screen)
In other words
Home -> Screen 1 (Item Details Screen) -> Screen 2 (Nutrients Screen)
After getting the necessary details from API from search for Item Details & Nutrients, User can navigate between Item Details & Nutrients back and forth..
I could navigate from Screen 1 (item Details) to Screen2 (Nutrients Screen) and swipe back to Screen 1 (item Details) but how could I swipe forward to look at Screen 2 (Nutrients Screen) again without searching for nutrients in Screen 1 as I already have the data from API for Nutrients.
Any ideas on how to implement this ? this is basically navigate to Screen 1 to Screen 2 on search from Screen 1 or if Screen 2 search is already done and we have data so swipe forward should navigate to Screen 2, I can have a button to navigate to Screen 2, the button on Screen 1 conditinally appears only when we have API data for Nutrients Screen and it will navigate to Screen 2 on that button click, but i need swipe functionality as well to navigate forward
I have Home, Screen 1, Screen 2 in stackNavigator, is it alright or i could use any other navigators to achieve this.
I have researched how to do this and can't find it in the docs anywhere. Please let me know if I am missing it somewhere. Thanks!
Nutrition Context:
import React, { createContext, useState, useEffect } from "react";
export const NutriSyncContext = createContext();
const DEFAULT_IS_NUTRI_SCANNED = false;
export const NutriSyncContextProvider = ({ children }) => {
const [isNutritionScanned, setisNutritionScanned] = useState(
DEFAULT_IS_NUTRI_SCANNED
);
// When we first mount the app we want to get the "latest" conversion data
useEffect(() => {
setisNutritionScanned(DEFAULT_IS_NUTRI_SCANNED);
}, []);
const nutrionScanClick = ({ navigation }) => {
setisNutritionScanned(true);
//navigation.navigate("NutritionFacts");
};
const contextValue = {
isNutritionScanned,
setisNutritionScanned,
nutrionScanClick,
};
return (
<NutriSyncContext.Provider value={contextValue}>
{children}
</NutriSyncContext.Provider>
);
};
inside navigation.js
const tabPNMS = createMaterialTopTabNavigator();
function tabPNMSStackScreen() {
const { isNutritionScanned } = useContext(NutriSyncContext);
console.log(isNutritionScanned);
return (
<tabPNMS.Navigator initialRouteName="PNMSNutrition" tabBar={() => null}>
<tabPNMS.Screen name="PNMSNutrition" component={PNMSNutrition} />
{isNutritionScanned ? (
<tabPNMS.Screen name="NutritionFacts" component={NutritionFacts} />
) : null}
</tabPNMS.Navigator>
);
}
and In stack Navigation:
const SearchStack = createStackNavigator();
const SearchStackScreen = () => (
<NutriSyncContextProvider>
<SearchStack.Navigator
initialRouteName="Search"
screenOptions={{
gestureEnabled: false,
headerStyle: {
backgroundColor: "#101010",
},
headerTitleStyle: {
fontWeight: "bold",
},
headerTintColor: "#ffd700",
headerBackTitleVisible: false, //this will hide header back title
}}
headerMode="float"
>
<SearchStack.Screen
name="Search"
component={Search}
options={({ navigation }) => ({
title: "Search",
headerTitleAlign: "center",
headerLeft: () => (
<Entypo
name="menu"
size={24}
color="green"
onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}
/>
),
})}
/>
<SearchStack.Screen
name="NutriTabs"
options={() => ({
title: "PNMS",
headerTitleAlign: "center",
})}
component={tabPNMSStackScreen}
/>
</SearchStack.Navigator>
</NutriSyncContextProvider>
);
I have NutriSyncContextProvider around the stack, but when I click the button on my page it will call nutrionScanClick method in context class it works just fine but when i do navigation.navigate to the NutritionFacts tab that is enabled i get error saying NutritionFacts tab doesn't exist
I got it working below is the code:
const nutrionScanClick = () => {
if (!isNutritionScanned) setisNutritionScanned(true);
else {
//Make a API call here for new data for the new scanned one's and then navigate
navigation.navigate("NutritionFacts");
}
};
useEffect(() => {
if (isNutritionScanned) {
navigation.navigate("NutritionFacts");
}
}, [isNutritionScanned]);

There's no swipe back n forth between screens in a stack navigator. You need to use a tab navigator to be able to keep multiple screens like that.
https://reactnavigation.org/docs/material-top-tab-navigator
import { createMaterialTopTabNavigator } from '#react-navigation/material-top-tabs';
const Tab = createMaterialTopTabNavigator();
function MyTabs() {
return (
<Tab.Navigator tabBar={() => null}>
<Tab.Screen name="ItemDetails" component={ItemDetails} />
{showNutrients ? <Tab.Screen name="Nutrients" component={Nutrients} /> : null}
</Tab.Navigator>
);
}

Related

Force the screen defined in initialRouteName to be displayed when the tab button is pressed

I have a problem with bottom tab navigation. This is screen structure:
Main Tab:
Tab A
Screen A1
Tab B
Screen B1
Screen B2
I can navigate from Screen A1 to Screen B2 (changing tab).
I can navigate from Screen B1 to Screen B2.
This is the example code:
import React from "react";
import { StyleSheet, View, Text, Button } from "react-native";
import { NavigationContainer } from "#react-navigation/native";
import { createStackNavigator } from "#react-navigation/stack";
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs";
const Tab = createBottomTabNavigator();
const Stack = createStackNavigator();
const TabAScreen1 = ({ route, navigation }) => {
return (
<View>
<Text>Tab A screen 1</Text>
<Button
title="Go to tab B screen 2"
onPress={() => {
navigation.navigate("TabBStack", { screen: "TabBScreen2" });
}}
/>
</View>
);
};
const TabBScreen1 = ({ route, navigation }) => {
return (
<View>
<Text>Tab B screen 1</Text>
<Button
title="Go to tab B screen 2"
onPress={() => {
navigation.navigate("TabBScreen2");
}}
/>
</View>
);
};
const TabBScreen2 = ({ route, navigation }) => {
return (
<View>
<Text>Tab B screen 2</Text>
</View>
);
};
function TabBStack() {
return (
<Stack.Navigator initialRouteName="TabBScreen1" screenOptions={{ headerShown: false }}>
<Stack.Screen name="TabBScreen1" component={TabBScreen1}/>
<Stack.Screen name="TabBScreen2" component={TabBScreen2}/>
</Stack.Navigator>
);
}
function MainTab() {
return (
<Tab.Navigator screenOptions={{ headerShown: false }}>
<Tab.Screen name="TabAScreen1" component={TabAScreen1}/>
<Tab.Screen name="TabBStack" component={TabBStack}/>
</Tab.Navigator>
);
};
const App = props => {
const navigationRef = useRef();
return (
<NavigationContainer>
<MainTab/>
</NavigationContainer>
);
};
const styles = StyleSheet.create({});
export default App;
Navigation packages:
"#react-navigation/bottom-tabs": "^6.2.0",
"#react-navigation/native": "^6.0.8",
"#react-navigation/stack": "^6.1.1",
At the beginning, if I navigate to Tab B using bottom tab button, Screen B1 is loaded. Then I can navigate to Screen B2 from Screen B1 or Screen A1. Being in Screen B2, if I tap on Tab B bottom tab button the Screen B2 is popped and Screen B1 is showed. This is the expected behavior.
The problem is this one. At the beginning, if I navigate to Screen B2 from Screen A1, Screen B2 is loaded. At this point, I can't navigate to screen B1 at all. If I tap on Tab B bottom tab button nothing happens, even initialRouteName of TabBStack is set to TabBScreen1. I think in this case the first screen in the stack is B2.
Is it possible to pop B2 (or all screens in the stack) and push B1 tapping bottom tab button? I know about listeners, but I don't know how to implement this behavior.
function MainTab() {
return (
<Tab.Navigator screenOptions={{ headerShown: false }}>
<Tab.Screen name="TabAScreen1" component={TabAScreen1}/>
<Tab.Screen name="TabBStack" component={TabBStack}
listeners={({ navigation }) => ({
tabPress: (e) => {
console.log("Tab B pressed");
// If current route is not Screen B1, then pop all and push Screen B1
},
})}
/>
</Tab.Navigator>
);
};
This only happens if you initially load the application start in stack A and have never been to stack B before. This causes the initial route of the TabBStack to not be mounted!
If you then navigate to a nested screen inside the stack of the TabBStack from TabAScreen1, the default route reset functionality does not work. The framework does only mount TabBScreen2 but not the initial route specified in that particular tab.
If we do the following instead:
Load the application: the default screen is TabAScreen1
Go to TabBStack using the bottom tab navigation.
Go back to TabAScreen1.
Click the Go to tab B screen 2 button
Press the TabBStack bottom tab button
You will notice that it now successfully resets the route. This is the default behavior used in most applications.
How do we solve this?
We have luck. The framework allows us to do this in a very convenient way by using the initial prop.
By default, when you navigate a screen in the nested navigator, the specified screen is used as the initial screen and the initial route prop on the navigator is ignored.
In our case, this causes the TabBScreen1 to never be mounted. Thus, we can not reset to this route. If we provide initial: false while navigating to the nested screen, the navigation framework will not use TabBScreen2 as the initial route but TabBScreen1. If we do this, we can reset the route by pressing the TabBStack button again.
navigation.navigate("TabBStack", { screen: "TabBScreen2", initial: false });

How do I pass certain route parameters from a screen function to a navigation header button in React Native

I am new to react native and am basically wondering how to pass information from one screen to another if the button that triggers the shift from one screen to another is in the navigation class.
For example, If I have a screen:
export default function Screen() {
const data = ["a", "b", "c"]
}
and another screen with a SectionList section that requires that information:
const data = [
{
title: "",
data: {route.params?.data},
},
and finally within the navigation tab a custom navbar header:
<Stack.Screen
name="Screen"
component={ChatScreen}
options={({ route }) => ({
headerRight: () => (
<Pressable onPress={() => navigation.navigate("Screen", {data: *get data from
Screen here*)}>
<Ionicons
name="ellipsis-horizontal"
size={25}
color="#5C6AEF"
style={{ marginBottom: 2 }}
/>
</Pressable>
),
})}
/>
basically, how do I call the information from Screen in the Navigation tab to pass to the SectionList screen?
Thanks for the help!

How to update navigation header from screen?

react-native version 0.62
I am making a quiz app and on every new question(a new quiz screen, QuestionScreen.js), I want to update a label in the navigation bar which is declared in App.js
In App.js, the problem here is I don't know how to pass the state questionIndex to HomeScreen or QuestionScreen, normally I thought this would be using props like <Question questionIndex={questionIndex}>. I tried to pass them in options but that doesn't seem to work...
const App = () => {
const [questionIndex, setQuestionIndex] = useState(0);
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='Home'
>
<Stack.Screen
name='Home'
component={HomeScreen}
options={{
title:''
}}
/>
<Stack.Screen
name='QuestionScreen'
component={QuestionScreen}
options={({ navigation }) => ({
headerBackTitleVisible: false,
headerRight: ()=>(<HeaderRight pageIndex={questionIndex}/>),
headerRightContainerStyle: {
alignItems: "flex-end",
paddingRight: 25
},
})}
/>
function HeaderRight(props) {
return (
<Text>{props.pageIndex}/{questions.length-1}</Text>
);
}
const HomeScreen = ({ navigation }) => {
return (
<View style={styles.container} >
<ScrollView>
<TouchableOpacity onPress={()=>
//here I'm unable to retrieve the questionIndex props.
//tried navigation.getParams("questionIndex") or route.params
navigation.navigate('QuestionScreen', {
pageIndex: 0
})
}>
In QuestionScreen.js
function QuestionScreen({route, navigation, options}) {
const { pageIndex } = route.params;
[Full code here on github]https://github.com/liaoxsong/expofirst
I am a newbie to react-native and I come from native mobile development, this Stack.Navigator api looks weird, can't I just manage the navigation header from the screen component itself? That would make things easier.
it is difficult to customize the default header given by react navigation, what you can do is disabling the header and creating your own custom header.
disable header by giving prop headerMode={"none"} in createStackNavigator and have your own custom header on top,
to disable header docs : https://reactnavigation.org/docs/stack-navigator/#headermode

Set Screen Title in ReactNative createBottomTabNavigator

I am trying to set the Screen Title of the Navigated Screen by using navigator. But it's not working/ changing and doesn't throw an exception either.
Scenario
I want to load some states from Redux and display nice Header Title in the Navigation Screen when the user clicks on the bottom Tab.
User clicks on second Tab in bottom navigation tab
Load state from Redux
Set Header Title
Expected
I want to set the following text to the Screen title
headerTitle set in Screen
Actual
I declared my BottomTabNavigator as following:
import FilterScreen from '../screens/FilterScreen';
import ItemsListScreen2 from '../screens/ItemsListScreen copy';
const BottomTab = createBottomTabNavigator();
const INITIAL_ROUTE_NAME = 'List';
export default function BottomTabNavigator({ navigation, route }) {
// this code works
navigation.setOptions({ headerTitle: 'header Set in Navigator' });
const [isFilterVisible, toggleFilter] = useState(false);
return (
<>
<BottomTab.Navigator
initialRouteName={INITIAL_ROUTE_NAME}
tabBarOptions={{
inactiveBackgroundColor: '#42a5f5',
inactiveTintColor: '#ffffff'
}}
>
<BottomTab.Screen
name="List"
component={ItemsListScreen}
options={{
title: 'List',
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} name="md-calendar" iconType="ion" />,
}}
/>
<BottomTab.Screen
name="List2"
headerMode="screen"
component={ItemsListScreen2}
screen
options={{
title: 'title List2',
headerTitle: 'my header title',
tabBarIcon: ({ focused }) => <TabBarIcon focused={focused} name="md-calendar" iconType="ion" />,
}}
/>
....
</BottomTab.Navigator>
</>
);
}
In the above component, I could set the Screen Title by using navigation.setOptions and it works.
So, I tried to do the same operation to set the headerTitle in List2 Screen. But it doesn't have any effect to the screen header. It only changes the label of the tab to "this is a title".
import { StyleSheet, View, Text } from 'react-native';
const ItemsListScreen2 = ({ navigation }) => {
navigation.setOptions({
headerTitle: `headerTitle set in Screen`, // <-- no effect at the screen level
title: "this is a title"
});
return (
<View>
<Text>Screen 2</Text>
</View>
);
}
I tried to do console.log to navigation object passed to the component and I can see it here:
Could you please help me to sort out this issue and suggest me how I could set the title from the screen?
Do I need to create a new function (Eg. getHeaderTitle), load the Redux state and set the title due to the route within BottomTabNavigation component? I want to keep that component clean and I want to load the Redux States only when the user navigates to the screen. I'd like to keep the screen specific codes in its own related screen.

React Navigation (V2 / V3): How to access navigation.state.index in navigationOptions on screen

I'm building a React Native app and for my navigation I use React Navigation (V3). For my UI elements I use React Native Elements.
I have a screen which I reuse in multiple navigators. Sometimes it is at the root of a stack, and sometimes it's pushed on with a stack. I want to programmatically add a headerLeft element to this screen navigationOptions based on its position in the stack, because I either want the default back button to show, or a burger menu icon to show, if the screen is at the root of the stack (because the stack is nested in a drawer).
So I tried the following:
export class ScannerScreen extends Component {
static navigationOptions = ({ navigation }) => ({
headerLeft:
navigation.state.index > 0 ? (
undefined
) : (
<Icon type="ionicon" name="ios-menu" onPress={navigation.toggleDrawer} />
),
title: strings.scannerTitle
});
// ...
The problem is this doesn't work as navigation.state.index is undefined here. How would one do this with React navigation?
Edit:
As per request I added a screenshot of console.log(navigation) (via <Icon />s onPress.)
I found a hacky solution, which is okay but I also dislike it a bit:
const stackConfig: StackNavigatorConfig = {
cardStyle: styles.card,
navigationOptions: {
headerBackTitleStyle: styles.headerBackTitle,
headerStyle: styles.header,
headerTintColor: "black"
}
};
const HomeStack = createStackNavigator(
{ HomeScreen, RestaurantDetailScreen, MenuScreen, ScannerScreen },
{
...stackConfig,
initialRouteName: "HomeScreen",
navigationOptions: ({ navigation }) => ({
headerLeft:
parseFloat(navigation.state.key.slice(-1)) > 1 ? (
undefined
) : (
<Icon
containerStyle={styles.leftIcon}
type="ionicon"
name={getIconName("menu")}
onPress={navigation.toggleDrawer}
/>
),
...stackConfig.navigationOptions
})
}
);
This way it automatically sets it for the whole stack. You can also do this in the respective screen individual navigation options. But this only works, since the automatically assigned key for the screen contains it's index.