How to pass params from a stack navigator to a Material top tab navigator? - reactjs

I have a Stack named as PaymentStack which consists of two Stack screens. The second screen consists of a MaterialTopTabs which have two other Screens named as 'Net banking' and 'Cash Payment'.
PaymentStack consists of two screens, one screen is a simple Stack screen navigator with a functional component passed in the component attribute of Stack.Screen but for the other screen I have tried to make a Material Top tabs Navigator
const TopTabStack = createMaterialTopTabNavigator();
//this MaterialTopTab is passed as a component in the below PaymentStack tab navigator
const MaterialTopTab = ({ navigation }) => (
<TopTabStack.Navigator>
<TopTabStack.Screen
name={'Net banking'}
component={ComponentX} //*I want to receive the params in this screen/cmpnt.
/>
<TopTabStack.Screen
name={'Cash payment'}
component={ComponentX}
/>
</TopTabStack.Navigator>
)
//this PaymentStack is passed as a component in Bottom tab navigator
const PaymentStack = ({navigation}) => {
<Stack.Navigator>
<Stack.Screen
name={'PaymentOption'}
component={ComponentY} //Suppose I am on this Screen
/>
<Stack.Screen
name={'MaterialTopTab'}
component={MaterialTopTab} //this component is defined above
/>
</Stack.Navigator>
}
export default function AppStack = () => {
return (
<NavigationContainer>
<PaymentStack /> //defined above
</NavigationContainer>
);
}
Now suppose I am in the screen named PaymentOption whose component is ComponentY and I want to navigate to the Screen named as Material top tab whose component is a Material top tab navigator in which two screens are present named as Net banking and Cash payment and receive the params in the Net banking. How can we do this ?
I tried
navigation.navigate('MaterialTopTab', {body : data} )
But when I am accessing the body in ComponentX , it shows undefined.
function ComponentX({route}){
console.log(route.params.body)
.......
.......
}

Ok, hopefully this is what you're looking for. Please ignore the typescript typings if you aren't using Typescript. As you can see, our RootStack component is a stack navigator that contains 1 screen and a TabNavigator. I don't have experience using top tabs but I'm pretty sure it works the same way as bottom tabs. We can navigate to our TabNavigator from the first screen in RootStack (Screen1), passing a prop called helloKitty to the TabNavigator. The TabNavigator then passes the helloKitty prop from route.params to the first screen in the TabNavigator (TabScreen1). I'm not particularly sure if what I did with TabScreen1Props is the proper way to do it, but it works for now. Honestly though, at this point, if this is what your navigation structure looks like, why not consider useContext or a state management library like Zustand?
import React, { useEffect } from 'react'
import { Text, View } from 'react-native'
import { createStackNavigator, StackScreenProps } from '#react-navigation/stack'
import { BottomTabScreenProps } from '#react-navigation/bottom-tabs'
import { Button } from 'react-native'
import { NavigationContainer } from '#react-navigation/native'
type HelloKittyType = string
type RootStackParamList = {
Screen1: undefined
SomeCuteTabs: { helloKitty: HelloKittyType }
}
type RootStackNavigationProps = StackScreenProps<RootStackParamList>
type SomeCuteTabsParamList = {
TabScreen1: undefined
TabScreen2: undefined
}
type SomeCuteTabsNavigationProps = BottomTabScreenProps<SomeCuteTabsParamList>
const RootStack = createStackNavigator<RootStackParamList>()
const SomeCuteTabs = createStackNavigator<SomeCuteTabsParamList>()
const Screen1 = ({ navigation }: RootStackNavigationProps) => (
<View>
<Button title="Go to tabs" onPress={() => navigation.navigate('SomeCuteTabs', { helloKitty: 'I love hello kitties' })} />
</View>
)
interface TabScreen1Props extends SomeCuteTabsNavigationProps {
helloKitty: HelloKittyType | undefined
}
const TabScreen1 = ({ route, navigation, helloKitty }: TabScreen1Props) => {
useEffect(() => {
console.log(helloKitty)
}, [])
return (
<View>
<Text>Tab Screen 1</Text>
</View>
)
}
const TabScreen2 = () => (
<View>
<Text>Tab Screen 1</Text>
</View>
)
const RootStackComponent = () => (
<RootStack.Navigator>
<RootStack.Screen name="Screen1" component={Screen1} />
<RootStack.Screen name="SomeCuteTabs" component={SomeCuteTabsComponent} />
</RootStack.Navigator>
)
const SomeCuteTabsComponent = ({ route, navigation }: RootStackNavigationProps) => (
<SomeCuteTabs.Navigator>
<SomeCuteTabs.Screen name="TabScreen1">{(props) => <TabScreen1 {...props} helloKitty={route.params?.helloKitty} />}</SomeCuteTabs.Screen>
<SomeCuteTabs.Screen name="TabScreen2" component={TabScreen2} />
</SomeCuteTabs.Navigator>
)
export default function App() {
return (
<NavigationContainer>
<RootStackComponent />
</NavigationContainer>
)
}

