React Native Navigator - limit the number of backs - react-native

I'm developing a complex App with many different screens with several forms. The users complain that from time to time it crashes and exits. I assume it can be from too many navigator.pushes so I'm trying to limit the number of backs, but I can't seem to get it working right.
I'm using immediatelyResetRouteStack which I call from Navigator onDidFocus prop.
The problem is, if I call it in Navigator onDidFocus prop it triggers a new render and the screen I'm viewing re-renders after about 1 second. I tried also in a generic function that I call whenever I do navigator.push but then it doesn't do the animation and it is very slow to change to a new screen.
Is there a better way to limit the number of backs?
My code:
_navigatorOnDidFocus(){
let routes=_navigator.getCurrentRoutes();
if (routes.length > 4){
let routesCopy = _.cloneDeep(routes);
let newRoutes = [
routesCopy[0],
routesCopy[2],
routesCopy[3],
routesCopy[4],
];
_navigator.immediatelyResetRouteStack(newRoutes);
}
}

Related

How to force a deep render in React Native on a specific interval?

How can I update a react component once every half second? I have a need to update a component to show new information as it flows in from an api. I want the component to rerender so it can display to new information at a specific interval.
In React, we could use forceUpdate. Unfortunately, in React Native, we don't have that luxury. Luckily, useState offers a deep render every time the setState function is called. This solution is a little hacky, but it gets the job done. Here's how it works:
const [inProgress, setInProgress] = useState(false); //This should be true when you want rerendering to happen.
const [updateCounter, setUpdateCounter] = useState(0); //This is the state the will force the update.
if (inProgress) {
setTimeout(() => setUpdateCounter(updateCounter + 1), 500);
}
This is the simplest way I've found to do it. The interval in this example is set to 500ms. However, it could be set to whatever you want. I chose half a second because I felt it offered a responsive enough feel to the user while also giving enough time for setState to run.

Reload previous screen in React Native

I've been using this way to refresh the previous screen when triggering a button from the current screen.
Screen A
constructor(props){
...
this.handleOnNavigateBack = this.handleOnNavigateBack.bind(this);
}
handleOnNavigateBack = (foo) => {
this.setState({ foo })
}
goToScreenB(){
this.props.navigation.navigate('ScreenB', {
onNavigateBack: this.handleOnNavigateBack
})
}
Screen B
goBackToScreenA(){
this.props.navigation.state.params.onNavigateBack(this.state.foo)
this.props.navigation.goBack()
}
I need to keep the function inside the parameters first so that it can be triggered when going back from screen b. But this is okay to use if it is from 1 screen to 1 screen. If I have a multi layer screen been navigated such as
from Screen A -> Screen B -> Screen C,
and go back
from Screen C -> Screen B -> Screen A then reload the function at Screen A, it will be troublesome.
Or maybe the situation is :
from Screen A -> Screen B, then from Screen B replace Screen C, then from Screen C -> Screen A then only reload the function at Screen A.
By doing so, I have to keep passing the function to reload when every navigation taken place. So I was wondering if there is a way to capture the screen entered and fire the specific function that you want like how Ionic did.
In Ionic case, there is an event called ionViewWillEnter where it helps to perform this reloading thing without going through with the function registration and all. You simply call out the function and that's it.
I've been trying to find a way that is working similar to this at React Native but I can't have it work around. I've heard about redux but never implement it as I still can't understand how it works so I'm trying not to use it first.
Based on your question you are looking for something similar to 'ionViewWillEnter' which will run a callback whenever a screen is focused.
Given that you are using React Navigation v5
The hook useFocusEffect serves the same purpose of running a function whenever a screen is focused this includes navigating back to a screen as well.
From the documentation
Sometimes we want to run side-effects when a screen is focused. A side
effect may involve things like adding an event listener, fetching
data, updating document title, etc. While this can be achieved using
focus and blur events, it's not very ergonomic.
The code would be something like this
useFocusEffect(
React.useCallback(() => {
alert('Screen focused')
// Do something when the screen is focused
return () => {
alert('Screen was unfocused');
// Do something when the screen is unfocused
// Useful for cleanup functions
};
}, [])
);
You can find the documentation here
https://reactnavigation.org/docs/use-focus-effect/
If your requirement is updating the data by passing something from screen B or C you can think of using Context which is easy to use or if you are planning to to use redux that is also a possible alternative.

How to call method whenever screen loaded, Like always should call method in react native

I am developing react native project and I am loading some graphs from server response.
It is a Tab based app and this code is written in first tab.
But, In some use cases that data is not loading to that graph properly.
I have written that code in componentDidMount(), But it will call only once. But, My requirement is I have to call whenever view loaded, That time only render method is calling.
I have tried to add addlistener for navigation, But, Due to its it not navigation stack throwing error.
I have found some solution like below.
componentDidMount() {
}
fetchGraphData = () => {
//some code fetching from DB and redux based on conditions
}
render() {
this.fetchGraphData();
return (
);
}
}
But, This is not good practice as per code standards.
I am not receiving props, But, We are using some graphs which are
loading from data. My requirement is I have to call api fetch data
method after screen load every time.
Any suggestions, I have to call that fetchGraphData() once render method or view loaded.
Your problem is that when you move the 'fetchGraphData' function to a screen with the 'fetchGraphData' function, you must execute it. This problem can be solved by something simpler than I thought.
componentDidMount() {
this.fetchGraphData();
}
You can try rendering again when you move to a screen with a function.
this.props.navigation.push('functionMoveScreen') // Rendering the screen again.

