How to initialize Stack navigator? - react-native

I have a Bottom Tabs Navigator that contains a Stack Navigator on each tab.
const MainNavigator = createBottomTabNavigator<MainRoutes>();
export interface MainNavProps {
user: User,
signOut: SignOut
}
export function MainNav({ user, signOut }: MainNavProps) {
return (
<MainNavigator.Navigator initialRouteName="DashboardSection" screenOptions={mainScreenOptions}>
<MainNavigator.Screen name="DashboardSection" component={DashboardStack} options={dashboardOptions} />
<MainNavigator.Screen name="HashSection" component={HashStack} options={hashesOptions} />
<MainNavigator.Screen name="AccountSection" component={AccountStack} options={accountOptions} />
</MainNavigator.Navigator>
)
}
I want to emphasize here the HashStack Navigator. It consists of two screens, namely:
const HashNavigator = createStackNavigator<HashRoutes>();
const screenOptions = ({ navigation, route }: HashStackProps): StackNavigationOptions => {
const pressHandler = () => navigation.navigate("Modify", { title: "modify hashes" });
return {
...sectionScreenOptions,
headerRight: function HeaderLeft() {
return (
route.name === "Index" ?
<Button
marginR-10
iconSource={() => (
<MaterialCommunityIcons name="plus" color={Colors.primaryDarker} size={28} />
)}
style={{ width: 35, height: 35 }}
color={Colors.primaryDarker}
outline
outlineColor={Colors.primaryDarker}
onPress={pressHandler}
/> : <></>
);
},
headerTitle: route.params.title
}
}
export function HashStack() {
return (
<HashNavigator.Navigator initialRouteName="Index" screenOptions={screenOptions} >
<HashNavigator.Screen name="Index" component={Hash} initialParams={{ title: "hashes overview" }} />
<HashNavigator.Screen name="Modify" component={ModifyHash} />
</HashNavigator.Navigator>
);
}
To simplify the explanation I recorded a video:
https://user-images.githubusercontent.com/56756949/137601978-9d3aec89-3732-42a4-834b-b427bc1f73ca.mp4
When I press the tab Hashtag, which is in the middle of the screen it shows the HashStack component.
As you can see the headerRight property on the HashStack ScreenOptions is active and it shows a plus button. Clicking on the plus button it navigates to the Modify route within HashStack.
Then pressing on the tab Dashboard, which is the first Route in the Bottom Tabs Navigator and press the Hashtag tab again, it does not show the initial route screen, which is:
<HashNavigator.Screen name="Index" component={Hash} initialParams={{ title: "hashes overview" }} />
The question is, how to reset the HashNavigator when another Bottom Tab has been pressed so that the Hashtag tab always shows the initial route Index.

You can pass unmountOnBlur: true inside the tab screen options of the HashStack
const hashesOptions = {
// other options
unmountOnBlur: true
}
export function MainNav({ user, signOut }: MainNavProps) {
return (
<MainNavigator.Navigator initialRouteName="DashboardSection" screenOptions={mainScreenOptions}>
<MainNavigator.Screen name="DashboardSection" component={DashboardStack} options={dashboardOptions} />
<MainNavigator.Screen name="HashSection" component={HashStack} options={hashesOptions} />
<MainNavigator.Screen name="AccountSection" component={AccountStack} options={accountOptions} />
</MainNavigator.Navigator>
)
}

Related

How do i skip the animation only replace with react-navigation

i am using #react-navigation/stack^5.14.4 and #react-navigation/native^5.9.4, to handle the scene transition between Home, Login and Profile pages.
there are 2 transition cases i need to handle:
Home -> Profile (should have animation enabled)
Login -> Profile (need to skip the animation)
the reason why to skip the animation is that I am using a loading skeleton similar to the layout of Profile page during the login process. It is weird to have a transition between the profile page content and the skeleton itself. However I want to keep the awesome animation if pushed from Home page.
Is there a simple solution like
navigation.replace("Profile"); // with animation
navigation.replace("Profile", { animationEnabled: false }); // skip animation
You can use options to get the route property and check weather there is any parameter to disable the screen
snak: https://snack.expo.dev/#ashwith00/react-navigation-5-boilerplate
heres is an example
function TestScreen({ navigation }) {
return (
<View style={styles.content}>
<Button
mode="contained"
onPress={() => navigation.push('Test1', {disabledAnimation: true})}
style={styles.button}>
Push screen Without Animation
</Button>
<Button
mode="contained"
onPress={() => navigation.push('Test1')}
style={styles.button}>
Push new screen
</Button>
{navigation.canGoBack() ? (
<Button
mode="outlined"
onPress={() => navigation.pop()}
style={styles.button}>
Go back
</Button>
) : null}
</View>
);
}
const Stack = createStackNavigator();
function MyStack() {
return (
<Stack.Navigator>
<Stack.Screen
name="Test"
component={TestScreen}
options={{ title: 'Custom animation' }}
/>
<Stack.Screen
name="Test1"
component={TestScreen}
options={({ route }) => ({
title: 'Custom animation',
cardStyleInterpolator: !route.params?.disabledAnimation
? undefined
: CardStyleInterpolators.forNoAnimation,
})}
/>
</Stack.Navigator>
);
}
i find a more simple way to make it:
firstly, export the Stack.Screen node from the router file.
export const ProfileStack = (
<Stack.Screen name={'Profile'} component={Profile} />
);
export default Router = () => (
<Stack.Navigator>
<Stack.Screen name={'Home'} component={Home} />
<Stack.Screen name={'Login'} component={Login} />
{ProfileStack}
</Stack.Navigator>
);
then I can execute like this to disable the animation once.
with animation:
<Button onClick={() => {
navigation.replace('Profile');
}} />
without animation
<Button onClick={() => {
ProfileStack.props.options.animationEnabled = false;
navigation.replace('Profile');
ProfileStack.props.options.animationEnabled = true;
}} />

