Implement StackRouter in react navigation v5 for nested routes - react-native

I've got a react native app with the below screen hierarchy
RootNavigator
|
|---AppNavigatorStack
| |--Screen1
| |--Screen2
|
|---LoginNavigatorStack
|--loginScreen
|--forgotPasswordScreen
I've been trying to create a StackRouter for this workflow
const options = {
initialRouteName: 'LoginNavigatorStack',
}
const routerConfigOptions = {
routeGetIdList: {},
routeNames: ["AppNavigatorStack", "LoginNavigatorStack"],
type: 'stack',
routeParamList: {}
}
let router = StackRouter(options)
How can I configure routerConfigOptions to use function like getStateForAction, getInitialState in StackRouter
Sample code to make a new state
const action = CommonActions.reset({
index: 0,
routes: [{name: "Screen2"}],
})
let newState = router.getStateForAction(state, action, routerConfigOptions)
state - current state object of the application which we could get from onStateChange of NavigationContainer
The result of newState is null because I haven't added Screen1, Screen2, loginScreen, forgotPasswordScreen in routerConfigOptions
I'm actually expecting a new state which should have the details to navigate to loginScreen.
How can I add all nested screens and create StackRouter for this?

Your syntax looks more like of react-navigation < 4. I am going to post here, how I do it using react-navigation v5.
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
const LoginStack = createStackNavigator();
const AppStack = createStackNavigator();
const RootStack = createStackNavigator();
const LoginStackNavigator = () => {
return (
<LoginStack.Navigator>
<LoginStack.Screen name="LoginScreen" component={LoginScreen}/>
<LoginStack.Screen name="ForgotPasswordScreen" component={ForgotPasswordScreen}/>
</LoginStack.Navigator>
)
}
const AppStackNavigator = () => {
return (
<AppStack.Navigator>
<AppStack.Screen name="Screen1" component={Screen1}/>
<AppStack.Screen name="Screen2" component={Screen2}/>
</AppStack.Navigator>
)
}
const RootStackNavigator = () => {
return (
<RootStack.Navigator>
<RootStack.Screen name="LoginStackNavigator" component={LoginStackNavigator}/>
<RootStack.Screen name="AppStackNavigator" component={AppStackNavigator} />
</RootStack.Navigator>
)
}
const App: () => React$Node = () => {
return (
<NavigationContainer>
<RootStackNavigator/>
</NavigationContainer>
);
};

Related

navigation.navigate('Home') showing some error in typescript

When I use useNavigation or from props { navigation } for navigate between screen using navigation.navigate('Home') the typescript return error Argument of type '"Main"' is not assignable to parameter of type '{ key: string; params?: undefined; merge?: boolean | undefined; } | { name: never; key?: string | undefined; params: never; merge?: boolean | undefined; }' what does it mean?
below is my code:
import React from 'react';
import { View } from 'react-native';
import { Button } from 'react-native-paper';
import { useNavigation } from '#react-navigation/native';
const Home: React.FC = () => {
const navigation = useNavigation();
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Button onPress={() => navigation.navigate('Main')}>Navigate</Button>
</View>
);
};
export default React.memo(Home);
previously i used react navigation v5 and working find with that method
thanks for your help
Maybe you want to do something like this:
export type RootStackParamList = {
Main: undefined;
Home: undefined;
};
const Stack = createStackNavigator<RootStackParamList>();
export const RootNavigator = () => {
return (
<Stack.Navigator initialRouteName="Main">
<Stack.Screen
name="Main"
component={Main}
/>
<Stack.Screen
name="Home"
component={Home}
/>
</Stack.Navigator>
);
};
then in your code to do something like this:
type homeScreenProp = StackNavigationProp<RootStackParamList, 'Home'>;
const Home: React.FC = () => {
const navigation = useNavigation<homeScreenProp>();
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Button onPress={() => navigation.navigate('Main')}>Navigate</Button>
</View>
);
};
This is because you must specify this type, as this will ensure type-safety.
Solution:
1 - Type checking the navigator
// root.routes.tsx
import { createStackNavigator } from '#react-navigation/stack';
export type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Feed: { sort: 'latest' | 'top' } | undefined;
};
const RootStack = createStackNavigator<RootStackParamList>();
2 - Specify a global type for your root navigator
// navigation.d.ts
import { RootStackParamList } from '../routes/root.routes';
declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList { }
}
}
3 - Use it!
import { useNavigation } from '#react-navigation/native';
const navigation = useNavigation();
navigation.navigate('Home');
Refs:
#acro5piano answer from https://github.com/react-navigation/react-navigation/issues/9741
https://reactnavigation.org/docs/typescript/#annotating-usenavigation
I solved this problem, it's simple:
type Nav = {
navigate: (value: string) => void;
}
const { navigate } = useNavigation<Nav>()
function foo() {
navigate("Home")
}
While the other answers cover a good amount, I think I may add to this by describing usage in the useNavigation hook as well as passing the {navigation} through props to a screen.
First, setup the Stack Navigator:
/**
* Types for Stack Navigator.
*/
export type StackParamList = {
Main: undefined;
Home: undefined;
};
const Stack = createStackNavigator<StackParamList>();
When using the useNavigation hook:
import { StackNavigationProp } from "#react-navigation/stack";
/**
* Types for the Stack Navigator.
*/
export type StackNavigation = StackNavigationProp<StackParamList>;
const navigation = useNavigation<StackNavigation>();
When passing the navigation down as a prop in a screen:
/**
* Types for passing the navigation props to screens in the Bottom Tab Navigator.
*/
export type StackNavigationProps = {
navigation: StackNavigation;
};
const SomeScreenInTheStack = ({ navigation }: StackNavigationProps) => {
...
}
Hope this is useful to someone!
For more details, you can refer this article.
I think the best way is this:
create a file #types/index.ts
inside this file paste de code:
routeNames: never[];
};
export type navigationProps = {
navigate: (screen?: string) => void;
goBack: () => void;
reset: (index: number, routeNames: Routes[]) => void;
};
Now you just need to use the navigationProps as the generic of useNavigation example:
import { useNavigation } from '#react-navigation/native';
const navigation = useNavigation<navigationProps>();
You can just put a Type on the screen name. Just like:
const handleContinue = () => navigation.navigate('Login' as never);

