React - Navigating to the same component weird behaviour - react-native

I'm breaking my head over this, the behaviour I'm seeing seems weird for me, though it might make sense to some of you.
Consider this component:
const DishPreparation = ({ dish }) => {
const [slideIndex, setSlideIndex] = useState(0)
const sceneRef = useRef(null)
useKeepAwake();
return (
<View style={styles.scene} ref={sceneRef}>
<View flex={0.12} style={{ marginTop:-10 }}>
<ProgressSteps activeStep={slideIndex} activeStepIconBorderColor={colors.lightTan} completedProgressBarColor={colors.lightTan} completedStepIconColor={colors.lightTan}>
{dish.preparationSteps.map((step, index) => (
<ProgressStep removeBtnRow key={index}/> ))}
</ProgressSteps>
......
)
Which is being rendered through another component Dish, that can be navigated to. I have some floating component in my app that allows me to navigate to Dish with a specific dish. If I navigate to a dish through it for the first time, or if I navigate there through other component that doing it by pushing the component to the stack, everything works fine. The weird behaviour is when I'm already inside this component with a specific dish, and then navigate through the floating component to a different dish, it's like the old dish is being kept.
Lets say first dish had 3 elements in dish.preparationSteps, and the second one has 4, then dish.preparationSteps.map(step, index) returns only 3 elements instead of 4, but if I render step.someInfo inside the mapping, then I actually see the new dish values.
How is this happening? I'd expect either 4 elements to be returned, or 3 elements but with the old dish values, how is this mixture happening? Also, dont know if it helps but slideIndex keeps its old value, and doesn't reinitialize to 0.
This is how I navigate through the floating component:
navigation.navigate('Tabs', {
screen: 'Home',
params: { screen: 'Dish', params: { from: '', data: dish } },
})
This is how I navigate to it through other some other component(which works as expected)
navigation.push('Dish', {from: 'DishList', data: item})
If any other code is needed I'll be happy to add it.

When using .navigate instead of .push, navigation will look for that screen name ("Dish") and just change navigation params, without remounting component or opening a new screen with it. Usually you can just use navigation.push (like in your last example), but the problem is that you are trying to navigate from some top-level navigator. You can't use .push there because it will push to the outer navigator which doesn't have "Dish" screen. If you want to add another "Dish" screen on top of the existing one in the nested navigator, you need to navigation.push from the Stack Navigator you want to navigate in; it's only that your floating is not located in that Stack Navigator
First thing you can try is to add a unique key to your screen when navigating, e.g.
navigation.navigate('Tabs', {
screen: 'Home',
params: { screen: 'Dish', key: dish.id, params: { from: '', data: dish } },
})
so that navigation will compare screens not by the name but by the name and key. This will most likely replace existing Dish screen with a new one
Better solution would be to dispatch navigation action that will drill down to the Stack Navigator you want to navigate in, and dispatch StackActions.push there
And even better solution would be not trying to navigate inside inner navigators from outer navigators

Maybe the getId prop is what you're looking for?
From the React Navigation docs:
In a stack navigator, calling navigate with a screen name will result in different behavior based on if the screen is already present or not. If the screen is already present in the stack's history, it'll go back to that screen and remove any screens after that. If the screen is not present, it'll push a new screen.
You could use the getId prop to push a new screen instead. For example, say you have specified a getId prop for Profile screen:
<Screen name={Profile} component={ProfileScreen} getId={({ params }) => params.userId} />
Now, if you have a stack with the history Home > Profile (userId: bob) > Settings and you call navigate(Profile, { userId: 'alice' }) the resulting screens will be Home > Profile (userId: bob) > Settings > Profile (userId: alice) since it'll add a new Profile screen as no matching screen was found.

Related

What is a difference between navigation.navigate(), navigation.push(), navigation.goBack() and navigation.popToTop() if I go back from page to page?

From this and this, I learned that there is a meaningful difference between navigation.navigate() and navigation.push(). Nevertheless, I am still wondering if I can use navigation.navigate() or navigation.push() instead of navigation.goBack() or navigation.popToTop().
In my case, there is one page, and some parameters are included in the navigation. (i.e, through navigation.navigate(param, {...}) or navigation.push(param, {...}). Once I move to another page, some change in variable happened, and now I would like to send back the param with new data to the first page. I considered doing navigation.navigate(param, {...}) or navigation.push(param, {...}) again as it looks I cannot send back any parameters using goBack() or popToTop(), according to this
I checked this, but I am not 100% sure as I think pages might be stacked a lot if a user does the above actions many times.
Lets take an example
Think you have screens A,B and C in the stack and A is the home screen.
The actual stack will be an object but for easy understanding i'm using a simple array.
When you start the stack will be [A]
When you do a navigate to B the stack will be [A,B]
And if you push C to the stack from B then it will be [A,B,C]
Now all this is common but now if you do a navigate to B from C
then it will unmount C and go back to B and the stack will be [A,B]
If you chose push then it will add a new screen to the stack and stack will be [A,B,C,B] Notice that push always adds a new screen to the stack.
Ignore the push and assume that the stack is [A,B,C]
Now if you do goBack from C then it will pop just like the navigate method and go back to B.
But if you do popToTop it will unmount both C and B and make the stack look like this [A].
The difference is that goBack and popToTop does not pass parameters like navigate and push.
There is a way to achieve the same result of popToTop and goBack using navigate and useNavigationState.
The useNavigationState hook will get you the current navigation state which will have the information of all the screens in the stack. The sample navigation state value would be like this
{
stale: false,
type: 'stack',
key: 'stack-A32X5E81P-B5hnumEXkbk',
index: 1,
routeNames: ['Home', 'Details', 'MyView', 'ExtView'],
routes: [
{ key: 'Home-y6pdPZOKLOPlaXWtUp8bI', name: 'Home' },
{
key: 'MyView-w-6PeCuXYrcxuy1pngYKs',
name: 'MyView',
params: { itemId: 86, otherParam: 'anything you want here' },
},
],
}
As you can see you have the option to use this information to navigate to any screen in the stack. The navigate method can be used like below as well
navigation.navigate({ key: navState.routes[0].key, params: { id: 12 } })
If you use the key 0 then you will be taken to root along with a parameter and it will unmount the screen in the middle.
If you want to go back you can simply do an index - 1 which will give the same effect as goBack
navigation.navigate({ key: navState.routes[navState.Index-1].key, params: { id: 12 } })
So your requirement can be achieved.

How to goBack globally between stacks in React Navigation?

I am using react navigation ("#react-navigation/native": "^5.1.3") and I have the following setup:
BottomNavigation
-stack1
-stack2
It looks like goBack() is local to the stack. What that means is that if I navigate from a page in stack1 to a page in stack2, I am unable to go the the page I came up from.
Solutions (or rather hacks) that didn't work for me:
pass the source screen as param and then navigate. That isn't a real back button and does not play well with android back button.
Put all pages in bottom navigation. Bottom navigation does not have animations it seems, so I can not achieve the right transitions
Put all pages in stack navigation. In this case I lose the fixed bottom navigation. I can add it to each page, but when transitioning it will go away with the old screen and come again with the new one, which is undesirable.
So I am wondering if I am missing something big here, like a globalBack() that I overlooked?!
And also, I am looking for a solution to this problem which remains.
Naturally if you have bottoms tabs with each tab having its own stack navigator, calling navigation.goBack() will go back from one screen inside stack navigator to previous screen inside that same stack navigator. That's how navigation works in pretty much every app. Pressing back button or swiping back does not change tab for you, tabs are more like separate small apps by itself. If you want to specifically jump from one tab to another instead of going back in stack, use navigation.dispatch(TabActions.jumpTo('Profile')). If pressing something inside tab#1 makes you go to to tab#2 then this screen most likely also belongs to tab#1
also, take a look at backBehavior prop of Tab.Navigator, it might be doing what you want depending on what exactly it is you want https://reactnavigation.org/docs/bottom-tab-navigator/#backbehavior
I'm using bottom tab navigator with 2 stacks as well. I faced similar issue and agree with #Max explanation. Due to my Notification screen is in Stack 1, I have to goBack to Notification after navigating away to Detail screen. After searching for the fix, I'm using this workaround (for v6).
Tab 1 - Stack 1 (Home > Notification screen)
Tab 2 - Stack 2 (Reward Home > Reward Detail screen)
I passed a param when navigating from Notification to RewardDetail. Then I override the headerLeft and use BackHandler to handle Android back function.
Notification.js
navigation.navigate('RewardStack', {
screen: 'RewardDetail',
initial: false,
params:{notification: notification_data_source}
})
RewardDetail.js
const payload = route.params.notification
//1. override headerLeft button
useLayoutEffect(() => {
if(payload)
navigation.setOptions({
headerLeft: () => (
<Button
TouchableComponent={TouchableOpacity}
buttonStyle={{paddingTop:4, paddingLeft:0}}
type='clear'
icon={<Icon name={'chevron-left'} size={30} style={{color:'#FFF'}} />}
onPress={()=>{
navigation.goBack()
navigation.navigate('Notification') //can use this only
}}
/>
)
})
}, [navigation]);
//2. Add BackHandler
useEffect(() => {
const onBackPress = () => {
if (payload) {
navigation.goBack()
navigation.navigate('Notification') //can use this only
return true
} else {
return false
}
}
BackHandler.addEventListener('hardwareBackPress', onBackPress)
return () => BackHandler.removeEventListener('hardwareBackPress', onBackPress)
}, [navigation]);
I can just use navigation.navigate('Notification') to return to Notification but this will cause Detail screen to stay mounted in Stack 2. I want the Stack 2 to return to RewardHome after go back to Notification. Hence I used:
navigation.goBack() //pop screen to RewardHome
navigation.navigate('Notification') //jump to Notification

Why Navigation.goBack() not working properly?

I am using following way to create navigation system
StackNavigator -> DrawerNavigator -> (Screen A, Screen B, Screen C)
Screen A is initial Route
Screen A to Screen B Using
this.props.navigation.navigate("Screen B") //Working Fine
Screen B to Screen C Using
this.props.navigation.navigate("Screen C") //Working Fine
In Screen C
this.props.navigation.goBack() //Not Working
But
this.props.navigation.goBack(null) //It's going to Screen A instead of Screen B
What's wrong here.
Please help me Thank you.
As you didn't show the navigation structure of the drawer, I don't know how the 3 screens are put inside of it. I'm assuming they are 3 different drawerScreens
This happens because you are using a DrawerNavigation, that doesn't create a history of screens when you navigate around it.
To solve this you have to change the navigation structure to something like:
DrawerNavigation => StackNavigator => Screen A, B, C
You can specify backBehavior="history" inside Drawer.Navigator and it shouldn't move you to the initial root on goBack anymore :)
Example:
<Drawer.Navigator
backBehavior="history"
>
...
As the answer above is, the drawer navigators are not in the stack, so you can't tell the path of your journey.
the key property for goBack() is a dynamically created string, created by react-navigation whenever navigate to a new route.
These stack keys are stored in this.props.navigation.state
NOTE: Use .goBack(null) if your goal is to return to any place
without specifying a closing target.
Like this example, you can add to the stack.
Example
const Drawers = createDrawerNavigator(
{
A: {
screen: HomeScreen
},
...
},
{
initialRouteName: "A"
}
);
const RootStack = createStackNavigator(
{
Drawers: {
screen: Drawers
},
otherStack: {
screen: otherStack
},
....

How can I receive a variable back in my routes.js to load a different view?

I am using a switchNavigator to display either a show view or a view where the user can add more content. I want to send back a boolean variable just as a flag, I think I have that part just right but I don't know how to make it so that my code receives it and changes view.
This is in my routes.js
let hasItems = true;
const ItemsScreens = createSwitchNavigator(
{
Items: {
screen: Items,
},
ItemsExist: {
screen: ExistingItems,
},
},
{
mode: 'card',
initialRouteName: hasItems ? 'ItemsExist' : 'Items',
navigationOptions: {
drawerIcon: getDrawerItemIcon('account-balance-wallet'),
title: `Items`,
},
},
);
inside my ExistingItems.js I have a button that does:
<Button
onPress={() => this.props.navigation.navigate('Items', {hasItems: false})}
label={'Add Items'}
/>
My idea is to call the view again but send the false value in the variable to enter the actual adding items state but I have no idea how to make it actually receive the value. I tried doing an if like:
if(this.props.navigation.state.params.hasItems)
but that is undefined and crashes.
As suggested in react navigation Authentication flows example, create one another screen AuthLoadingScreen, which checks the condition and according to condition navigate to your another screen. However, I also know that extra screen will not good for user UI but it will work around.
Which property is seen as undefined?
Aren't you also controlling the wrong variable? the param you are passing is hasBanks but you are controlling hasItems

go two screen back with single press event using react-navigation in react native app

I am using reactnavigation component from https://reactnavigation.org and using the code below i am going one screen back
<Button
onPress={() => goBack()}
title="Go back from this HomeScreen"
/>
how can i go 2 screen back on single press action
I am using this code to initialize the navigator
const RouteConfigs = {
Login: {screen:Login},
Home: {screen:Home},
Chat: {screen:Chat},
Facebook: {screen:Facebook},
Facebookgallery: {screen:Facebookgallery}
}
const StackNavigatorConfig = {
headerMode: 'none',
}
export default StackNavigator(RouteConfigs, StackNavigatorConfig)
I navigate from home to Facebook with this code :
() => this.props.navigation.navigate('Facebook', {user:this.state.user})
and from Facebook to Facebookgallery with this code :
onPress={ () => this.props.navigation.navigate('Facebookgallery', {user:this.state.user}) }
now i want to go back from Facebookgallery to Home directly with some parameters
I know it's an older question but what I use is:
navigation.pop(n);
Takes you to the previous screen in the stack. If you provide a number, n, it will specify how many screens to take you back within the stack.
https://reactnavigation.org/docs/en/navigation-prop.html
you can use navigation.pop(screenCount)
with screenCount as integer
navigation.pop(2)
here is for refference
https://reactnavigation.org/docs/stack-actions#pop
React Navigation is updated not to use pop function directly, use dispatch function with StackActions.
navigation.dispatch(StackActions.pop(2));
navigation.navigate({ routeName: SCREEN, key: SCREEN_KEY_A });
navigation.navigate({ routeName: SCREEN, key: SCREEN_KEY_B });
navigation.navigate({ routeName: SCREEN, key: SCREEN_KEY_C });
navigation.navigate({ routeName: SCREEN, key: SCREEN_KEY_D });
Now you are on-screen D and want to go back to screen A (popping D, C, and B). Then you need to supply a key to goBack FROM:
navigation.goBack(SCREEN_KEY_B)
https://reactnavigation.org/docs/en/navigation-prop.html#going-back-from-a-specific-screen-with-goback
The "proper" solution right now is:
Get the key for the screen after the screen you want to go to. In
your case, you'll need to get the key for the Facebook screen.
Call NavigationAction.back(key: 'key-of-previous-screen'), and it
will pop your navigation stack as if you were on the screen with that key
Code example where I go back multiple screens in a thunk (relevant if you use redux):
export const postItemAndReturnToMap = item => (dispatch, getState) => {
const {nav} = getState();
// Get the key for the screen after/above the root view and use that as reference
// to return to the root view. This is hardcoded for my stack setup, as you can see
// there are quite a few nested StackNavigators in my setup
const {key} = nav.routes[0].routes[0].routes[0].routes[1];
// Dispatch whatever action you want
dispatch(postItem(item));
// This will now go back multiple screens, in my case to the
// bottom of the top stackNavigator
return dispatch(NavigationActions.back({key}));
};
This isn't pretty. Let's hope react-navigation implements something like .back(2) to make things easier in the future.
If you are using push then you can go to root screen with this
this.props.navigation.popToTop();
and to Push
this.props.navigation.navigate('SummaryScreen', {
otherParam: 'anything you want here',
});
You could define an additional navigation action type, e.g. POP_TWO_ROUTES and overwrite StackRouter.getStateForAction(passedAction, state) like that (only exemplary):
...
const defaultGetStateForAction = AppNavigator.router.getStateForAction;
AppNavigator.router.getStateForAction = (passedAction, state) => {
if(state && state.routes && state.routes.length > 2
&& passedAction.type === 'POP_TWO_ROUTES') {
let routes = state.routes.slice();
routes.pop();
routes.pop();
return {
index: routes.length - 1,
routes: routes
};
}
// default behaviour for none custom types
return defaultGetStateForAction(passedAction, state);
}
...
Then in your screen component you can do something like that:
...
onPress={() => this.props.navigation.dispatch({
type: 'POP_TWO_ROUTES'
})}
...
See also https://reactnavigation.org/docs/routers/#Custom-Navigation-Actions.
It sounds like you're looking for something along the lines of #reset (https://reactnavigation.org/docs/navigators/navigation-actions#Reset).
I haven't used this library, but you likely are trying to go back to the navigation stack's root screen. Other libraries I've used for nav have some sort of popToRoot or something similar.
Try this
onPress={() => {
props.rootNavigation.dispatch(
NavigationActions.reset({
index: 0,
key:null,
actions: [NavigationActions.navigate({ routeName: 'Home' })]
})
)
}}
This should work:
this.props.navigation.pop(2)
For React Native V6 2022
Simple Go Back "goBack"
navigation.goBack()
Go Back tow screen or 3 on specific the name "navigate"
props.navigation.navigate("RouteName")
Navigate to the top screen "popToTop"
navigation.popToTop()