Related

React Native Nested Stack Navigation issue

TL;DR Please excuse me, for the mess I've made here and If something is unclear
I made the following react native stack navigation
import { NavigationContainer, createNativeStackNavigator, useNavigation } from '#react-navigation/native'
import { Pressable, Text } from 'react-native'
const LoginStack = createNativeStackNavigator()
const hideHeader = { headerShown: false }
const LoginNavigation = () => {
return (
<LoginStack.Navigator initialRouteName="SignInScreen">
<LoginStack.Screen name="SignInScreen" component={SignInScreen} options={hideHeader}/>
<LoginStack.Screen name="OTPScreen" component={OTPScreen} options={hideHeader}/>
</LoginStack.Navigator>
)
}
const LoginScreen = () => (
<View>
<View>...Some Other UI code is here</View>
<LoginNavigation />
</View>
)
const ProfileScreen = () => {
const navigation = useNavigation()
return (
<View>
<Pressable onPress={() => navigation.navigate('LoginScreen')}> 👈🏻
<Text>Go to Login </Text>
</Pressable>
</View>
)
}
const RootStack = createNativeStackNavigator()
const RootNavigation = () => {
return (
<RootStack.Navigator initialRouteName="LoginScreen">
<RootStack.Screen name="LoginScreen" component={LoginScreen} options={hideHeader}/>
<RootStack.Screen name="ProfileScreen" component={ProfileScreen} options={hideHeader}/>
</RootStack.Navigator>
)
}
const App = () => {
return (
...other provders
<NavigationContainer>
<RootNavigation />
</NavigationContainer>
)
}
The navigation flow goes like this
LoginScreen -> (SignInScreen -> OTPScreen) -> ProfileScreen
The problem is that every time I dispatch "LoginScreen" navigation from "ProfileScreen," it goes to "OTPScreen" instead of "SignInScreen." Yes, that does sound strange, but after numerous tests, the result remains the same.
I'm positive the reason is the router cache, but I'm not sure how to solve it. Tried clearing cache by following few answers here but no luck
I've figured out a way to fix this and it's functioning 👇🏻
navigation.reset({index: 0, routes: [name: 'LoginScreen']})
but it is moving forward (but it must go backwards like goback)
Can someone help me with this, or is there a better way to do this?

React navigation - switch between 2 tab navigation

Friends, I tried many times but I could not do it. How can I switch between 2 tab navigation, for example 1. tab navigation become a member and login, 2. tab navigation homepage, settings etc. I was doing it in version 3 but now for some reason I couldn't, thanks to all the friends who helped
here is a quick snippet, i only show one tab navigator's full logic for reference, but the second can follow the same logic.
Using react navigation v5.x.x
It works by nesting navigators in screens
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { createStackNavigator } from '#react-navigation/stack';
const HomeTabsNavigator = createBottomTabNavigator();
const HomeTabRenderer = () => {
const initialRouteName = 'Feed';
const initialRouteParams = {};
return (
<HomeTabsNavigator.Navigator
initialRouteName={initialRouteName}
initialRouteParams={initialRouteParams}
>
<HomeTabsNavigator.Screen name={'Feed'} component={'<add react component>'} />
<HomeTabsNavigator.Screen name={'Dashboard'} component={'<add react component>'} />
<HomeTabsNavigator.Screen name={'Friends'} component={'<add react component>'} />
</HomeTabsNavigator.Navigator>
);
};
const Stack = createStackNavigator();
const StackRenderer = () => {
const initialRouteName = 'FirstTabNav';
const initialRouteParams = {};
return (
<NavigationContainer>
<Stack.Navigator initialRouteName={initialRouteName}
initialRouteParams={initialRouteParams}
headerMode={'none'}
>
<Stack.Screen name={'FirstTabNav'} component={HomeTabRenderer} />
<Stack.Screen name={'SecondTabNav'} component={OtherTabRenderer} />
</Stack.Navigator>
</NavigationContainer>
)
}