Passing navigation to BottomNavigation

In my React Native app, I use react-navigation version 5.
How do I pass the navigation object to the scenes in BottomNavigation?
Here's the component where I create the BottomNavigation:
import React from 'react';
import { BottomNavigation } from 'react-native-paper';
// Components
import CodeScanner from '../../../screens/vendors/CodeScannerScreen';
import Home from '../../../screens/home/HomeScreen';
import Vendors from '../vendors/VendorsStack';
// Routes
const homeRoute = () => <Home />;
const vendorsRoute = () => <Vendors />;
const scanRoute = () => <CodeScanner />;
const HomeTabs = (props) => {
const [index, setIndex] = React.useState(0);
const [routes] = React.useState([
{ key: 'home', title: 'Home', icon: 'newspaper-variant-multiple' },
{ key: 'vendors', title: 'Vendors', icon: 'storefront' },
{ key: 'codescanner', title: 'Scan', icon: 'qrcode-scan' }
]);
const renderScene = BottomNavigation.SceneMap({
home: homeRoute,
vendors: vendorsRoute,
codescanner: scanRoute
});
return (
<BottomNavigation
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderScene={renderScene}
labeled={false} />
);
}
export default HomeTabs;
In this code, I do have the navigation in the props but haven't been able to figure out a way to pass navigation to the screens. Please also note that the vendor one is actually a stack navigator. In particular, that's where I need access to navigation so that I can open up other screens.
You should pass it from tabBar prop like below (I use TypeScript) and BottomTabBarProps:
export declare type BottomTabBarProps = BottomTabBarOptions & {
state: TabNavigationState
descriptors: BottomTabDescriptorMap
navigation: NavigationHelpers<ParamListBase, BottomTabNavigationEventMap>
}
So you pass BottomTabBarProps to your custom tab component
<BottomTab.Navigator
screenOptions={TabBarVisibleOnRootScreenOptions}
initialRouteName={'Explore'}
tabBar={(props: BottomTabBarProps) => <HomeTabs {...props} />}
>
<BottomTab.Screen name="Explore" component={ExploreScreen} />
...
</BottomTab.Navigator>
So inner HomeTabs you got props.navigation
I think you don't need to pass navigation to BottomNavigation. Because in react-navigation v5 navigation can access anywhere with hook
Read this document https://reactnavigation.org/docs/connecting-navigation-prop/
You may pass data to the vendors screen through the route like so:
const HomeTabs = ( props ) => {
...
const [routes] = React.useState([
...
{ key: 'vendors', title: 'Vendors', icon: 'storefront', props: props },
...
]);
...
}
On your vendor screen you can then retrieve and use the data like this:
const VendorsRoute = ({ route }) => {
const props = route.props;
<Vendors />
}

