React Native StackNavigator Title Change Causes Recursion - react-native

I've split out the navigationOptions to the top of each screen to make things easy to understand, rather than keeping it in the TabNavigator. Here's an example of one screen:
static navigationOptions = ({navigation}) => {
const {params = {}} = navigation.state;
return {
title: params.title != undefined ? `${params.title}` : "Step 1: Select a Letter",
tabBarOnPress: ({...props, scene})=>{
params.onFocus();
},
titleStyle: {
textAlign: 'center'
}
}
};
Once a user selects a letter on this screen, I switch the view to show a different component (a list of items that start with that letter, for example).
I'd like to change the title of the TabNavigator Header to now show something like "Step 2: Select an item from the list". I saw on a thread that you can call a method from the method that rebuilds the view like this:
_letterSelectedHandler = (letter) => {
this._changeTitle("Step 2: Select an item...");
...
}
With the method:
_changeTitle = (title) => {
this.props.navigation.setParams({title});
}
Unfortunately, it seems to create an infinite loop. Is there a better way of simply changing the TabNavigator Header Title?
I should mention that I'm not using Redux or anything too complex, since I'm new to React Native. Please let me know if I can provide any more information to help illustrate the issue I'm having.

Related

Dynamic titles with function components

Recently I started rewriting some of my legacy code that used class components to modern hooks. The issue I have is that I used this to set header title like so:
static navigationOptions = ({ navigation }) => ({
title: someVar,
})
Now, I have to do it like so:
MyScreen.navigationOptions = ({ navigation }) => {
return {
title: navigation.getParam('headerTitle'),
}
}
and then
useEffect(() => {
navigation.setParams({
headerTitle: 'Some title',
})
}, [])
Which works fine with static screen titles. But for dynamic titles it does not. It takes a second to update the title, first it renders with empty title. Which is explainable, given the method. It worked perfectly with class components. Is there a better way to do this?
You are setting the params (setParams) passed to the screen and not the options (setOptions) of the screen itself which is what's causing the odd behaviour you are experiencing.
I am not sure what your use case is so I can't tell you which one to use but there are two ways to set the title in react navigation. Either from the navigator using the options parameter or from inside the component using navigation.setOptions take a look at this https://reactnavigation.org/docs/headers

Passing params in React Navigation 5

I'm trying to pass a few params between a Tab Navigator. Below is the structure of my program. In bold are the routes
App(Tab Navigator): { Main(stack) & Filter(screen) }
Main(Stack Navigator): { Home(screen) & MediaDetails(screen) }
I have a button on the screen associated with Filter which has an onPress() function. I'm passing a few params(but let's only consider the param to for the sake of this question).
this.props.navigation.navigate('Home', {
to: this.state.to,
}
Now in the screen associated with Home, I'm reading the param inside the state like this:
state = {
to: this.props.route.params.to
}
Inside App.js, I've set the initial value of to to be '2020' like this:
<Stack.Screen
name="Home"
component={HomeScreen}
initialParams={{
to: '2020'
}}
/>
The initial value is indeed set to 2020. I press the button. Let's say I'm setting to to 1900. Just before this.props.navigation.navigate executes, I console.log(this.state.to) the value and it is indeed updated to 1900. However, as the screen changes to Home, the value reverts back to 2020(observed via console.log)
Could someone point out the cause for this spooky behavior? I've been trying to debug this for many hours with no luck. React Navigation 5 is pretty new as well so couldn't find anything similar online. Any help will be greatly appreciated. Thank you for reading all the way.
Edit: Issue has been resolved and full code has been removed!
/* 1. Navigate to the Details route with params */
onPress={() => {
navigation.navigate('Details', {
itemId: 86,
otherParam: 'anything you want here',
});
}}
/* 2. Get the param */
const DetailsScreen = ({ route, navigation }) => {
const { itemId } = route.params;
const { otherParam } = route.params;
return (
<View></View>
);
};
for more info: https://reactnavigation.org/docs/params/
I solved this problem by updating the state in shouldComponentUpdate().
This bug was due to the state not being updated as the screen remained mounted.
This was possible because of ravirajn22 on GitHub who explained to me the reasoning behind this bug and a possible solution.
Link to the answer: https://github.com/react-navigation/react-navigation/issues/6863
As it is stated Here, you can do it like this:
navigation.navigate('otherStackName', {
screen: 'destinationScreenName',
params: { user: 'jane' },
});
In Functional Component write simple logic like this:-
In the parent component
<TouchableOpacity onPress={()=>navigation.navigate('childRoute', {data_Pass_to_Child: "Hello I am going to child component"})}>
In the child component
const Chat = ({route}) => {
console.log(route.params.data_Pass_to_Child);
return(whatever based on your UI)
}
Note:
Replace childRoute with your route name and also replace data {data_Pass_to_Child: "Hello I am going to child component"} with whatever data you want to pass.
send
props.navigation.navigate('YourScreenName', {
your_key: any_value
});
receive in YourScreenName.js
var valueReceived = props.navigation.state.params.your_key;

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

Conditional React Navigation header button

I am trying to make a button appear in headerRight of the header bar in a React Navigation screen, but I cant seem to get it to work.
I want the start button (what is in headerRight) to show when props.players.length > 1.
Can anyone point me in the right direction?
You can use the same mechanics describe here for title: https://reactnavigation.org/docs/en/headers.html#setting-the-header-title
Set a navigation params and use it on your navigationOptions.
In your case:
state = { players: 0 };
static navigationOptions = ({ navigation }) => {
return {
headerRight: navigation.getParam('players', 0) > 1 ? <YourHeaderButtonComponent /> : null ,
};
};
addPlayer = () => {
this.setState(({players}) => {
this.props.navigation.setParams({players: players + 1})
return {players: players + 1 }
});
}
render {
...
<Button onPress={this.addPlayer}
...
}
If you have a nested navigation for example a child bottomTabNavigator inside a parent stackNavigator please look att applying parent screen options based on child navigator's state see documentation here
For me the second option with Using navigation.setOptions worked with a child bottom tab navigator so I could change stackNavigator header options based on bottomTabNavigator page.

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()