Invariant Violation: Maximum update depth exceeded using react Navigation v5 - react-native

I have a login screen which I have placed in stack. After user logs in successfully he is redirected to home screen which is a drawer screen. One of the options of drawer screen is logout, so on click of it user should be logged out. Following is my code for logout screen. I am just showing a progress bar in logout screen in ui but in useEffect hook, I am calling the following code
navigation.reset({
routes: [{name: LOGIN_SCREEN}],
});
I also tried calling the above method in useLayoutEffect but then the logout button just hangs.
My Navigation stack looks something as follows
<Stack.Navigator>
<Stack.Screen
name={LOGIN_SCREEN}
component={LoginScreen}
options={{headerShown: false}}
/>
<Stack.Screen
name={HOME_STACK_SCREEN}
component={DrawerStack}
options={{headerShown: false}}
/>
// ...
</Stack.Navigator>
My drawer component as follows
<Drawer.Navigator
drawerStyle={{backgroundColor: BLUE_COLOR_1}}
drawerContentOptions={{labelStyle: {color: '#FFF'}}}
>
<Drawer.Screen
name={HOME_SCREEN}
component={Home}
options={{
// ...
}}
/>
<Drawer.Screen
name={LOGOUT_SCREEN}
component={Logout}
options={{
// ...
}}
/>
// ...
</Drawer.Navigator>
Following is my logout component
const Logout = ({navigation}) => {
async function logout() {
try {
await AsyncStorage.clear();
navigation.reset({
index: 0,
routes: [{name: LOGIN_SCREEN}],
});
} catch (e) {
Alert.alert(e.toString());
console.log(e.toString());
}
}
useEffect(() => {
logout();
}, []);
return <ProgressBar />;
};

Invariant Violation: Maximum update depth exceeded using react Navigation v5 occurs when something going in an infinite loop
navigation.reset({
routes: [{name: LOGIN_SCREEN}],
});
When you say you put the above code in useEffect, I suspect that hook is getting called multiple times.