Pointing to StackNavigator from TabNavigator throws Maximum update depth exceeded error

In my React Native app, I'm using react-navigation version 5 along with react-native-paper.
I'm trying to have one of the items in my bottom tab to point to a stack navigator. When I do that, I get the following error:
Maximum update depth exceeded. This can happen when a component calls
setState inside useEffect, but useEffect either doesn't have a
dependency array, or one of the dependencies changes on every render.
If I point the tab item to a regular screen, everything works fine. As per the react-navigation documentation, I use the BottomNavigation component from react-paper as opposed to using their tab solution -- https://reactnavigation.org/docs/material-bottom-tab-navigator/#using-with-react-native-paper-optional
Here's my tabs component:
import React from 'react';
import { BottomNavigation } from 'react-native-paper';
// Components
import CodeScanner from '../../../screens/vendors/CodeScannerScreen';
import Home from '../../../screens/home/HomeScreen';
import Vendors from '../vendors/VendorsStack';
// Stylesheet
import { styles } from './style-home-tabs';
const HomeTabs = (props) => {
const [index, setIndex] = React.useState(0);
// Routes
const homeRoute = () => <Home />;
const vendorsRoute = () => <Vendors />;
const scanRoute = () => <CodeScanner />;
const [routes] = React.useState([
{ key: 'home', title: 'Home', icon: 'newspaper-variant-multiple' },
{ key: 'vendors', title: 'Vendors', icon: 'storefront' },
{ key: 'codescanner', title: 'Scan', icon: 'qrcode-scan' }
]);
const renderScene = BottomNavigation.SceneMap({
home: homeRoute,
vendors: vendorsRoute,
codescanner: scanRoute
});
return (
<BottomNavigation
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderScene={renderScene}
labeled={false} />
);
}
export default HomeTabs;
And here's the stack navigator that I'm pointing to which causes the error:
import React from 'react';
import { createStackNavigator } from '#react-navigation/stack';
// Components
import VendorsScreen from '../../../screens/vendors/VendorsScreen';
// Navigator
const VendorStack = new createStackNavigator();
const VendorsStack = (props) => {
return(
<VendorStack.Navigator screenOptions={{ headerShown: false }}>
<VendorStack.Screen name="Vendors" component={VendorsScreen} />
</VendorStack.Navigator>
);
}
export default VendorsStack;
As I said, if I were to point to a regular screen, everything works fine. Any idea what's going on here and how to fix it?
Moving the route declarations outside the component fixed the issue. Every time the component got mounted, it was calling these functions and that was causing the issue.
import React from 'react';
import { BottomNavigation } from 'react-native-paper';
// Components
import CodeScanner from '../../../screens/vendors/CodeScannerScreen';
import Home from '../../../screens/home/HomeScreen';
import Vendors from '../vendors/VendorsStack';
// Stylesheet
import { styles } from './style-home-tabs';
// Routes
const homeRoute = () => <Home />;
const vendorsRoute = () => <Vendors />;
const scanRoute = () => <CodeScanner />;
const HomeTabs = (props) => {
const [index, setIndex] = React.useState(0);
const [routes] = React.useState([
{ key: 'home', title: 'Home', icon: 'newspaper-variant-multiple' },
{ key: 'vendors', title: 'Vendors', icon: 'storefront' },
{ key: 'codescanner', title: 'Scan', icon: 'qrcode-scan' }
]);
const renderScene = BottomNavigation.SceneMap({
home: homeRoute,
vendors: vendorsRoute,
codescanner: scanRoute
});
return (
<BottomNavigation
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderScene={renderScene}
labeled={false} />
);
}
export default HomeTabs;

Disable back button in a specific Screen (React Navigation Stack)

