Passing updated state to react-navigation screen - react-native

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

Related

Reload a React Native class component

I have a class component directions in my project. I navigate to another component from it using this.props.navigation.navigate(). Now the problem is that I want to navigate back to the same directions component but with passing new values, ie I want it to reload from scratch, defining state variables once again. How can I do it?
Using navigation.navigate() simply takes me back to the previous state the screen has been.
this.props.navigation.navigate('direction',{
riderLocation:this.state.rideInfo.location,
ride_id:this.state.ride_id,
});
And this is the componentDidMount of directions.
componentDidMount(){
alert('componentDidMount');
const {navigation,route}=this.props;
this.state.riderLocation = navigation.getParam('riderLocation');
this.state.ride_id= navigation.getParam('ride_id');
}
In the "directions" component, use "componentDidMount" method.
Inside "componentDidMount" method, call a function which updates the state value as desired.
Once you are redirected back to the "directions" component, then "componentDidMount" will run and the state will be updated.
====
Edit:
Try using componentDidUpdate() method in "directions" component.
componentDidUpdate(prevProps, prevState) {
if (prevProps.navigation.getParam('ride_id') !== this.props.navigation.getParam('ride_id')) {
const {
navigation,
route
} = this.props;
this.setState({
riderLocation: navigation.getParam('riderLocation'),
ride_id: navigation.getParam('ride_id')
})
}
}
Also instead of "this.state.riderLocation" and "this.state.ride_id" use this.setState in componentDidMount(), just like I have written in componentDidUpdate().

React Native Multiselect

I am new to React Native
I am trying to create a multiselect view where user can select and deselect the items and then the selected items should pass back to the previous container and when user comes back to the next view the selected items should be checked.
I am trying to implement but getting the issue it is not updating data accurately. It shows only 1 selected item when I came back again to the screen.
Can anyone tell me the best way to do that or if there is any tutorial.
Should I do it with Redux or using react native?
Any help would be appreciated!!
Thanks!!
I believe the issue you describe is due to the following:
In componentDidMount you are calling updateItemWithSelected in a loop. This updateItemWithSelected call is both overwriting the checked attributes for all of the arrayHolder values on each call and also not using the updater function version of setState, so the later call of the loop may overwrite the earlier calls since setState is async and batched. If you are not using updateItemWithSelected elsewhere you should simplify componentDidMount to:
componentDidMount() {
const selectedTitles = {};
const { state } = this.props.navigation
const params = state.params || {};
if (params.data.length){
params.data.forEach((element) => {
// create a map of selected titles
selectedTitles[element.title] = true;
})
}
const arrayHolder = this.array.map(item => {
// map over `this.array` and set `checked` if title is in `selectedTitles`
return {...item, checked: !!selectedTitles[item.title]};
});
this.setState({ arrayHolder });
}
and delete updateItemWithSelected.

Localization of React Native navigators

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

ReactNative componentDidMount doesn't get called

I have two screens: A and B, connected with a StackNavigator
Screen A is a QR code scanner. As soon as a QR code is scanned, it navigates to screen B.
In screen B, I make an API call using the QR code that gets passed as a navigation param from screen A. I trigger this API call in componentDidMount.
My issue is: if I navigate from A to B, then back to A, then to B again, componentDidMount does not get called and I have no way to trigger the API call.
EDIT:
Here's some code
Screen A
Handler function that gets called when a QR code is scanned:
handleQRCode = qrCode => {
NavigationService.navigate('Decode', {qrCode});
};
Screen B
The QR code is pulled from the navigation state params and used for an API call (startDecode) through redux.
componentDidMount() {
qrCode = this.props.navigation.state.params.qrCode;
this.props.startDecode(qrCode.data);
}
My issue is that componentDidMount only gets called the first time that route is taken.
In react-navigation each screen is kept mounted. This means that when you you go back to B, you might have changed the props, but componentDidMount was already invoked in the first creation of this screen.
There are two options available for you (AFAIK) that can handle this case:
Instead of calling this.props.navigation.navigate() you can use
this.props.navigation.push which will create another instance of
screen B, thus invoking the componentDidMount React lifecycle
event.
In screen B you can catch the event where its props have changed.
This can take place in the new static lifecycle event
getDerivedPropsFromState or it can be done in the soon to be
deprecated componentWillReceiveProps.
I was facing a similar issue and I used this.props.navigation.addListener() to resolve it. Basically, force-calling componentDidMount() may be possible by pushing same screen again using a key (I haven't tried it) but your stack will keep growing as well, which is not optimal. So, when you return to a screen already in stack, you can use addListener() to see if it is being re-focused, and you can replicate you componentDidMount() code here:
class MyClass extends Component {
someProcess = () => {
// Code common between componentDidMount() and willFocus()
}
componentDidMount() {
this.someProcess();
}
willFocus = this.props.navigation.addListener(
'willFocus',
(payload) => {
this.someProcess();
}
);
}
When MyClass is called for the first time, componentDidMount will get called. For the other times when it is still in stack but instead just gains focus, addListener will get called.
This happens because the B component is mounted only on the first time it is accessed, so componentDidMount won't be called again.
I recommend you to pass a callback to the setOnNavigatorEvent method of your navigator, with the 'didAppear' event. Your callback will be invoked on every event emitted by react-native-navigation, and you can verify to do your logic every time the screen appears (hence the use of 'didAppear' event). You can base your code on the following:
export default class ExampleScreen extends Component {
constructor(props) {
super(props);
this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
}
onNavigatorEvent(event) {
if (event.id === 'didAppear') {
// do API call here
}
}
}

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..