React Native: How to handle protected routes with react navigation - react-native

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
}

Related

How to add screen at beginnig with CommonsActions.reset() in react navigation

I want to change screens order in navigation flow reseting navigation state
I want to change screens order in navigation flow, something like
{
condition ? <> <Screen A/> <Screen B/> </> : <> <Screen B/> <Screen A/> </>
}
following [https://reactnavigation.org/docs/auth-flow/#removing-shared-screens-when-auth-state-changes%5C]
How is the same screen this dont change the order (only when get condition from storage when app starts), so i was trying something like this to add screen A at beginnig keeping the others
navigation.dispatch((state) => {
const stateCopy = { ...state };
const routes = stateCopy?.routes?.unshift({ name: 'screen-A' });
// screen a dont have the key as the others in state
return CommonActions.reset({
...state,
routes,
index: 0,
});
});
Basically this doesnt works and i didnt find an example in [https://reactnavigation.org/docs/navigation-actions/#reset%5C] . How can i do this? Thanks.

React Navigation how to setup children routes

I have a React Native Expo application using React Navigation. I think I have all the navigation routes (links) not well setted up.
So in my app let's say I have a Profile screen which has 2 list items to navigate. Application settings & User settings. These two have some more items inside each one.
My navigation tree would be something like:
ProfileScreen (screen with 2 items => User settings | Application settings)
UserSettings:
Language (Navigation to LanguageScreen)
Change password (Navigation to ChangePasswordScreen)
Personal data (Navigation to PersonalDataScreen)
ApplicationSettings:
...
My LinkingConfiguration is configured like:
profileStack = {
initialRouteName: "profile",
screens: {
profile: "profile",
userSettings: "profile/userSettings",
language: "profile/userSettings/language",
changePassword: "profile/userSettings/password",
personalData: "profile/userSettings/personalData",
applicationSettings: "profile/applicationSettings"
}
}
profileStack is a navigation stack. The rest are screens.
I'm wondering how I need to setup well the tree, because if I deep link to myApp.com/profile/userSettings/language, if I go back, I go directly to the initialRouteName which is profile, and I guess this is because the tree is not well generated.
In angular router would be setted as
profile: path: "...", children: [ userSettings: path: "...", children: [ {...}]]
How does navigation tree have to be setted up correctly in my example? Maybe using many navigation stacks?
Can anybody enlight me?
FYI:
React Navigation: v6 (it was also happenning in v5)
React Native: v0.69.5
Expo: v46.0.9
React Navigation allows you to set up the same hierarchy you showed in your example. You want to nest the navigators just as in your depiction.
https://reactnavigation.org/docs/nesting-navigators
Something like:
function Profile() {
return (
<Stack.Navigator>
<Stack.Screen name="UserSettings" component={UserSettings} />
<Stack.Screen name="ApplicationSettings" component={ApplicationSettings} />
</Stack.Navigator>
);
}
function UserSettings() {
return (
<Stack.Navigator>
<Stack.Screen name="Language" component={Language} />
<Stack.Screen name="ChangePassword" component={ChangePassword} />
<Stack.Screen name="PersonalData" component={PersonalData} />
</Stack.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Profile />
</NavigationContainer>
);
}
The structure of navigation is created by how you nest components.

Expo v4 / React : Passing datas between tab screen

Expo v4..
Hello friends,
I am using expo tab navigation:
https://reactnavigation.org/docs/hello-react-navigation
How can I pass datas bewteen expo tabs ?
OrderScreen.tsx:
import { createMaterialTopTabNavigator } from '#react-navigation/material-top-tabs';
const Tab = createMaterialTopTabNavigator();
export default function OrderScreen({navigation, route}) {
let data = route.params
return (
<Tab.Navigator>
<Tab.Screen name="Details" component={DetailsScreen} />
<Tab.Screen name="Photos" component={PhotosScreen} />
</Tab.Navigator>
);
}
PhotosScreen.tsx:
export default function PhotosScreen() {
// HOW TO GET route.params here ??
DetailScreen.tsx:
export default function DetailsScreen() {
// HOW TO GET route.params here ??
I manage to have data in only one tab using :
navigation.navigate('Order', {screen: 'Details' ,params: {datas:someDatas}});
But I want to have the datas in both tabs.
I have been searching everywhere but impossible to find.
I would be very surprise there is no solution for that.
T H A N K S !!!! :-)
finding no answer on the net, I've been using the context api.
for a guy new to react it took a while to get the idea of context but it seems to be quite powerful once settled.

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')}}

Invariant Violation: Maximum update depth exceeded using react Navigation v5

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