Hello!
I need to disable the back button on the navigation bar. help me plsss.
Routes
Home: I don't want to leave the application
Success: I don't want to go back to Checkout.
Example: click here
import React from 'react';
import { createStackNavigator } from '#react-navigation/stack';
import { OrderProvider } from '../contexts/order';
import Home from '../pages/Home';
import Checkout from '../pages/Checkout';
import Success from '../pages/Checkout/success';
const AppStack = createStackNavigator();
const AppRoutes = () => (
<OrderProvider>
<AppStack.Navigator screenOptions={{ headerShown: false }}>
<AppStack.Screen name="Home" component={Home} /> <-- here
<AppStack.Screen name="Checkout" component={Checkout} />
<AppStack.Screen name="Success" component={Success} /> <--- here
</AppStack.Navigator>
</OrderProvider>
);
export default AppRoutes;
import React from 'react';
import { View} from 'react-native';
const Success = () => {
return (
<View />
);
};
export default Success;
You can do the following:
const Home = () => {
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
return true;
};
BackHandler.addEventListener('hardwareBackPress', onBackPress);
return () =>
BackHandler.removeEventListener('hardwareBackPress', onBackPress);
}, []),
);
// ...
};
const Success = ({navigation}) => {
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
navigation.navigate('Home');
return true;
};
BackHandler.addEventListener('hardwareBackPress', onBackPress);
return () =>
BackHandler.removeEventListener('hardwareBackPress', onBackPress);
}, []),
);
// ...
};
I've set it up so that clicking back on the Home screen does nothing by returning true when the hardwareBackPress event is called.
For the Success screen I navigate back to Home.
I assumed this is the behavior you're looking for from reading your question.
It is important that you don't forget to import useFocusEffect from
react-navigation everywhere you use it:
import { useFocusEffect } from '#react-navigation/native';
When to return true or false in the hardwareBackPress event handler function is explained in the react navigation documentation:
Returning true from onBackPress denotes that we have handled the
event, and react-navigation's listener will not get called, thus not
popping the screen. Returning false will cause the event to bubble up
and react-navigation's listener will pop the screen.
If you want to read more read the documentation here: https://reactnavigation.org/docs/custom-android-back-button-handling/.

Should I call multiple addNavigationHelpers in nested react navigation?

I'm following https://github.com/parkerdan/SampleNavigation to integrate redux with react-navigation.
I have a question: should I call addNavigationHelpers multiple times for nested navigators?
In the sample:
const routeConfiguration = {
TabOneNavigation: { screen: TabOneNavigation },
TabTwoNavigation: { screen: TabTwoNavigation },
TabThreeNavigation: { screen: TabThreeNavigation },
}
const tabBarConfiguration = {
tabBarOptions:{
activeTintColor: 'white',
inactiveTintColor: 'blue',
activeBackgroundColor: 'blue',
inactiveBackgroundColor: 'white',
}
}
export const TabBar = TabNavigator(routeConfiguration,tabBarConfiguration);
<TabBar
navigation={
addNavigationHelpers({
dispatch: dispatch,
state: navigationState,
})
}
/>
<NavigatorTabOne
navigation={
addNavigationHelpers({
dispatch: dispatch,
state: navigationState
})
}
/>
<NavigatorTabTwo
navigation={
addNavigationHelpers({
dispatch: dispatch,
state: navigationState
})
}
/>
<NavigatorTabThree
navigation={addNavigationHelpers({
dispatch: dispatch,
state: navigationState
})}
/>
addNavigationHelpers is called 4 times, one for the TabNavigator and 3 others for the tabs.
Is this the recommended way by the document?
Navigation state is automatically passed down from one navigator to another when you nest them.
What actually are TabBar, NavigatorTabOne, NavigatorTabTwo... ?
I suppose you have some code like this:
export const TabBar = TabNavigator({
NavigatorTabOne: { screen: NavigatorTabOne},
NavigatorTabTwo: { screen: NavigatorTabTwo},
NavigatorTabThree: { screen: NavigatorTabThree}
}, {
initialRouteName: 'NavigatorTabOne',
})
To create your root container you need this:
import { createReduxBoundAddListener, createReactNavigationReduxMiddleware } from 'react-navigation-redux-helpers';
export const navigationMiddleware = createReactNavigationReduxMiddleware(
"root",
state => state.nav,
);
const addListener = createReduxBoundAddListener("root");
class ReduxNavigation extends React.Component {
render() {
const { dispatch, nav } = this.props
const navigation = ReactNavigation.addNavigationHelpers({
dispatch,
state: nav,
addListener,
});
return <TabBar navigation={navigation} />
}
}
This could be your reducer
const firstAction = TabBar.router.getActionForPathAndParams('NavigatorTabOne')
const initialState = TabBar.router.getStateForAction(firstAction)
export const reducer = (state = initialState, action) => {
const nextState = TabBar.router.getStateForAction(action, state);
return nextState || state;
};
Anyway, don't use that repository
The official react navigation doc does exist
If you want to use something for scaffolding your react native + redux + react navigation, you can have a look a this project, that will create app base in a command