passing text input between custom header using stack navigation

I wanted to connect the SearchMain, SearchHistoryList, and SearchResult screens using the custom header of the stack navigation.
At first, the SearchMain screen appeared,
and when the TextInput in the custom header is onFocus, the SearchHistory screen appeared,
and the SearchResult screen appeared when the TextInput is onSubmitEditing or when the SearchButton was pressed.
Also, I set the keyboard focus to be maintained when switching screens, and use Keyboard.discuss() when I want to close it.
However, if you enter something at the TextInput in custom header immediately after the screen changes, it will be entered at the TextInput on the previous screen.
I think it's because the three screens don't share a single custom header!! each header is created, and the keyboard focus on the previous screen!!
I want all screens use the same header, or the values of the headers are consistent.
What should I do to solve this problem?
I searched variously such as singleton header, static header, and header value consistent, but there is no good solution.
I recently started studying React Native, so I'm not sure about my method, and a completely different approach is also welcome~~
Below is my code, thanks.
const Stack = createStackNavigator();
function NavigationBar({navigation}){
const [query, setQuery] = React.useState('');
function changeSearchQuery(text : string) : void {
setQuery(text);
}
function deleteSearchQuery() : void {
setQuery("");
}
return(
<InputView>
<BackIcon onPress={() => {navigation.goBack()
Keyboard.dismiss()}}>
<Icon name="arrow-back-ios" size={widthPercentage(24)} color="#666666"/>
</BackIcon>
<InputText
keyboardType="default"
maxLength={100}
onChangeText={(str) => setQuery(str)}
value = {query}
placeholder="검색어 입력"
placeholderTextColor="#E9E9E9"
returnKeyType="search"
onFocus={() => navigation.navigate("SearchHistory", {changeSearchQuery:changeSearchQuery})}
onSubmitEditing={() => {navigation.navigate("SearchResult", {changeSearchQuery:changeSearchQuery})
Keyboard.dismiss()}}
allowFontScaling= {false}
/>
{ query.length > 0 &&
<DeleteIcon onPress={() => deleteSearchQuery()}>
<Icon name="clear" size={widthPercentage(20)} color="#666666"/>
</DeleteIcon>
}
<SearchIcon onPress={() => {navigation.navigate("SearchResult")
Keyboard.dismiss()}}>
<Icon name="search" size={widthPercentage(20)} color="#666666"/>
</SearchIcon>
<Line></Line>
</InputView>
);
}
export const Search = () =>{
return (
<Stack.Navigator initialRouteName="SearchMain" keyboardHandlingEnabled={false} screenOptions={{header:props => <NavigationBar navigation={props.navigation}/>}}>
<Stack.Screen name="SearchMain" component={SearchMain} options={{animationEnabled: false}}/>
<Stack.Screen name="SearchHistory" component={SearchHistory} options={{animationEnabled: false}}/>
<Stack.Screen name="SearchResult" component={SearchResult} options={{animationEnabled: false}}/>
<Stack.Screen name="SearchFilter" component={SearchFilter} options={{headerShown: false}}/>
</Stack.Navigator>
);
}
export default Search;
It's not a good pattern to set the state inside the header, instead you should centralized the state in your component and then use navigation.setOption to pass functions/state value.
In this way your header will look like this:
function NavigationBar({
query,
onFocus,
onChangeText,
onPressBackIcon,
onSubmitEditing,
onDeleteSearch,
onSearch
}){
return(
<InputView>
<BackIcon onPress={onPressBackIcon}>
<Icon name="arrow-back-ios" size={widthPercentage(24)} color="#666666"/>
</BackIcon>
<InputText
keyboardType="default"
maxLength={100}
onChangeText={onChangeText}
value = {query}
placeholder="검색어 입력"
placeholderTextColor="#E9E9E9"
returnKeyType="search"
onFocus={onFocus}
onSubmitEditing={onSubmitEditing}
allowFontScaling= {false}
/>
{ query.length > 0 &&
<DeleteIcon onPress={onDeleteSearch}>
<Icon name="clear" size={widthPercentage(20)} color="#666666"/>
</DeleteIcon>
}
<SearchIcon onPress={onSearch}>
<Icon name="search" size={widthPercentage(20)} color="#666666"/>
</SearchIcon>
<Line></Line>
</InputView>
);
}
Than you will have in SearchMainScreen your state, your functions and your custom header:
// remember headerMode='none' in screen option
const SearchMainScreen = () => {
const [query, setQuery] = React.useState('');
const onFocus = () => {}
...
return (
<View>
<NavigationBar
onFocus={onFocus}
query={query}
...
/>
{
(isFocus && !query) ?
<SearchHistoryComponent/> :
(isFocus && query) ?
<SearchResultComponent/> :
<List/>
}
</View>
)
}

