IntialParams on a Tab Navigator Screen is undefined (react navigation v5) - react-native

I'm trying to pass an initialParam prop to a TabNavigator screen. It is meant to be a login piece of local state, just doing a proof of concept. It works fine with the stack navigator screen, but the route.params on the tab screen is undefined. What am I doing wrong?
Also, I'm pretty sure this will have to change as far as state management goes, but I wanted to make sure I could simply change the state in the parent App and have it take effect. It works on the stack screen, meaning I can call setLoggedIn(true) and it will take me to the tab navigator. But I can't go back...
const Track = () => {
return (
<TrackStack.Navigator>
<TrackStack.Screen
name='TrackListScreen'
component={TrackListScreen}
/>
<TrackStack.Screen
name='TrackDetailScreen'
component={TrackDetailScreen}
/>
</TrackStack.Navigator>
);
};
const App = () => {
const [loggedIn, setLoggedIn] = useState(false);
return (
<NavigationContainer>
{loggedIn ? //is logged in
(
<MainTab.Navigator>
<MainTab.Screen
name='TrackCreateScreen'
component={TrackCreateScreen}
initialParams={{ setLoggedIn }}
/>
<MainTab.Screen
name='AccountScreen'
component={AccountScreen}
/>
<MainTab.Screen
name='Track'
component={Track}
/>
</MainTab.Navigator>
) : (
<LoginStack.Navigator>
<LoginStack.Screen
name='SignupScreen'
component={SignupScreen}
initialParams={{ setLoggedIn }}
/>
<LoginStack.Screen
name='SigninScreen'
component={SigninScreen}
/>
</LoginStack.Navigator>
)}
</NavigationContainer>
);
};
export default App;

Related

How to navigate from Home screen to Login screen in a nested navigation ? React Navigation v6

So I'm trying to improve my navigation in my React Native project using React Navigation. I would like to know how to navigate through a nested navigator from Home to Login screen.
navigation.ts
export type RootStackParamList = {
AuthorizedTabStack: BottomTabScreenProps<AuthorizedTabNavigationList>;
AuthorizedModalStack: NavigatorScreenParams<AuthorizedModalList>;
UnauthorizedStack: NavigatorScreenParams<UnauthorizedStackList>;
};
export type AuthorizedTabNavigationList = {
Home: undefined;
Planner: undefined;
};
export type AuthorizedModalList = {
InputModal: undefined;
};
export type UnauthorizedStackList = {
Login: undefined;
};
In my MainNavigator.tsx, I've implemented this...
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
{auth.currentUser ? (
<>
<Stack.Screen name="AuthorizedTabStack" component={TabNavigation} />
<Stack.Screen
name="AuthorizedModalStack"
component={ModalNavigation}
/>
</>
) : (
<Stack.Screen
name="UnauthorizedStack"
component={UnauthorizedStack}
/>
)}
</Stack.Navigator>
</NavigationContainer>
The UnauthorizedStackList is basically a StackNavigator
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Login" component={LoginScreen} />
</Stack.Navigator>
I tried using this and there is an error: The action 'NAVIGATE' with payload {"name":"UnauthorizedStack","params":{"screen":"Login"}} was not handled by any navigator.
Do you have a screen named 'UnauthorizedStack'?
const navigation = useNavigation<NavigationProps>();
const handlePressLogOut = () => {
logOut()
navigation.navigate('UnauthorizedStack', { screen: 'Login' });
};
Please let me know if there are better practices on nested navigator or anything else in the code as well. I would like to learn more!
Your navigation.navigate(...) is called before auth state updates, because changing state is not happening right away. You cannot navigate to a screen, that is not rendered.
A few notes about your code:
you don't have to call navigation.navigate(...) after logging out, because you are conditionally rendering screens with auth.currentUser, so UnauthorizedStack will be rendered right after logOut(),
if Login screen is the only screen in your stack, probably there is no need for using stack,

How to disable drawer inside Stack Navigator nested inside Drawer Navigator?