How to deal with state during a screen transition using react native and react navigation

I am getting an object undefined exception during a transition from one screen to another. I have a pretty good idea what is happening, I am just not sure if there is a clean way of handling it without just checking for undefined everywhere.
Here is my scenario:
Source screen references a user object in a Redux store
Navigation is initiated from the source screen to the destination screen.
The destination screen componentDidMount() is called where I clear the user object from the Redux store.
The source screen render() gets called again and the undefined error occurs because I cleared user from the store.
I am assuming that the source and destination screens have some overlap due to the transition. I have tried adding listeners with no luck. I can only get listener handlers to fire for type = action,
I am probably making this more complicated than it is, but I would like to find out if there is a standard way of dealing with this or if I am going about it in a completely wrong way.
Also, my react-navigation is integrated with Redux as per the instructions on React Navigation's website and the Redux React Navigation sample code.
Here is the basic code.
Screen 1
componentDidMount() {
// This is a Redux action that sets an object called user to null
clearUser();
{
Screen 2
render() {
return (
// Other code here
// user is null here because render gets called after Screen1#componentDidMount() gets called
<Text>{user.firstName + ' ' + user.lastName}</Text>
<TouchableOpacity
onPress={() => navigation.dispatch({ type:'NAV_SCREEN_1' })}
>
// button code here
</TouchableOpacity>
// Other code here
)
}
The type of error is "TypeError: Cannot read property 'firstName' of null"
I understand the error and why I am getting it. The user object has been set to null by my Redux action in componentDidMount() of Screen 1 and then Redux causes render of Screen 2 to get called which is referencing a user object which is null.
My question is, is there a special way to accommodate this behavior without having to check if the user object is null every place I need to use it ? Should I call clearUser() somewhere else ?
For the newest version of react-navigation, you can use either addListener, withNavigationFocus or isFocused to detect transition state of the screens.
Another unofficial way is to catch action type NavigationActions.COMPLETE_TRANSITION, in case of using middleware like redux-saga.
As an alternative, you can simply make your Redux-connected screen handle undefined or null state. I had a similar problem and I ended up rendering a loader instead of the screen content when user state is undefined. When I log out, the screen shows a loader for a fraction of a second, then the app navigates away.

How to navigate to a different view that is not a child?

I am new to react native and new to iOS (not programming) so please excuse me if this question is a simple one. I am trying to navigate from one view to another (with a transition), however they are not related so I do not need the back navigation. I actually do not have a navigation bar at all. When using the Navigator component it seems to not support this at all. I am not sure if there is a separate way to do this but I am not able to figure it out without implementing my own hack.
If I use the navigator component and keep pushing on the views then it just keeps them all in memory and I do not want that. I can transition from one view to another and then pop but I may end up going to the wrong view in that case. I can also replace the view but it seems that does not allow for transitions.
To give you a scenario think of it like this:
Application starts and loads a "Loading" screen.
When initial loading is complete it will then go to the "Login" screen.
There is a button on the "Login" screen to "Register" or "Retrieve Password".
If they click "Register" it will take them there with a button back to "Login".
If they click "Retrieve Password" it will take them to a page with buttons to go back to "Login" or "Register".
So by this example you can see that there is no way to pop because if you were on the login screen and went to the register screen and then wanted to go the retrieve password screen then pop just simply wouldn't work. I do not want any navigation controls on the screen I just want to be able to do a smooth transition between these screens.
Now I was able to find a way to do this but I had to add a method to the Navigator class and hack code in using some of there core methods which seems like its not a good idea at all but here is the code (note this is really just a hack to see if it would work):
Navigator.prototype.pushWithUnmount = function(route) {
var activeLength = this.state.presentedIndex + 1;
var activeStack = this.state.routeStack.slice(0, activeLength);
var activeAnimationConfigStack = this.state.sceneConfigStack.slice(0, activeLength);
var nextStack = activeStack.concat([route]);
var destIndex = nextStack.length - 1;
var nextAnimationConfigStack = activeAnimationConfigStack.concat([
this.props.configureScene(route),
]);
this._emitWillFocus(nextStack[destIndex]);
this.setState({
routeStack: nextStack,
sceneConfigStack: nextAnimationConfigStack,
}, () => {
this._enableScene(destIndex);
this._transitionTo(
destIndex,
null, // default velocity
null, // no spring jumping
() => {
this.replaceAtIndex(nextStack[destIndex], 0);
this.setState({
presentedIndex: 0,
});
}
);
});
}
By using the code provided above I am now able to do:
this.props.navigator.pushWithUnmount({ component: SomeComponent });
With this code the views are pushed onto the stack with a transition and the old views are unmounted when its finished.
Please tell me that I am doing something wrong and that there is a better way to do this?
The default router with React Native is pretty limited. I'd check out React Native Router Flux. We just switched to it a few weeks ago in our product and have really liked it. It does exactly what you want.