Localization of React Native navigators - react-native

I am building an app where the users are prompted to choose their language the first time they launch the app. The language is then stored locally using AsyncStorage.
Every time the app launches the language is retrieved from storage and saved into the global.lang variable to be used by all components:
AsyncStorage.getItem('settings', (err, item) => {
global.lang = item.lang;
});
When I use the global.lang variable in the render() method in any component everything seems to be ok. However I run into trouble when trying to use the same variable when initializing my navigators:
const TabNavigator = createBottomTabNavigator(
{
Home: {
screen: HomeScreenNavigator,
navigationOptions:{
title: strings['en'].linkHome, --> this works
}
},
News: {
screen: NewsScreen,
navigationOptions:{
title: strings[global.lang].linkNews, --> this fails
}
}
});
I believe that this because the value is not retrieved from AsyncStorage by the time that the navigators are constructed. If I set the global.lang manually (eg. global.lang = 'en';) it seems to be OK, but not when I try to retrieve it from the storage.
Is there something that I am missing? Could I initialize the navigator with a default language and change the title later based on the value retrived?
Any help would be greatly appreciated.

The navigators are constructed in the app launch. So you would need to use some placeholder text and use the method described here where you change all screen titles based on the screen key...
Or... this sounds insane and i have never tried it. But you can use a loading screen where you retrieve the languaje settings. then... via conditional rendering you "render" a navigator component . Idk if it would work the same way , but you can try it. below some code that i just created for this purpose
export default class MainComponent extends React.Component {
constructor(props) {
super(props);
this.state = { hasLanguage:false};}
componentDidMount(){
this.retrieveLanguage()
}
async retrieveLanguage(){
//await AsyncStorage bla bla bla
//then
this.setState({hasLanguage:true})
}
render() {
return (
{
this.state.hasLanguage?
<View>
//this is a view that is rendered as a loading screen
</View>:
<Navigator/>//this will be rendered, and hence, created, when there is a language retrieved
}
);
}
}
Again. I don't know if react navigation creates the navigator at render . If so. When it creates a navigator , there should be the languaje to be used there

Related

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.

Passing updated state to react-navigation screen

How can I pass new state to a React Navigation function?
My code currently looks like this:
Simplified view of my parent class:
constructor(props){
super(props)
this.state = {
code: "aaa"
}
this.refresh = this.refresh.bind(this)
}
refresh() {
this.setState({
code: "bbb"
})
}
async componentDidMount(){
const {navigate} = this.props.navigation
navigate("Child", {screen: "Screen Two", code: this.state.code, refresh: this.refresh})
}
In the child class I then do the following:
this.props.navigation.state.params.refresh()
The issue I am facing:
Option 1: If I have the code as it currently is, it will not pass the new state value to the navigator because it is not in the render function
Option 2: If I place the code in the render function, it gives me the warning: "Cannot update during an existing state transition".
What am I doing wrong and how can I fix this?
Further details
I am using this main screen to load some of the details from an API on the web and store them in state. I want to be able to pass a refresh function to the second screen that I will be able to use to reload data from the API onto the main screen. Once the data is loaded back into the state on the main screen it should propagate back down to the second screen. This seems easy to do without using a navigator, but I am not sure how to do it with a navigator.
I am not currently wanting to use redux due to the learning curve, but would like to look into it some time in the future.
So you are trying to call refresh() method inside your child component. If you use this inside render function the refresh() method will be called repeatedly and it will give a warning: "Cannot update during an existing state transition".
If you keep the code as it is, it will update the parent class state. But that update will not be reflected when you accessing this.props.navigation.state.params.code. This will only give the value 'aaa'.
Option 1;
You can use redux and easily handle this scenario.
Option 2;
If you really want to know the value of the parent class state you can pass a function as navigation params to child which will return the value of the state.
Parent class.
constructor(props){
super(props)
this.state = {
code: "aaa"
}
this.refresh = this.refresh.bind(this);
this.getState = this.getState.bind(this)
}
refresh() {
this.setState({ code: "bbb" })
}
getState() {
return this.state.code;
}
async componentDidMount(){
const {navigate} = this.props.navigation
navigate("Child", {screen: "Screen Two", code: this.state.code, refresh: this.refresh, getState: this.getState })
}
Inside your child class use the following code to get the parent class state.
let parentClassState = this.props.navigation.state.params.getState();