I have a nested stack navigator inside a drawer navigator and I don't want the user to be able to swipe from the left and open the drawer when they aren't on the first page of the stack navigator. I found some answers but they seem to use some old syntax that isn't used in the official documentation.
// stack navigator
const Items = () => {
const IStack = createStackNavigator()
return (
<IStack.Navigator initialRouteName="Items" screenOptions={{ headerShown: false }} >
<IStack.Screen name="Items" component={ItemSelect} options={{ ...TransitionPresets.SlideFromRightIOS }} />
<IStack.Screen name="Item" component={ItemScreen} options={{ ...TransitionPresets.SlideFromRightIOS }} />
</IStack.Navigator>
)
}
// drawer
const App = () => {
return (
<NavigationContainer theme={MyTheme}>
<StatusBar barStyle="light-content" backgroundColor="#232931" />
<Drawer.Navigator initialRouteName="Items">
<Drawer.Screen name="Items" component={Items} />
</Drawer.Navigator>
</NavigationContainer>
)
}
I want to disable the drawer on the second screen of the stack navigator i.e ItemScreen.
Here's what I ended up doing -
<Drawer.Screen
name="Items"
component={Items}
options={({ route }) => {
const routeName = getFocusedRouteNameFromRoute(route) ?? 'Items'
if (routeName == "Item")
return ({swipeEnabled: false})
}}
/>
More about getFocusedRouteNameFromRoute.
It's weird to see no answers anywhere for such a simple scenario.

In react navigation how can we replce drawer screen by stack screen where stack is in drawer? Not same screens (react native)

After successfull loging im moving to this drawer.
const SalesRepDrawerNavigation = () => {
return (
<SalesRepDrawerNavigator.Navigator initialRouteName="SalesRepDashboardStack">
<SalesRepDrawerNavigator.Screen
name="SalesRepDashboardStack"
component={SalesRepDashboardStackNavigation}
options={{
headerTitle: 'Sales Rep Dashboard'
}}
/>
<SalesRepDrawerNavigator.Screen
name="Customers"
component={CustomerListScreen}
/>
<SalesRepDrawerNavigator.Screen
name="SalesOrderView"
component={SalesOrderStackNavigation}
options={{
}}
/>
</SalesRepDrawerNavigator.Navigator>
);
};
const SalesRepDashboardStackNavigation = () => {
return (
<SalesRepDashboardStackNavigator.Navigator key="test" headerMode="none" initialRouteName="SalesRepDashboard">
<SalesRepDashboardStackNavigator.Screen
name="SalesRepDashboard"
component={SalesRepDashboardScreen}
/>
<SalesRepDashboardStackNavigator.Screen
name="CreateSalesOrder"
component={CreateSalesOrderScreen}
/>
</SalesRepDashboardStackNavigator.Navigator>
);
};
then when i go to customer list screen and select a customer I want to move to create sales order screen and at the same time want to remove customer list route. I'm using react native with react navigation v5.
How can I do this?
Use it like:
In DrawerData component you will have to create a drawer item list (a custom component) and for navigation between the screens normally use the navigation.navigate('YOUR_SCREEN_NAME');
Add all your screens in the Stack.Navigator only. Drawer will have only the Home screen.
/*Drawer data is custom drawer item list*/
export function DrawerNav() {
return (
<Drawer.Navigator drawerContent={props => <DrawerData {...props} />}>
<Drawer.Screen name="Home" component={HomeScreen} />
</Drawer.Navigator>
);
}
const RootStackScreen = () => {
return (
<Stack.Navigator>
<>
<Stack.Screen
name="HomeScreen"
component={DrawerNav}
/>
<Stack.Screen
name="FirstScreen"
component={FirstScreen}
/>
<Stack.Screen
name="SecondScreen"
component={SecondScreen}
/>
</Stack.Navigator>
);
}

react native splash page warning

