Building React Navigation v5 stack with Stack Navigation and Tab Navigation - react-native

I'm currently attempting to get my head round how to build out my React Navigation stack with v5, and not using a switchNavigator.
My basic app structure is such (will post an image of a flow below):
check Auth
-> signed in yes
-> tab navigation (with stack navigators nested within)
-> signed in no
-> nested stack navigator
But I just can't figure out how I should build the root of my app. I don't know how to combine my tab navigator and stack navigator together to make my app function from start to finish.
(It's worth noting, the 'check Auth' is an actual screen, not a conditional)
Here's my illustration showing my project layout:
Can someone advise please how I can build this out? Any tips or general structure / advice on how best to do it would be really appreciated!

So what I've typically done in the past is the render either or, not both. So as an example, the root navigation could look as follows:
import { NavigationContainer as RootNavigationContainer } from "#react-navigation/native";
const Navigation = () => {
const { someToken } = useContext(SomeContext);
return (
<RootNavigationContainer>
{!someToken ? <AuthStack /> : <MainStack />}
</RootNavigationContainer>
);
};
The determining of the user's authenticated state should be done while the Splash Screen is being shown, using a separate component just confuses the flow IMO. If the user is authenticated, you can set some state (in the example a token is set on the context) which will then drive which navigation stack is shown.
In my example, AuthStack would contain all of the screens where you user is unauthenticated - so your login, registration etc. MainStack would contain the screens that should only be shown to authenticated users.
Things like FaceID should be included in your Login flow.

Related

Auth Stack Navigation with "Cannot update a component from inside the function body of a different component"

In react-native I am using react-navigation v5 and I have two stack navigators: one for public screens and one for authenticated screen.
App -> Navigation Container -> PublicStack || AuthStack
PublicStack = SelectPlatform -> Login
AuthStack = Home
I am using MobX store where I have a flag (named isLoggedIn) which gets updated when the user loggs in. According to https://reactnavigation.org/docs/auth-flow/ I am returning this in the render method of App:
return (
<NavigationContainer>
{this.props.store.auth.loggedInIndicator ? (
<AuthStack.Navigator>
{screens}
</AuthStack.Navigator>
) : (
<PublicStack.Navigator>
{screens}
</PublicStack.Navigator>
)}
When I navigate from Select Platform Screen to Login Screen and when i change the loggedInIndicator (which is in the store) I get the warning:
Warning: Cannot update a component from inside the function body of a different component
The error is pretty self-explanatory because I am aware that I am causing re-render of the parent by making action into the child component, therefore rendering it with different stack navigation. Do you know any workaround? I tried searching in other posts, but i didn't find anything similar to my case. Additionally I am using Class components where I cannot use hooks.

React Navigation error: not handled by any navigator

I realize why this occurring but am trying to figure out the best way to go about getting the required behavior:
Let's say I have a tab navigator and inside those tabs I have stack navigators. When I first open the app I am in the Home tab. Now let's say a push notification comes in and I handle that and say go to this screen in the profile stack navigator. The screen is not usually the first screen in the navigator but because I have not navigated to the profile screen via the tab navigator the initial screen is not loaded so it's the first in the stack. If I call navigation.pop it will not be handled by a navigator because their is no screen to go back to in the profile navigator.
I figured I could just call navigation.navigate('Profile') and it does navigate to that screen but it doesn't pop the initial screen so clicking the tab by default will now make the first screen the base screen until the app is restarted.
If I call navigation.goBack() I won't run into the above problem but I won't be able to always ensure that the 'Profile' screen is the place it goes back to.
Ideally I'd like some way to say push this screen into this stack and then push this screen. So when pop is called it will always show that users profile screen.
** EDIT **
After looking through some docs I found that I can do the following:
navData.dispatch(() => {
navData.navigate('Home', {
screen: 'ProfileScreen',
});
navData.navigate('Home', {
screen: 'ProfileScreen',
params: {
screen: 'ViewPostScreen',
params: { shouldPlay: true },
},
});
});
Though I get the following warning: Possible unhandled Promise rejection: TypeError: undefined is not an object 'action.target'
I'm also using this logic in another location of my app with a different screen and works in development but not in production. Looking for suggestions on to best handle the above situation.

How to pass parameters to a deep react-navigation Stack Navigator

I have a react-native app using react-navigation. I want to pass some props all along the stack navigator, but not sure what is the right thing to do.
To give more details, my screens are:
ListThingsScreen: List all things.
ShowThingScreen: Show details of the thing.
EditThingScreen: Edit the thing.
They are organized in such way:
const HomeStack = createStackNavigator(
{
ListThings: ListThingsScreen,
ShowThing: ShowThingScreen,
EditThing: EditThingScreen,
},
{
initialRouteName: "ListThings"
}
);
export default createBottomTabNavigator({
HomeStack
});
An app user:
Log in.
See all things (in ListThingsScreen).
Click on one of them, jump to ShowThingScreen to see its details.
Click on a button of the details screen, jump to EditThingScreen to edit the thing.
Once finished editing, jump back to ShowThingScreen.
In ListThingsScreen componentDidMount(), I will make async call to fetch logged in user data such as userId, username, etc. I want all screens in the stack knows about them to behave correctly when show and when edit.
Solutions I can think of:
Pass parameters to routes, as suggested by react-navigation docs. This works, but as I have more screens in the stack, I write too much code like navigation.push("RouteName", {user}) and navigation.getParam("user", "No user").
This is pretty much the only solution I have found. An alternative is to use React context. I like this approach better, however I want to use react-navigation and the concept of Router and Route don't exist in the library.
// ListItemsScreen.js
import { BrowserRouter as Router, Route } from "react-router-dom";
const UserContext = React.createContext();
<UserContext.Provider value={{ user }}>
<Router>
<div className="app-container">
<Route path="/show/:thingId" component={ShowThing} />
<Route path="/edit/:thingId" component={EditThing} />
</div>
</Router>
</UserContext.Provider>
// ShowItemScreen.js
<UserContext.Consumer>
{({user}) => (...)}
</UserContext.Consumer>
Which approach is more "correct"? If it is the 2nd approach, how can I do it with react-navigation?

App Navigation state persisting EVEN IF app closed and restarted

I have a react-native app with two screens, Home and Details. Using react-navigation, Ive set the Stack navigator as following
const RootStack = createStackNavigator(
{
Home: FormComponent,
Details: DetailScreen
},
{
initialRouteName: "Home",
headerMode: "none"
}
);
Home contains a form, which once submitted, navigates to the Details screen with relevant data (using navigation.navigate("Details",{some data})). At this point, if I exit the app, and then open it again, the Details screen loads, with all the data preserved(Instead of the Home screen). I logged the navigation object data (this.props.navigation.) and it prints like the app was never closed.
Am I missing something here? Im new to React Native and Navigation, but from what I understand this is not expected behaviour.
Tried uninstalling app and rebuilding. This resets the app and Home screen loads. If I try reinstalling without uninstalling, back to same behaviour.
Tried also manually forcing navigation.goBack() on ComponentWillUnmount() but no difference.
This should've been a comment but sadly i don't have enough reputation.
Could you check if you haven't accidentally set a persistenceKey as a navigator prop?
https://reactnavigation.org/docs/en/state-persistence.html

React Navigation - Reset Stack Router to initial route without knowing which route that is

I'm using React Navigation with a Tab Router that has multiple Stack Routers nested inside.
In one screen that can be accessed in any of the stack routers, I want to have an action that resets the stack router (in which the screen is) to the initial route of that stack router.
For example: If I'm on the Home stack router I want it to reset to the Home screen, if I'm on the Profile stack router I want it to reset to the Profile screen.
Is there any way to do this?
Note: I'm not integrating with redux, would redux integration make this easier?
I think the simplest way could be without redux passing the redirected screen - the initial screen of that stack- as a parameter in every navigate.
For example,
this.props.navigation.navigate('CommonScreen1', { initial: 'Home'});
this.props.navigation.navigate('CommonScreen2', { initial: 'Home'});
// in CommonScreen2 do the reset to the this.props.navigation.state.params.initial
this.props.navigation.navigate('CommonScreen1', { initial: 'Profile'});
this.props.navigation.navigate('CommonScreen2', { initial: 'Profile'});
// in CommonScreen2 do the reset to the this.props.navigation.state.params.initial