this.state within react-navigation screen

I'm having trouble accessing and defining state. My app uses react-navigation. Overall within my app I can always work with state (without errors), but within the primary tabbar screens, I get a "null is not an object" error when I use a simple definition of state like I do below.
I am not using redux
export class Review_Screen extends React.Component {
// set title at the top of the page
static navigationOptions = ({navigation}) => ({
title: navigation.state.params.title
});
constructor(props) {
super(props);
const { params } = this.props.navigation.state;
this.state = {
// general ID info
barcode: params.productdata.barcode,
userID: params.user.ID,
username: params.user.name,
expanded: false,
testing: 'hallo hallo',
};
}
render() {
console.log('-_-_-_-_-_-_-_-_-_-_-_-_-');
console.log(this.state.testing);
console.log('-_-_-_-_-_-_-_-_-_-_-_-_-');
const { params } = this.props.navigation.state;
etc...
// results in error "null is not an object (evaluating 'this.state.testing')
I assume this is because I am supposed to work differently with state when I am within react-navigation.
How do I define some local state variables? What is best practice? I will at some point go to redux, but am not ready for that yet.
Well, that's embarrassing. The issue arose because I had my react-native on "hot reloading", while I was coding.
This basically means that I was asking for a variable that wasn't defined, because the constructor wasn't called (the app was live while coding).
Once I restart or reload the app, then it does define the state (because while initially loading the app it calls the constructor().
Hope this serves someone else..

React Native - How to change data in js file using text input

My android app is loading string data from a js file that is looking like this:
module.exports = {
DATA: {
firstdata: 'data',
seconddata: 'data2'},
};
What I want to do, is change content of this file using text input with
onChangeText={(text) => data.DATA.firstdata.setState({text})}
But it tells me that "undefined is not a function".
These are my first attempts with text inputs. Any help will be appreciated :)
it seems you are trying to implement different concepts. You are trying to change js file using setState function, which is a function provided by react to change data in local component state. So, in your case, data.DATA.firstdata should be just a string, that doesn't know what setState function is. But, according to an error, it seems you are not even importing this DATA variable.
What you can do is set this data to your local state when component is mounted, and then mutate this local state with setState function.
...
import { DATA } from './path/to/the/file'
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = {
firstdata: DATA.firstdata,
seconddata: DATA.seconddata,
};
}
render() {
return (
...
<TextInput onChangeText={(text) => this.setState({firstdata: text})}>
)
}
}

Set updated state props on button click using redux

I am trying to create a button which is displaying and hiding a navigation menu on click. I am using Redux to get the current state into the Component, but something is not working with the onPress function.
When pressing the button I want to check the current state of this.state.showNavigation (can be true/false) but I am getting an "undefined is not an object" error immediately after clicking the button.
I think I am running into a lifecycle issue here. I already tried to ship around this via setting the state in componentWillMount like that:
componentWillUpdate(){
this.state = NavigationStore.getState();
}
Anyway that didn't help. Some advise is much appreciated. Thanks!
Heres my code:
class NavigationButton extends React.Component {
constructor(props, context) {
super(props, context);
this.state = NavigationStore.getState();
NavigationStore.subscribe(() => {
this.setState(NavigationStore.getState());
});
// alert(this.state.showNavigation);
}
render() {
return (
<TouchableOpacity
onPress={this.handlePressButton}
style={navigationButtonStyles.button}>
<Image source={buttonImage} />
</TouchableOpacity>
);
}
handlePressButton() {
if(this.state.showNavigation){
NavigationStore.dispatch({
type: 'HIDE_NAVIGATION',
});
}
else{
NavigationStore.dispatch({
type: 'SHOW_NAVIGATION',
});
}
}
};
I was using a pretty strange approach, I did not use the react-redux package for the whole thing and couldn't connect my store. I deep dived into https://github.com/bartonhammond/snowflake and got it solved, the snowflake example was really helpful to understand the basics!