I saw you code, to be honest it does't look good to me if we think of auth flow, Here are reason why I am saying this. Do check this link step by step , you get to know how simply you can achieve this auth flow in proper manner. React Navigation Auth Flow, this problem comes when we try to navigate screen from android back button , user can go back to login after you navigate user to home page on success login
Second you can try one condition in you current code.
You need to check one condition before calling the logout function in you screen
useEffect(()=>{
if(check if user value if still there in async storage ) {
**Here you can excute you code for log out **
}
}
And instead of using reset action from navigation use popToTop , Here you can find the ref how you can us PopToTop or use this
navigation.popToTop()
Cheers !!

Related

Navigate from inner navigator to outer navigator generate extra screen slide

I have a problem during the logout in my react native app. I set many navigators to handle multiple navigation flow, but when I try to logout from the app, the login screen appears two times.
I try to explain how I organize my navigators below:
I created a TabNavigator which handle the main navigation of the app if a user is logged in otherwise I show the LoginNavigator which handle login, registration and password forgot screens.
I use a state, stored on redux for handling the login state (isLoggedIn).
<NavigationContainer>
{isLoggedIn ? <TabNavigator/> : <LoginNavigator/>}
</NavigationContainer>
Into the TabNavigator I set other navigator for handling page flow in each sections, like the exemple below:
TabNavigator
Screen X
Navigator1
Screen A
Screen B
Navigator2
Screen C
Screen D
Screen Y
Into the Screen C I'd like to logout from the app, so I call an action which set the isLoggedIn state to false.
What happens is that the Login screen (the inital screen of LoginNavigator) appears, but it suddently slides off and another Login screen appears.
It happens only from nested screen, instead if I try to logout from Screen X or Screen Y it works perfectly fine.
Does anyone encounter this problem? How can I solve this behaviour?
I solved this behaviour!
Instead of showing the correct navigator using a ternary operation, base on redux state, I wrap TabNavigator and LoginNavigator inside a custom stack navigator with a WaitingScreen.
I set the WaitingScreen as inital route and I moved the logic about which route between MainNavigator and LoginNavigator has to be shown inside WaitingScreen, in a useLayouEffect with isLoggedIn as dependecy.
<NavigationContainer>
<Stack.Navigator
initialRouteName={NavConst.LOADING}
screenOptions={{
headerShown: false,
contentStyle: {
backgroundColor: Color.primary600,
},
animation: "none",
}}
>
<Stack.Screen name={NavConst.LOADING} component={WaitingScreen} />
<Stack.Screen name={NavConst.LOGIN_NAV} component={LoginNavigator} />
<Stack.Screen name={NavConst.MAIN_NAV} component={MainNavigator} />
</Stack.Navigator>
</NavigationContainer>
and inside the WaitingScreen:
useLayoutEffect(() => {
isLoggedIn
? navigation.navigate(NavConst.MAIN_NAV);
: navigation.navigate(NavConst.LOGIN_NAV);
}, [isLoggedIn]);

React Native: How to handle protected routes with react navigation

What I want is to protect specific screens where the user can run specific actions and only at that point redirect him to a login screen, if the user is successfully authenticated, redirect him to the point where he left off the flow.
We can do this in React JS(with react-router), so I would like to know if its posible to implement a similar solution for react-native(with react-navigation):
React JS approach with React Router
function RequireAuth({ children }) {
const { authed } = useAuth();
const location = useLocation();
return authed === true ? (
children
) : (
<Navigate to="/login" replace state={{ path: location.pathname }} />
);
}
This is possible in react native and is documented in the authentication workflow section of the documentation.
In summary we conditionally need to render screens inside the navigator. Here is a minimal example using a Stack.Navigator which is similar to your posted code snippet. The workflow is identical for other navigators (nested or not). I will use the context api in my example, but there are other ways to achieve the same as well. The overall pattern is the same.
const AppContext = React.createContext()
function App() {
const [isSignedIn, setIsSignedIn] = React.useState(false)
const appContextValue = useMemo(
() => ({
isSignedIn,
setIsSignedIn,
}),
[isSignedIn]
)
return (
<AppContext.Provider value={appContextValue}>
<Stack.Navigator>
{!isSignedIn ? (
<Stack.Screen
name="Login"
component={LoginScreen}
/>
) : (
// whatever screens if user is logged in
<Stack.Screen name="Home" component={HomeScreen} />
)}
</Stack.Navigator>
</AppContext.Provider>
);
In the LoginScreen, you then need to change the authed state to true if the login was successful. The first screen in the conditional will then be initially rendered automatically (in this case HomeScreen).
function LoginScreen() {
const { setIsSignedIn } = useContext(AppContext)
// do whatever, then setIsSignedIn to true
}

Navigating from a nested screen to another nested screen in a different Tab Stack (react-navigation)

I'm using react navigation 5 (with react native) and my navigation set up looks like this:
// Root
<NavigationContainer>
<RootStack.Navigator mode="modal" headerMode="none">
<RootStack.Screen name="Auth" component={AuthStackNavigator} />
<RootStack.Screen name="App" component={BottomTabsNavigator} />
</RootStack.Navigator>
</NavigationContainer>
// App
<Tab.Navigator tabBar={bottomTabBar}>
<Tab.Screen name="ScreenA1" component={StackA} />
<Tab.Screen name="ScreenB1" component={StackB} />
</Tab.Navigator>
// TAB A
<StackA.Navigator headerMode="none">
<StackA.Screen name="ScreenA1" component={ScreenA1} />
<StackA.Screen name="ScreenA2" component={ScreenA2} />
</StackA.Navigator>
// TAB B
<StackB.Navigator headerMode="none">
<StackB.Screen name="ScreenB1" component={ScreenB2} />
<StackB.Screen name="ScreenB2" component={ScreenB2} />
</StackB.Navigator>
So inside my App i have 2 Bottom Tabs (Tab A and Tab B) each having 2 nested screens (Screen A1>A2 and B1>B2).
So when i tap on Tab A i go to ScreenA1 then in there i can move on to ScreenA2. Same for Tab B.
Now on ScreenB2 from Tab B i have a button that should navigate the user to Screen A2 with some data to prefill on that screen.
If i do it like this:
navigation.navigate('ScreenA1', {
screen: 'ScreenA2',
params: { data },
});
I'll land on ScreenA2 with the data, but:
If i visited ScreenA2 before, the previous state persists and i can see its old state and data.
If i never visited ScreenA2 before it's now not inside the Tab A Stack, but instead pushed on top of ScreenB2.
So i guess i would need to reset the screen before i navigate to it (or unmount whenever i leave it) and also make sure that it gets put inside the Tab A Stack on creation, so when i try to call goBack from Screen A2 i land on Screen A1. Maybe navigate back to root first and call the screen from there without the user seeing all screens flashing up.
You can replace
navigation.navigate('ScreenA1'...
By
navigate.push('ScreenA1'...
That way, a new route will be added to your navigation stack, with new state and props.
Resolved it with this:
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [
{
name: 'ScreenA1',
state: {
routes: [
{
name: 'ScreenA1',
},
{
name: 'ScreenA2',
params: { data },
},
],
},
},
],
}),
);
Notice how i have 'ScreenA1' twice in there. I guess that's necessary because the Navigator and the first Screen inside the Navigator share the same route name

React Native Error: The action 'NAVIGATE' with payload {"name":"GameScreen"} was not handled by any navigator

I know that this issue has been brought up before but I have not found any answer that works for me.
I simply want to navigate from screen "Start" to screen "Game", using App.js as the router.
App.js:
const Stack = createStackNavigator();
const App = () => {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Start">
<Stack.Screen name="Start" component={StartingScreen} />
<Stack.Screen name="Game" component={GameScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
StartingScreen.js:
const StartingScreen = ({ navigation }) => {
//lots of code
return (
//more code
<Button title="Begin" onPress={() => {navigation.navigate('GameScreen')}}/>
)
This gives me the error in the title with ("Do you have a game named 'GameScreen'?") and nothing happens. I have tried following the React Navigation docs, but in their example they put everything in App.js, that does not work for me. Other things I have tried include exporting the navigation stack to StartingScreen.js, changing the arguments of navigation.navigate(), placing the navigator inside StartingScreen.js.
GameScreen is spelled exactly the same in all places.
Change your code like below
const StartingScreen = ({ navigation }) => {
//lots of code
return (
//more code
<Button title="Begin" onPress={() => {navigation.navigate('Game')}}/>
)
You are giving the component name but you should provide the name of the screen that you give in the stack which is 'Game'
As your screen name is given 'Game' in the stack navigator, you should navigate to 'Game' instead of 'GameScreen'.
onPress={() => {navigation.navigate('Game')}}

Reset StackNavigator from within the Component

BROAD PROBLEM (for individuals searching for the same solution):
I have an app with a switch-based StackNavigator ("MainStack"). I would like to reset MainStack as {index: 1, routes: [{name: 'Home', name: 'NestedStack'}]}, and I would like to call this from within the MainStack component (see below)
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
{
anon ?
<Stack.Screen name="Auth" component={AuthScreen} />
<Stack.Screen name="MainStack" component={MainStackScreen} />
}
</Stack.Navigator>
</NavigationContainer>
);
}
const MainStack = createStackNavigator();
function MainStackScreen({navigation, route}) {
*** An event occurs here ***
*** And I'd like to respond to the event with a navigation.reset() ***
return (
<MainStack.Navigator>
<MainStack.Screen name="Home" component={Home} />
<MainStack.Screen name="NestedStack" component={NestedStackScreen} />
</MainStack.Navigator>
);
}
I have tried several different combinations of navigation.dispatch({...CommonActions.reset({}), target}), producing different errors from the screen not existing to issues with screens.options.
I would also greatly appreciate advice on how to specify which screen to start with for NestedStack... would this just be {name: 'NestedStack', screen: 'NestedScreen'}??
SPECIFIC USE-CASE:
What I am really trying to achieve is Linking. I have users opening links when the app is closed (which I am accessing via Linking.getInitialURL) and from Camera screens within the app (which I am listening to via Linking.addEventListener). I have placed both of these in the MainStack component to intercept links as the app switches from the auth flow and as a QR code is scanned within the app.
I have to listen to changes in the MainStackScreen route, then set a variable isNavigatorMounted to true once the route is fully mounted, and then that triggers my check for Linking.getInitialURL.
Please address the Broad Question for general StackOverflow users, but if this is a terribly inefficient method, please let me know.
This answer was provided https://discord.com/channels/102860784329052160/288388742929055765/770038660220715028
You do not need CommonActions or anything. Simply apply the following to the StackNavigator's navigation object:
navigation.reset({
routes: [{ name: 'Home' }, { name: 'NestedStack' }],
});