Passing state via route.params with React Navigation returning undefined

I'm attempting to pass a 'passcode' state as params in my React Native app.
I'm doing this by passing params into a 'navigation.navigate' call.
However, every time I navigate to the next screen, it's returning 'undefined' for 'route.params'.
For reference, here is my component I'm passing data FROM:
const SignUpPasscodeScreen = ({ navigation }) => {
const [passcode, setPasscode] = useState(0)
return (
<View>
<View style={styles.mainView}>
<SubLogo />
<Heading title="Set passcode" />
<SubHeading content="You'll need this anytime you need to access your account." />
<Input inputText={ text => setPasscode(text) } inputValue={passcode} />
</View>
<View style={styles.subView}>
<CtaButton text="Continue" onPressFunction={ () => navigation.navigate({ routeName: 'SignUpLegalName', params: { passcode } } ) } />
</View>
</View>
)
}
And here's the component I'm passing data to, and where the error occurs upon navigation:
const SignUpLegalName = ({ route, navigation }) => {
const { passcode } = route.params
return (
<View>
<View style={styles.mainView}>
<SubLogo />
<Heading title="Tell us your name" />
<SubHeading content="This needs to be the same as what's on your passport, or any other form of recognised ID." />
<Input />
<Input />
</View>
<View style={styles.subView}>
<CtaButton text="Continue" onPressFunction={ () => navigation.navigate('SignUpLink')} />
</View>
</View>
)
}
I've tried two forms of passing the props through:
Passing it in as a second argument
Passing it in as a 'params' object as shown above
Both should work according to the documentation - link here
For reference, this is my route structure:
const switchNavigator = createSwitchNavigator({
loginFlow: createStackNavigator({
SignUpPasscode: SignUpPasscodeScreen,
SignUpLegalName: SignUpLegalName,
})
});
The above structure doesn't say to me that it's a nested structure which therefore requires any additional work to pass it through...
Can someone help? It'd be appreciated as it's giving me a headache!
Have a try with below code in the button press event:
<CtaButton
text="Continue"
onPressFunction={() => navigation.navigate('SignUpLegalName',{ passcode })}
/>

React Native navigating between two screens back & forth

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

React Native StackNavigator: Passing props from a screen to the header of the next screen

I have a React Native StackNavigator like so:
const AppStack = () => {
return (
<NavigationContainer theme={{ colors: { background: "white" }}}>
<Stack.Navigator headerMode="screen">
<Stack.Screen name="Master" component={ Master } />
<Stack.Screen
name="Details"
component={ Details }
options={{ headerTitle: props => <Header {...props} /> }} // <-- how can I pass props from Master to the Header here
/>
</Stack.Navigator>
</NavigationContainer>
)
}
The navigation works fine. When I'm on Master, if I press a TouchableOpacity, it brings up Details, with the header component Header.
However, what I want is to pass props from Master to the Header component in Details.
Something like this:
{/* What I want is that onPress, I want to pass someones_name and someones_photo_url to
the Details' Header component */ }
const Master = () => {
const someones_name = "Steve";
const someones_photo_url = "http://somephoto.com/001.jpg";
return (
<TouchableOpacity onPress={() => navigation.navigate("Details")}>
<Text>{ someones_name }</Text>
<Image source={{ uri: someones_photo_url }}>
</TouchableOpacity>
)
}
Is this possible?
You can pass values to other screens when navigating by doing the following:
Assume I am on screen "Feed" and click on someones name thus I want to go to screen "Profile" where it will show that user name that I clicked on.
In screen Feed
props.navigation.navigate('Profile, { userName: 'Shane' })`
In screen Profile I can grab that value passed in via navigate with:
const { userName } = props.route.params
To set this up inside a header assuming your screen options are outside the screen component and cannot access the props. Look into using setOptions to configure your header title dynamically.
props.navigation.setOptions({
headerTitle: ...
})