I have made an splash page looks like this:
export default class Splash extends Component {
performTimeConsumingTask = async () => {
return new Promise((resolve) =>
setTimeout(() => {
resolve('result');
}),
);
};
async componentDidMount() {
const data = await this.performTimeConsumingTask();
// const navigation = useNavigation();
if (data !== null) {
this.props.navigation.navigate('BottomMainNavgigation');
}
}
render() {
return (
<View style={styles.viewStyles}>
{/* <Text style={styles.textStyles}>Welcome</Text> */}
<Image
style={styles.tinyLogo}
source={{
uri: URL.logov2,
}}
/>
</View>
);
}
}
THen I use this like this, in my navigation:
const RootStackScreen = (props) => {
const [t] = useTranslation();
return (
<SplashRootStack.Navigator>
<SplashRootStack.Screen
name="Main"
component={Splash}
options={{headerShown: false, headerBackTitle: t('back')}}
/>
<SplashRootStack.Screen
name="BottomMainNavgigation"
component={BottomMainNavgigation}
options={{headerShown: false, headerBackTitle: t('back')}}
/>
</SplashRootStack.Navigator>
);
};
and also:
<PaperProvider theme={MyTheme}>
<NavigationContainer linking={linking} fallback={<Splash />}>
<RootStackScreen />
</NavigationContainer>
</PaperProvider>
and like this in my app.js:
const App = () => {
return (
<Provider store={store}>
<PersistGate loading={<Splash />} persistor={persistor}>
<Suspense fallback={<Splash />}>
<Navigation />
</Suspense>
</PersistGate>
</Provider>
);
};
export default App;
when I run the application it looks like this:
there is a warning :
I get this warning with id= 0, 1 and 2 and I get this warning also:
what have I donr incorrectly ad how can I remove these warnings, also when I load the app in the emulator, I get a white screen for few seconds before I get my own splash page and then to my app.
how can I do this better?
You are using Splash component in redux persist as a loader and in your Splash component there isn't any navigation prop available because it's a parent component and not the part of navigation tree you need to use switch navigator for the same purpose but with the current structure navigation will not work unless you move navigation part inside the navigator tree. Now the solution is,
Use only splash as a static UI component.
Move you navigation or componentDidMount logic inside the stack navigator.
Add simple Activity indicator as fallback.
<PersistGate
loading={<ActivityIndicator style={{top: '45%'}}
animating color={theme.appColor} size='large' />}
persistor={ReduxStore.persistor}>
<Navigator />
</PersistGate>
Your warnings
Undefined is not an object :
The problem is that you are using the Splash as the fallback component so until your deeplink is resolved Splash would be displayed and the Splash here is not part of navigation which will not get the 'navigation' prop so you are getting the warning.
Same for the other higher order components like PersistGate and suspense you can given the splash for everything and all this is outsider navigation.
resolution : Use the activity indicator instead of splash for the fallback
This is due to one of your middleware in redux taking longer, better check your redux middleware.
White screen,
this is whats causing the white screen maybe caused by the same reason as your middleware warning or the component did mount of the splash screen. And you have several providers so better remove one or two and check whats causing that.
You can check this sample to get an idea on using splash screens and authentication.
https://snack.expo.io/#guruparan/rnn-v5

react navigation, get name of nested route

I have a nested drawer navigator below, I am using a custom component in the header:
header: props => {
return <DrawerHeader {...props} />;
},
When I try and access from props the current route in my header, like below, the title is undefined, how can I get the current route?
render() {
const {
navigation,
videos,
search: {term},
scene: {
route: {routeName: title}, // undefined
},
} = this.props;
return (
<View>
<View style={styles.container}>
Navigator:
function DrawerStack() {
return (
<Drawer.Navigator>
<Drawer.Screen
name="VideoEpisodesScreen"
component={VideoEpisodesScreen}
/>
<Drawer.Screen name="TestYourselfScreen" component={TestYourselfScreen} />
<Drawer.Screen name="MyResultsScreen" component={MyResultsScreen} />
<Drawer.Screen name="AboutScreen" component={AboutScreen} />
<Drawer.Screen name="TestsScreen" component={TestsScreen} />
<Drawer.Screen
name="BookmarkedVideosScreen"
component={BookmarkedVideosScreen}
/>
</Drawer.Navigator>
);
}
export default function AppNavigator() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={stackOptions}
/>
<Stack.Screen
name="Drawer"
component={DrawerStack}
options={drawerOptions}
/>
<Stack.Screen
name="MyResultsScreen"
component={MyResultsScreen}
options={options}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
Funnily enough I had the exact same problem and I found your question after it was just an hour old. Essentially the problem is that React Navigation will only give you the current route of the navigator containing the header. If you have a nested navigator, you won't be able to get it.
It looks like this is somewhat intentional, but I've found that by manually querying the state of the navigator, you can drill down to the "deepest" navigator route. Note that while this works for react-navigation 5, it may not work in the future.
You can iteratively query the nested state like this:
const state = navigation.dangerouslyGetState();
let actualRoute = state.routes[state.index];
while (actualRoute.state) {
actualRoute = actualRoute.state.routes[actualRoute.state.index];
}
Note that this is extremely brittle, but it seems to work good enough for my use cases. You should consider creating an issue/feature request on the react-navigation repository for supporting this use case officially.
React Navigation v6 solution:
A nested route object can be discovered through parent Screen listeners. It's given in a callback argument, and can be passed to getFocusedRouteNameFromRoute to get the route name. In the example shown below, I chose to utilize it during the event 'state' (whenever state changes in the nested stack), but you can use it elsewhere if you want.
<Screen
listeners={({ route }) => ({
state: () => {
const subRoute = getFocusedRouteNameFromRoute(route)
// Your logic here //
}
})}
/>
I think in react-navigation 5, you can access route from this.props
const { route } = this.props;