Dynamic titles with function components - react-native

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

Related

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;

i18n translation for "Back"-text in React Navigation on iOS

I'm using React Navigation and a StackNavigator in a React Native app. I'm able to translate the navigationOptions.title, however, on iOS if there is too much text in the header it defaults to showing the text "Back" next to the back button. I don't mind this, but I want it to say "Back" in my current language. How can I achieve this? The text I'm talking about:
An example of how I use the navigationOptions now:
class Example extends Component<Props> {
static navigationOptions = () => ({
title: i18n.t('example_title'),
});
// ...
}
In case it is relevant we're using react-native-localize with i18n-js for the i18n functionality. I don't want it to always say the previous screens name, or always back, I want it dynamically as it is now, just with i18n.
The navigationOptions object has an key for this called headerTruncatedBackTitle.
Title string used by the back button when headerBackTitle doesn't fit on the screen. "Back" by default. headerTruncatedBackTitle has to be defined in the origin screen, not in the destination screen.
You can for example utilize this with i18n simply by doing:
class Example extends Component<Props> {
static navigationOptions = () => ({
title: i18n.t('example_title'),
headerTruncatedBackTitle: i18n.t('example_back'), // "Back", "Zurück", etc.
});
// ...
}
In my application in Main AppStack createStackNavigator where you combine all of your screens, I have added a second parameter an object in which we give it a key
createStackNavigator({
...screens
},
{
defaultNavigationOptions: {
headerBackTitle: i18n.t('example_title')
}
})
This will set the Back button to the current language of the entire application.

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

React Native StackNavigator Title Change Causes Recursion

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.

How to handle a button press inside react-navigation titlebar

I am pretty new to react-native and I am currently handling navigation for my app using react-navigation.
I want to be able to add a button to my app's title bar created by react-navigation and be able to press a button to call a function.
static navigationOptions = ({ navigation }) => {
const { params } = navigation;
return {
title: 'Assign To',
headerRight: (
<Button
title='Back'
onPress={() => params.handlePress()}
/>
),
};
};
This is close to what I read from the react-navigation docs, but it I keep getting an error of undefined is not an object.
componentDidMount() {
this.props.navigation.setParams({ handlePress: () => this.myFunction.bind(this) });
}
I have myFunction defined inside my component as follows
myFuntion() {
//...
}
Any help is highly appreciated
I'm going to answer this, with a little more explanation. The problem as you know was that you were combining the syntax of two similar approaches. You can use either the .bind syntax: handlePress: this.myFunction.bind(this) or the so-called fat-arrow syntax: handlePress: () => this.myFunction. They are more-or-less equivalent and there are opinions that the fat-arrow syntax supersedes the bind syntax (and, as always, opinions to the contrary :) ). There are differences, such as (quoting from this):
Arrow functions are always anonymous, which means you can't, for
instance, reliably call them recursively (since you don't have a
reliable lexical name to use).
Arrow functions actually create
lexical bindings for this, super, and arguments. Only this is bound
by .bind(..).
Arrow functions cannot be used in new expressions,
while bind(..) bound functions can.
Also, given that this is a react native question, it might help to be aware of this interesting article:
https://medium.freecodecamp.org/why-arrow-functions-and-bind-in-reacts-render-are-problematic-f1c08b060e36
For most elements you can use the onPress() function in react-native
onPress={() => { Alert.alert("You clicked me") } }
If you need to bind you can usually do it with the following example:
onPress={this.functionName.bind(this)}