React Navigation 5, block back navigation after login

I am using React Navigation 5 in a project, and I'm having trouble trying to block a user from navigating back after a certain point.
The app uses a nested navigation structure similar to this:
ROOT (STACK)
|-- LoginScreens (STACK - options={{ gestureEnabled: false }} )
| |-- Login (SCREEN) -> when successful navigate to "Home"
| +-- Register (SCREEN) -> after registration, navigate to "Login"
|
+-- Home (TABS - options={{ gestureEnabled: false }} )
|-- BlahBlah (SCREEN)
|-- MyProfile (SCREEN)
+-- Dashboard (TABS)
|-- AllTasks (SCREEN)
+-- SomethingElse (SCREEN)
After a successful user login, the user is sent to the Home screen and should not be able to navigate back to the LoginScreens screen.
I have tried to use the componentDidMount lifecycle method on Home, as well as the useFocusEffect hook, with the following:
Placing a callback to React Native's BackHandler, returning true from the handler works (true means back action has been handled, no further back handlers will be called), but it will also block any back navigation within the screens in Home (e.g. I cannot navigate back from Dashboard to MyProfile).
Using navigation.reset({ index: 1, routes: [{ name: "Home" }] }). Without index: 1 the navigation just goes back to ROOT's initialRoute (in this case, LoginScreens). With index: 1, a Maximum update depth exceeded error is thrown.
Instead navigating directly to Home, I have tried using a navigation.reset() (note: no params, clears the entire navigation history), and after that navigate to the Home screen. This doesn't achieve the desired effect since the current route (ROOT's initialRoute, in this case: LoginScreens) is still pushed on the navigation history before navigating to Home.
Combining navigation and reset calls in different ways, I have only managed to get JS angry and throw errors and exceptions at me.
Aaaaand... I have ran out of ideas. Does anyone have any suggestions ?
It seems that React Navigation's docs tried to cover this use case with this guide:
https://reactnavigation.org/docs/en/auth-flow.html
The example there is very tricky, already introduces state management libraries, reducers, React hooks, and whatever else that doesn't really help. However, the summary of that guide is: Conditionally render routes.
Unlinke React Navigation 4 and previous versions, in React Navigation 5 you can conditionally render routes. In doing so you effectively rule out any possibilities of navigation to an inexistent route. Below, there is a very short example of how you can do it with a simple state variable. Keep in mind however that this example only takes into account a navigator with one route rendered at a time. If you have more routes that are rendered other than the ones in this example, you may need to adjust the RootStack.Navigator's props (initialRouteName for example), or explicitly navigate to a specific route.
import React from "react";
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import LoginNav from "./screens/LoginNav";
import HomeScreens from "./screens/HomeScreens";
const RootStack = createStackNavigator();
export default class MyApp extends React.Component {
constructor(props){
super(props);
this.state = { isLoggedIn: false };
}
setIsLoggedIn = (isLoggedIn)=>{ this.setState({ isLoggedIn }); }
render = () => {
// Using an arrow function to allow to pass setIsLoggedIn to LoginNav
// Pass setIsLoggedIn from the props of LoginNav to the screens it contains
// then from the screens call this function with a true/false param
const LoginScreens = (props)=> <LoginNav {...props} setIsLoggedIn={this.setIsLoggedIn} />
return <NavigationContainer style={{ flex: 1 }}>
<RootStack.Navigator>
{(this.state.isLoggedIn === false)
// If not logged in, the user will be shown this route
? <RootStack.Screen name="LoginScreens" component={LoginScreens} />
// When logged in, the user will be shown this route
: <RootStack.Screen name="Home" component={HomeScreens} />
}
</RootStack.Navigator>
</NavigationContainer>;
}
}
In this example, call (this.) props.setIsLoggedIn(true) to render the Home route, or call with a false param to return to the LoginScreens route.
Hopefully this example is easier to understand than the one in the docs.
Well, I have to admit, its was not easy to find the new reset method's syntax for v5, man... ReactNavigation docs really need an in site search functionality.
Anyway, reset method can be used, and worked perfectly for me.
It looks something like:
import { CommonActions } from '#react-navigation/native';
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [
{
name: 'Home',
params: { user: 'jane' },
},
],
})
);
I made a helper function which I am using in multiple places in my app, that looks like:
import { CommonActions } from '#react-navigation/native';
export const resetStackAndNavigate = (navigation, path) => {
navigation.dispatch(CommonActions.reset({ index: 0, routes: [{ name: path }] }));
};
I've done it this way for react-navigation v5:
I've created a CustomDrawerContent-Component to be able to handle each press on an item:
(Note: Ignore header and footer property, it's only an adjustment for my drawer.)
...
import {
DrawerContentScrollView,
DrawerItem,
} from '#react-navigation/drawer';
...
function CustomDrawerContent(props) {
const {
state: {routes, index},
descriptors,
navigation,
header,
footer,
} = props;
return (
<>
{header}
<DrawerContentScrollView {...props}>
{routes.map((route, i) => {
const focused = i === index;
const {title, drawerLabel, drawerIcon} = descriptors[
route.key
].options;
return (
<DrawerItem
key={route.key}
label={
drawerLabel !== undefined
? drawerLabel
: title !== undefined
? title
: route.name
}
icon={drawerIcon}
focused={focused}
onPress={() => {
navigation.dispatch(
CommonActions.reset({index: i, routes: [{name: route.name}]}),
// NOTICE: Removes the routes.<name>.state of the Stack to discard
// navigation-Position if coming back to it via Drawer-Menu.
// If this Stack-State in seeded later on, you can adjust it here needed
);
}}
/>
);
})}
</DrawerContentScrollView>
{footer}
</>
);
}
function MainDrawer(props) {
const {
screen,
screen: {initialRoute},
navigatorProps,
header,
footer,
hideDrawerItems,
} = props;
return (
<Navigator
initialRouteName={initialRoute}
{...navigatorProps}
drawerContent={(drawerProps) => (
<CustomDrawerContent {...drawerProps} header={header} footer={footer} />
)}>
{createMenuEntries(screen, hideDrawerItems)} // that's only an custom implementation of mine to create <Screen>-Entries. Feel free to replace it with your own
</Navigator>
);
}
export default MainDrawer;
The Magic at least is here:
{routes.map((route, i) => {
...
onPress => navigation.dispatch => CommonActions.reset({index: ⇒⇒ i ⇐⇐
While we map over each route, we use the current index and the route-name (of the drawer-item itself) to reset it's route-state, if we tapped it.
This works perfectly fine for my purposes, because even if you are in News ⇒ News Detail"and open the Drawer and click again on News, you are piped to the first Screen of your News-Stack.
I like the accepted solution, but the other way to do this is to use React Context.
const AuthContext = React.createContext();
const setIsLoggedIn = (isLoggedIn) => {
this.setState({ isLoggedIn });
}
and then wrap your entire navigator:
<AuthContext.Provider value={setIsLoggedIn}>
<RootStackNavigator>
// your screens, etc.
</RootStackNavigator>
</AuthContext.Provider>
then in your screen, you can use:
const { setIsLoggedIn } = React.useContext(AuthContext);
and call it when you want.
See this guide: https://reactnavigation.org/docs/auth-flow/
Initially I had posted this solution:
https://stackoverflow.com/a/60307042/12186963
However, eventually, I ended up not using it due to some serious jank issues I had with conditional rendering:
When aNavigator / Screen mounts, a lot of stuff happens, multiple screens might get instantiated (especially if you're using tabbed navigators without lazy mount), nested <Navigator />s might mount, react-navigation has to re-evaluate it's state, and much, much more.
The app does not have a choice but hold on until the entire route tree mounts before rendering it, which can cause blank flashes between mounts. On lower-end devices, the blank screen can persist for longer times than a user would tolerate.
The better alternative solution that I have found involves the imperative call to NavigationContainer.resetRoot method. By attaching a ref to the NavigationContainer, calling resetRoot will always act on the root navigation state.
resetRoot also allows to specify a new navigation state, which can be useful to change the currently active route.
The implementation is as follows:
libs/root-navigation.js
import React from "react";
// This is the ref to attach to the NavigationContainer instance
export const ref = React.createRef();
/**
* Resets the root navigation state, and changes the active route to the one specified
* #param {string} name The name of the route to navigate to after the reset
* #param {object|undefined} params Additional navigation params to pass to the route
*/
export function navigate(name, params) {
try {
ref.current.resetRoot({ index: 0, routes: [{ name, params }] });
} catch (e) {
console.error("Failed to reset the root navigation state. Make sure you have correctly attached the ref to the <NavigationContainer /> component.\nOriginal error:", e);
}
}
App.js(or wherever you render your<NavigationContainer /> component:
import { NavigationContainer } from "#react-navigation/native";
import * as RootNavigation from "./libs/root-navigation";
import { createStackNavigator } from "#react-navigation/stack";
import LoginScreen from "./screens/Login";
import RegisterScreen from "./screens/Register";
import DashboardScreen from "./screens/Dashboard";
import AccountScreen from "./screens/Account";
const RootStack = createStackNavigator();
const AuthenticationStack = createStackNavigator();
const HomeStack = createStackNavigator();
function AuthenticationScreens() {
return <AuthenticationStack.Navigator initialRouteName="Login">
<AuthenticationStack.Screen name="Login" component={LoginScreen} />
<AuthenticationStack.Screen name="Register" component={RegisterScreen} />
</AuthenticationStack.Navigator>;
}
function HomeScreens() {
return <HomeStack.Navigator initialRouteName="Dashboard">
<HomeStack.Screen name="Dashboard" component={DashboardScreen} />
<HomeStack.Screen name="Account" component={AccountScreen} />
</HomeStack.Navigator>;
}
export default function MyApp() {
// ... your awesome code :)
return <NavigationContainer ref={RootNavigation.ref}>
<RootStack.Navigator initialRouteName="Authentication">
<RootStack.Screen name="Authentication" component={AuthenticationScreens} />
<RootStack.Screen name="Home" component={HomeScreens} />
</RootStack.Navigator>
</NavigationContainer>;
}
Then, in some other place in your app, you can always import the navigate() function from the root-navigation.js file, and use that to reset the root stack:
import { Pressable, Text, View } from "react-native";
import * as RootNavigation from "./libs/root-navigation";
import * as ServerAPI from "./server-api";
function LoginScreen() {
const email = "hello#world.com";
const password = "P#$sw0rD!";
const onLoginPress = () => {
ServerAPI.login(username, password).then(({ success, user })=>{
if (success === true) {
// Here we reset the root navigation state, and navigate to the "Home" screen
RootNavigation.navigate("Home", { user });
} else {
alert("Wrong email or password...");
}
});
}
return <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Pressable onPress={onLoginPress}>
<Text>Login now!</Text>
</Pressable>
</View>;
}
I much more prefer this solution rather than my initial one. It also works with react-navigation#6.x.

React Native - pass props from One screen to another screen (using tab navigator to navigate)

I need to pass data from my HomeScreen to my SecondScreen. There are a ton of examples of how to do this if i'm clicking a button on the HomeScreen to navigate to SecondScreen, but can't find anything showing how to pass to SecondScreen if I'm using a v2 bottom tab navigator to go from HomeScreen to SecondScreen. I've tried screenprops and a couple other methods and spent about 8 hours trying to figure it out but could not get it to work. Any idea how to do this? Please, any hint would be amazing. Here is my code:
MainTabNavigator.js:
const config = Platform.select({
web: { headerMode: 'screen' },
default: {},
});
const HomeStack = createStackNavigator(
{
Home: HomeScreen,
},
config
);
HomeStack.navigationOptions = {
tabBarLabel: 'Home',
tabBarIcon: ({ focused }) => (
<MaterialIcons name="home" size={32} />
),
};
HomeStack.path = '';
const SecondStack= createStackNavigator(
{
Second: SecondScreen,
},
config
);
SecondStack.navigationOptions = {
tabBarLabel: 'Second screen stuff',
tabBarIcon: ({ focused }) => (
<MaterialIcons name="SecondScreenIcon" size={32} />
),
};
SecondStack.path = '';
const tabNavigator = createBottomTabNavigator({
HomeStack,
SecondScreen
});
tabNavigator.path = '';
export default tabNavigator;
HomeScreen.js:
class HomeScreen extends Component {
constructor(props){
super(props);
}
componentDidMount(){
this.setState({DataFromHomeScreen: 'my data that Im trying to send to SecondScreen'})
}
//....
SecondScreen.js:
class SecondScreen extends Component {
constructor(props){
super(props);
}
render()
return(
<View>{this.props.DataFromHomeScreen}</View>
)
//....
****Please find THINGS I'VE TRIED below:****
HomeScreen.js: when i do this it receives it at first but then passes null
render(){
return(
<View>
//all of my home screen jsx
<SecondScreen screenProps={{DataFromHomeScreen : 'data im trying to pass'}}/>
</View>
)
}
MaintTabNavigator.js: when i do this it receives it at first but then passes null
HomeStack.navigationOptions = {
tabBarLabel: 'Home',
tabBarIcon: ({ focused }) => (
<MaterialIcons name="home" size={32} />
),
};
<HomeStack screenProps={{DataFromHomeScreen:'data im trying to pass'}}/>
HomeStack.path = '';
I've tried like 5 other ways too that I can't even remember at this point. I don't want to have to call my database again in the second screen to get user info. Nobody I know knows react or react native. The React Native documentation at https://reactnavigation.org/docs/en/stack-navigator.html is minimal at best, only showing the below:
const SomeStack = createStackNavigator({
// config
});
<SomeStack
screenProps={/* this prop will get passed to the screen components as this.props.screenProps */}
/>
even if you go to the examples in the documentation and search for the word 'screenprop' you will not see any mention of the screen prop feature in either of the examples. All questions that I've seen only address how to pass props on button click which is easy. Is what I'm trying to do possible? I'm sure I'm not the only person using tab navigator who's retrieved data in the homescreen and need to pass it to other screens . Any advice helps. thanks.
ps.
Here is my Sign in class that is calling the Home screen:
class SignInScreen extends React.Component {
static navigationOptions = {
title: 'Please sign in',
};
render() {
return (
<View
style={styles.container}
contentContainerStyle={styles.contentContainer}>
<View>
<SocialIcon
title='Continue With Facebook'
button
type='facebook'
iconSize="36"
onPress={this._signInAsync}
/>
</View>
);
}
_signInAsync = async () => {
let redirectUrl = AuthSession.getRedirectUrl();
let result = await AuthSession.startAsync({
authUrl:
`https://www.facebook.com/v2.8/dialog/oauth?response_type=token` +
`&client_id=${FB_APP_ID}` +
`&redirect_uri=${encodeURIComponent(redirectUrl)}`,
});
var token = result.params.access_token
await AsyncStorage.setItem('userToken', token);
await fetch(`https://graph.facebook.com/me?fields=email,name&access_token=${token}`).then((response) => response.json()).then((json) => {
this.props.navigation.navigate('Home',
{
UserName : json.name,
FBID : json.id,
email : json.email
});
}) .catch(() => {
console.log('ERROR GETTING DATA FROM FACEBOOK')
});
};
}
export default SignInScreen;
I think you're calling your database in componentDidMount in your HomeScreen component, (I'm right?) and because another component in the same hierarchy needs the same data, you should considerer wrapping this into a new component and do the call to your data in that father component, then you pass the data to all the children that needs it. This is the react way to do things. The state of HomeScreen should not have the data, your data should live in a parent component in a higher hierarchy and pass the data to children as props.
In this way when you create your tabs you can pass the props as the react native docs suggest:
import { createBottomTabNavigator, BottomTabBar } from 'react-navigation-tabs';
const TabBarComponent = (props) => (<BottomTabBar {...props} />);
const TabScreens = createBottomTabNavigator(
{
tabBarComponent: props =>
<TabBarComponent
{...props}
style={{ borderTopColor: '#605F60' }}
/>,
},
);
Another solution could be to use a global state management with Redux or something similar.
I hope this helps.
Edit:
class Home extends React.Component{
constructor(props){
super(props);
this.state = {data: null}
}
componentDidMount() {
//get your props from navigation (your facebook credentials)
//your call to database
this.setState({data: yourResponseData});
}
render(){
const TabNavigator = createBottomTabNavigator(
{
HomeScreen: props =>
<HomeScreenStack
{...this.state.data}
/>,
SecondStack: props =>
<SecondStack
{...this.state.data}
/>,
},
);
return(
<TabNavigator />
)
}
}
const App = createAppContainer(Home);
export default App;
Use this.props.navigation.navigate.
In your HomeScreen, once you have the data you want to send, then navigate over to SecondScreen like so:
this.props.navigation.navigate('Second', { data: yourData })
To access this in SecondScreen whenever it is navigated to using navigation props, you can use NavigationEvents along with this.props.navigation.getParam.
/* your imports */
import { NavigationEvents } from 'react-navigation';
export default class SecondScreen extends React.Component {
/* your methods and properties */
render() {
<View>
<NavigationEvents
onDidFocus={() => this.setState({ data: this.props.navigation.getParam('data', {}) })}
/>
{ /* your SecondScreen render code */ }
</View>
}
}
Edit: For example, with your SignInScreen implementation, to access the props, use:
const username = this.props.navigation.getParam('UserName', '')
const fbid = this.props.navigation.getParam('FBID', 0)
const email = this.props.navigation.getParam('email', '')
This is the basic approach that I am using:
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
const TestComponent = (props) => {
return <Text>{`TestComponent: ${props.name}`}</Text>;
};
const Home = () => {
const Tab = createBottomTabNavigator();
return (
<View style={{flex: 1}}>
<Tab.Navigator>
<Tab.Screen name="Screen 1">
{() => <TestComponent name="test 1" />}
</Tab.Screen>
<Tab.Screen name="Screen 2">
{() => <TestComponent name="test 2" />}
</Tab.Screen>
</Tab.Navigator>
</View>
);
};
Notice that to pass the props to a Screen I am using a child function instead of passing a value to component. The child function can then return the component you want in the syntax you are used to, that has the props available. In this case, the props are the simple name, but you can expand this to handle your state.
I ended up using Redux, it only took me like 100 read throughs and attempts to learn it, but once I learned it it's amazing and simple.

this.props.navigation.navigate() doesn't work (using WithNavigation)

I have implemented a pop-up component in a header with More Options button. This button is available only on the UserProfileScreen. When you press on it you see two buttons: 'Settings' and 'Log out'. The issue is related to Settings button.
Expected behavior:
Press on Settings button -> this.props.navigation.navigate('SettingsNavigator') is called -> SettingsScreen is shown
Actual behavior:
Press on Settings button -> console.warn('go to settings') is called and nothing more (you are not redirected to SettingsScreen).
SettingsNavigator.js
import React from 'react';
import { createStackNavigator } from 'react-navigation';
import SettingsScreen from './SettingsScreen';
import EditProfileScreen from './EditProfileScreen';
import RemoveProfileScreen from './RemoveProfileScreen';
const SettingsNavigator = createStackNavigator({
SettingsScreen: SettingsScreen,
EditProfile: EditProfileScreen,
RemoveProfile: RemoveProfileScreen
}, {
initialRouteName: 'SettingsScreen'
});
export default SettingsNavigator;
optionHeaderButton.js
import { withNavigation } from 'react-navigation';
...
class OptionsHeaderButton extends Component {
...
navigateToSettings = () => {
console.warn('go to settings')
this.props.navigation.navigate('SettingsNavigator');
}
render() {
return(
<Menu onSelect = {value => {
switch(value) {
case 'Settings':
this.navigateToSettings();
break;
case 'Logout':
this.onLogOutPress();
break;
}
}}>
<MenuTrigger>
<Icon name = 'dots-vertical' size = {24} color = {text}/>
</MenuTrigger>
<MenuOptions style = {optionsHeaderButtonStyle.menuWrapper}>
<MenuOption value = {'Settings'}>
<View style = {optionsHeaderButtonStyle.optionWrapper}>
<View style = {optionsHeaderButtonStyle.optionIcon}>
<IconSimpleLine name = 'settings' size = {12} color = {text}/>
</View>
<Text style = {[optionsHeaderButtonStyle.option, optionsHeaderButtonStyle.lastOption]}>
Settings
</Text>
</View>
</MenuOption>
<MenuOption value = {'Logout'}>
<View style = {optionsHeaderButtonStyle.optionWrapper}>
<View style = {optionsHeaderButtonStyle.optionIcon}>
<IconSimpleLine name = 'logout' size = {12} color = {text}/>
</View>
<Text style = {[optionsHeaderButtonStyle.option, optionsHeaderButtonStyle.lastOption]}>
Logout
</Text>
</View>
</MenuOption>
</MenuOptions>
</Menu>
)
}
}
export default withNavigation(OptionsHeaderButton);
OptionsHeaderButton is nested to a Header Component that is nested in UserProfile Component.
As far as I know using withNavigation() I can call navigation.navigate() from any component. So why doesn't it work?
UPDATE:
After investigation I found that this issue is related to a bug of React Navigation that was found on April of 2017. It means that you can use navigation.navigate() only inside one Stack. But I can't find any info about fix of this bug.
you need to pass screen name instead of navigator.
this.props.navigation.navigate('SettingsSecreen');
try this:
import { NavigationActions, withNavigation } from 'react-navigation';
navigateToSettings = () => {
const navigateAction = NavigationActions.navigate({ routeName: 'SettingsScreen' });
this.props.navigation.dispatch(navigateAction);
}
I had this issue but I have wired up my App.js slightly differently using stack navigation and drawer navigation (And switch navigation for auth) - mostly followed the react navigation docs:
App.js
const AuthStack = createStackNavigator({ Login: Login }, { headerMode: 'none' });
const ActivityStack = createStackNavigator({ Activity: ActivityScreen}, {headerMode: 'none'});
// setup my routing & config here
export default createAppContainer(createSwitchNavigator(
{
Auth: AuthStack,
ActivityStack: ActivityStack,
},
{
intitalRouteName: 'Auth',
},
));
In the screen I want to navigate from I wire up the constructor like this (to bind the navigation function):
FirstScreen.js
export default class FirstScreen extends React.Component {
constructor(props) {
super(props);
this.navigateToScreen = this.navigateToScreen.bind(this);
}
state = {
// state items here
}
navigateToScreen(screenName) {
this.props.navigation.navigate(screenName);
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity style={styles.button} onPress=
{this.navigateToScreen("Activity")}>
<Text style={styles.loginText}>
ADD ACTIVITY
</Text>
</TouchableOpacity>
</View>
Note how I am calling the navigation, I am only passing in the route alias I gave earlier in the App.js file:
Activity: ActivityStack,
So I would write this.navigateToScreen("Activity") from the calling point passing in the alias not the screen name or stack name.
This navigates correctly to the screen and utilizes stack navigation I defined earlier in App.js.
Hope this makes sense (parent child hierarchy is achieved by nesting the various navigation features like switch, stack and drawer to achieve your desired results.
in react-navigation Version: 5.x withNavigation is not working. To fix this issue try this.
import { useNavigation } from '#react-navigation/native';
const navigation = useNavigation();
navigation.navigate("DetailsScreen")
no need to warp the component in withNavigation.
Hope this may helps.

Resources