How to Access the navigation prop from any component? - javascript

I have tried multiple ways in different stackoverflow post and also the original documentation from https://reactnavigation.org/docs/connecting-navigation-prop/
but still failed to solve my issue.
I have a Login Screen, after i navigate to a Home Screen,
From Home screen i wanted to navigate to another screen but the navigation props is undefined.
Tried following the documentation but still failed to do so.
Please help thank you so much.
I have imported my App.js, HomePage.js (The one i wanted to navigate to another Screen but failed to)
Currently the code i have is based on the documentation but is having no effect when button is clicked.
import * as RootNavigation from '../config/RootNavigation';
const HomePage = () => {
const onLogOut = () => {
alert("Logged out")
firebase.auth().signOut();
}
const inventoryOnPress = () => {
RootNavigation.navigate('InventoryScreen')
}
return(
<View>
<CardComponent
style={styles.cardBackground}
title="Inventory"
onPress={inventoryOnPress}/>
</View>
);
}
export default HomePage;
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { navigationRef } from './src/config/RootNavigation';
const Stack = createStackNavigator();
function NavStack() {
const [initialPage,setInitialPage] = useState('LoginScreen');
return(
<Stack.Navigator
initialRouteName={initialPage}
screenOptions={{
headerTitleAlign:'center',
headerStyle: {
backgroundColor: '#621FF7'
}
}}
>
<Stack.Screen
name="LoginScreen"
component={LoginPage}
/>
<Stack.Screen
name="HomeScreen"
component={HomePage}
headerLeft={null}
/>
<Stack.Screen
name="SignUpScreen"
component={SignUpPage}
/>
<Stack.Screen
name="InventoryScreen"
component={InventoryPage}
/>
</Stack.Navigator>
)
}
function App() {
return (
<NavigationContainer ref={navigationRef}>
<NavStack />
</NavigationContainer>
);
}
}
// RootNavigation.js
import * as React from 'react';
export const navigationRef = React.createRef();
export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}
// add other navigation functions that you need and export them

Please don't post firebase or any other API key on SO.
There are a few issues how you are handling navigation (authentication) here.
Please read the docs Auth Flow for detailed answer. One issue is when user is logged in you are returning just the HomeScreen and there is no NavigationContainer returned, this is one of the reason you don't have access to navigation prop in HomePage.
So basically in your App() fn you can return something like this
<NavigationContainer>
<Stack.Navigator>
isSignedIn ? (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</>
) : (
<>
<Stack.Screen name="SignIn" component={SignInScreen} />
<Stack.Screen name="SignUpScreen" component={SignUpPage} />
</>
)
</Stack.Navigator>
</NavigationContainer>
Now to get navigation prop in your HomeScreen you can use useNavigation Hook if you prefer hooks.
import { useNavigation } from '#react-navigation/native';
const HomePage = () => {
const navigation = useNavigation();
return (
<Button
title="Navigate To Profile"
onPress={() => navigation.navigate("Profile")}
/>
);
}

navigation prop is available to every individual screen enclosed in NavigationContainer.
You can access it as follows
const inventoryOnPress = () => {
props.navigation.navigate('InventoryScreen')
}
Refer this article for more detailed information for same.
Update
If you are not able to get navigation prop using ref to NavigationContainer then it implies your component is not rendered yet and has applied some actions on it.
So to avoid this use navigationRef.current.getRootState() to see if this returns an valid object and navigate consequently.

Related

Issue with UseNavigation in react native expo in App.js (Followed up from the previous issue)

Another issue that arrives from the previous issue: Issues with header button in React Native Expo
After changing from ../firebase to ./firebase. It says it couldnt find variable of navigation. So I import { useNavigation } from '#react-navigation/core' and also added const navigation = useNavigation(). But I am having the issue of
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
It seems to be able to sign out as I have the console logging of Signed Out Successfully but it cannot navigate back to the login
Am I doing something wrong here? My idea of adding this button on the right side at the border is for the user to be able to sign out faster when the user is logged in the app
Updated App.js
import { StatusBar } from 'expo-status-bar';
import React from 'react'
import { signOut } from 'firebase/auth';
import { auth } from './firebase';
import { useNavigation } from '#react-navigation/core'
import { StyleSheet, Text, View, Button } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import LoginScreen from './screens/LoginScreen';
import HomeScreen from './screens/HomeScreen';
import RegisterScreen from './screens/RegisterScreen';
import ForgetPasswordScreen from './screens/ForgetPasswordScreen';
import SubScreen1 from './screens/SubScreen1';
import SubScreen2 from './screens/SubScreen2';
const Stack = createNativeStackNavigator();
const navigation = useNavigation()
const handleSignOut = async () =>{
try{
await signOut(auth)
console.log("Signed out successfully")
navigation.replace("Login")
}catch (error) {
console.log({error});
}
}
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen options= {{ headerShown : false }} name="Login" component={LoginScreen} />
<Stack.Screen name="Home" component={HomeScreen} options={{headerRight: () => (
<Button
onPress={handleSignOut}
title="Sign Out"
color="#0782F9" /> ),}} />
<Stack.Screen name="Register" component={RegisterScreen} />
<Stack.Screen name="Forget Password" component={ForgetPasswordScreen} />
<Stack.Screen name="SubScreen1" component={SubScreen1} options={{ title: 'Search Result' }}/>
<Stack.Screen name="SubScreen2" component={SubScreen2} options={{ title: 'Random Search Result' }}/>
</Stack.Navigator>
</NavigationContainer>
);
}
You are breaking the rules of hooks in React. Its basically initializing the hook outside a component.
Put the hooks inside the component like here:
export default function App() {
// Use the hook here
const navigation = useNavigation();
// Add the function here also, its generally preferred to
// put functions inside the componenent so it can get access to hooks. Or you can also
// pass the hook as an argument if you prefer.
const handleSignOut = async () => {
try {
await signOut(auth);
console.log("Signed out successfully");
navigation.replace("Login");
} catch (error) {
console.log({ error });
}
}
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen options= {{ headerShown : false }} name="Login" component={LoginScreen} />
<Stack.Screen name="Home" component={HomeScreen} options={{headerRight: () => (
<Button
onPress={handleSignOut}
title="Sign Out"
color="#0782F9" /> ),}} />
<Stack.Screen name="Register" component={RegisterScreen} />
<Stack.Screen name="Forget Password" component={ForgetPasswordScreen} />
<Stack.Screen name="SubScreen1" component={SubScreen1} options={{ title: 'Search Result' }}/>
<Stack.Screen name="SubScreen2" component={SubScreen2} options={{ title: 'Random Search Result' }}/>
</Stack.Navigator>
</NavigationContainer>
);
}
Hope this helps.

How to show or hide status bar on different screens in React Native

I am new to react native and trying to create some app in which I want to show or hide status bar on different screens.In main screen I don't want to show status bar for that I have set its property hidden={true} but on doing this its also not visible on other screens how can I make it visible on other screens.
In Onboard and Home and screen I want to show status bar and in Login and Register screen I don't wanna show status bar.
Below is my code:
App.js
import React from 'react';
import {StatusBar} from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import Onboard from './components/Onboard';
import Login from './components/Login';
import Register from './components/Register';
import Home from './components/Home';
const Stack = createStackNavigator();
const App = () => {
return (
<NavigationContainer>
<StatusBar hidden={true}/>
<Stack.Navigator>
<Stack.Screen
name="Onboard"
component={Onboard}
options={{ headerShown: false }} />
<Stack.Screen
name="Login"
component={Login}
options={{ headerShown: false }} />
<Stack.Screen
name="Register"
component={Register} />
<Stack.Screen
name="Home"
component={Home}
options={{
headerLeft: null,
headerTintColor:'white',
headerStyle: {
backgroundColor: '#e57373',
elevation:0,
shadowOpacity:0
} }}/>
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;
Someone let me know how can I achieve desired results.
If you don't want to put StatusBar... in every screen, you can also use useFocusEffect hook of react-navigation. https://reactnavigation.org/docs/use-focus-effect/
It is cleaner and easy to read.
import { useFocusEffect } from '#react-navigation/native';
useFocusEffect(() => {
// This will run when component is `focused` or mounted.
StatusBar.setHidden(true);
// This will run when component is `blured` or unmounted.
return () => {
StatusBar.setHidden(false);
}
});
componentDidMount() {
StatusBar.setHidden(false);
}
Add this code to every page's class. Write true or false to show and hide.

How to change a class into function while still maintaining Stack Navigation screen use?

Hello this is my first time asking a question on here/asking for help. I feel like I'm overlooking a simple solution. But my goal is to create a use switch navigator so when the application first begins it will open the terms and conditions but once agreed to, it will give a different set of screens. Right now I am having trouble using React.useSet in TermsAndCondition.js but I keep getting Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
Now I understand that my class in TermsAndConditions.js needs to be a function but how do turn the class into a function while still being able to call it in App.js as a screen in Stack.Navigator.
I have also tried to just somehow import
state = {
accepted: false,
};
from the TermsAndCondition.js and use that as my conditional in App.js for switchNavigator but kept getting undefined or just simple errors. (I do apologize but I didn't save the errors for these attempts.) But most tutorials use the React.useSet and I haven't been able to find similar solutions.
App.js
import { NavigationContainer } from "#react-navigation/native";
import "react-native-gesture-handler";
import { createStackNavigator } from "#react-navigation/stack";
import mapScreen1 from "./src/screens/mapScreen1";
import mapListScreen from "./src/screens/mapListScreen";
import Home from "./src/screens/Home";
import TermsAndConditions from "./src/screens/termscond";
import isAccepted from "./src/screens/termscond";
const Stack = createStackNavigator();
export default class App extends React.Component {
render() {
return (
<NavigationContainer>
{isAccepted ? (
<Stack.Navigator>
<Stack.Screen
name="Terms and Conditions"
component={TermsAndConditions}
/>
</Stack.Navigator>
) : (
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="mapListScreen" component={mapListScreen} />
<Stack.Screen name="mapScreen1" component={mapScreen1} />
</Stack.Navigator>
)}
</NavigationContainer>
);
}
}
TermsAndCondition.js
import {
View,
Text,
ScrollView,
Dimensions,
TouchableOpacity,
} from "react-native";
const isCloseToBottom = ({ layoutMeasurement, contentOffset, contentSize }) => {
const paddingToBottom = 20;
return (
layoutMeasurement.height + contentOffset.y >=
contentSize.height - paddingToBottom
);
};
export const [isAccepted, setIsAccepted] = React.useState(true);
export default class TermsAndConditions extends Component {
state = {
accepted: false,
};
render() {
return (
<View style={styles.container}>
<Text style={styles.title}>Terms and conditions</Text>
<ScrollView
style={styles.tcContainer}
onScroll={({ nativeEvent }) => {
if (isCloseToBottom(nativeEvent)) {
this.setState({
accepted: true,
});
}
}}
>
<Text style={styles.tcP}>
Welcome to our website. Terms And Condition.
</Text>
</ScrollView>
<TouchableOpacity
disabled={!this.state.accepted}
onPress={() => setIsAccepted(!isAccepted)}
style={this.state.accepted ? styles.button : styles.buttonDisabled}
>
<Text style={styles.buttonLabel}>Accept</Text>
</TouchableOpacity>
</View>
);
}
}

Combining Stack, Drawer and Tab Navigator

I am having trouble realising the following:
I want a Tab Navigation with two Tabs: Home and Map.
In the Home Tab I want a Drawer Navigation. Within the Drawer Navigation I want to be able to Navigate to the screens Profile and About. When clicking the Back Button from Profile or About, I want to come back to the open Drawer Menu.
Currently I have this:
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { createStackNavigator } from '#react-navigation/stack';
import DrawerContent from './DrawerContent';
import Mapp from './Mapp';
import Home from './Home';
import Profile from './Profile';
import About from './About';
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
const Tab = createBottomTabNavigator();
const HomeStackNavigator = () => {
return <Stack.Navigator>
<Stack.Screen
name='Tab'
component={TabNavigator} />
<Stack.Screen
name='Profile'
component={Profile} />
<Stack.Screen
name='About'
component={About} />
</Stack.Navigator>
}
const TabNavigator = () => {
return <Tab.Navigator>
<Tab.Screen name='Home' component={Home} />
<Tab.Screen name='Map' component={Mapp} />
</Tab.Navigator>
}
const DrawerNavigator = () => {
return <Drawer.Navigator
drawerContent={props => DrawerContent(props)}>
<Drawer.Screen name='Home' component={HomeStackNavigator} />
</Drawer.Navigator>
}
const Navigation = () => {
return <NavigationContainer>
<DrawerNavigator />
</NavigationContainer >
}
and <DrawerContent /> looks like this
import React from 'react';
import {
DrawerContentScrollView,
DrawerItemList,
DrawerItem,
} from '#react-navigation/drawer';
const DrawerContent = props => {
return <DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
<DrawerItem label='Profile' onPress={() => props.navigation.navigate('Profile')} />
<DrawerItem label='About' onPress={() => props.navigation.navigate('About')} />
</DrawerContentScrollView>
}
export default DrawerContent;
This gives me
The Tab Navigation (why is there a header with title Tab, I don't want this, but I must set name prop in <HomeStackNavigator />).
Then I can open the Drawer (works as expected)
When I click on i.e. Profile I am only able to go back to Tab. Why not to Drawer?
What also drives me nuts is, that I cannot remove the Home Menu Item in the Drawer.
The drawer doesn't open in that scenario, you will need to open it yourself:
const HomeStackNavigator = () => {
return <Stack.Navigator>
<Stack.Screen
name='Tab'
component={TabNavigator} />
<Stack.Screen
name='Profile'
component={Profile}
options={({ navigation }) => ({
title: '',
headerLeft: () => (
<Button
title="<"
onPress={() => {
navigation.goBack();
navigation.openDrawer();
}}
/>
),
})}/>
<Stack.Screen
name='About'
component={About} />
</Stack.Navigator>
}
Here's a working Snack.

How do I use standalone Header in react-navigation with react-native?

I have a standalone Header that I made:
import React, { useContext } from "react";
import { Appbar } from "react-native-paper";
import { UserContext } from "../contexts/UserContext";
import { LanguageContext } from "../contexts/LanguageContext";
import localeSelect from "../services/localeSelect";
import { title } from "../data/locales";
function Header() {
const { user } = useContext(UserContext);
const { language } = useContext(LanguageContext);
return (
<Appbar.Header>
<Appbar.Action icon="menu" />
{!user && (
<>
<Appbar.Content
title={localeSelect(language, title)}
color="#ffffff"
/>
<Appbar.Action
icon="login"
color="#ffffff"}
/>
<Appbar.Action icon="account-plus" color="#ffffff" />
</>
)}
{user && (
<>
<Appbar.Content
title={localeSelect(language, title)}
color="#ffffff"
/>
</>
)}
</Appbar.Header>
);
}
export default Header;
However, I have been struggling to find a way to connect it to my Stack.Navigator in the main component:
import "react-native-gesture-handler";
import React, { useContext } from "react";
import { NavigationContainer } from "#react-navigation/native";
import { createStackNavigator } from "#react-navigation/stack";
import { View } from "react-native";
import Header from "./components/Header";
import Home from "./components/Home";
import Login from "./components/Login";
import GameNotes from "./components/GameNotes";
import { UserContext } from "./contexts/UserContext";
const Stack = createStackNavigator();
export default function ComponentContainer() {
const { user } = useContext(UserContext);
return (
<View>
<NavigationContainer>
<Header />
<Stack.Navigator initialRouteName="Home">
{user ? (
<Stack.Screen name="Home" component={GameNotes} />
) : (
<Stack.Screen name="Home" component={Home} />
)}
<Stack.Screen name="Login" component={Login} />
</Stack.Navigator>
</NavigationContainer>
</View>
);
}
Obviously this doesn't work, because the header isn't nested in the Stack.Navigator. You also cannot nest it in the Stack.Navigator, because it only accepts Stack.Screen as children.
I've also tried using RootNavigation as a way to do this, but have failed with that as well. So what exactly am I supposed to be doing to use this Header to navigate my app?
So you want to change something in the Stack.Navigator when some action happens in the Header? Most likely what you need is to pass a callback to the Header component from the ComponentContainer, which Header will call when a user clicks something. Inside this callback you can modify your state in order to change the Stack.Navigator or take some other action. The callback will be defined inside ComponentContainer.
I'm not exactly sure how this all works in react-native, but in React this is what it might look like:
Header:
// Imports
function Header(onPressCallback) {
const { user } = useContext(UserContext);
const { language } = useContext(LanguageContext);
return (
<Appbar.Header>
<Appbar.Action icon="menu" onPress={() => onPressCallback('menu')} />
// Other Header items
</Appbar.Header>
);
}
export default Header;
ComponentContainer:
// Imports
const Stack = createStackNavigator();
const onPressHandler = (headerItemName) => {
// Do something in response to a user clicking on the "menu" icon for example
// Maybe something like change the route on the Stack (don't know this API)
}
export default function ComponentContainer() {
const { user } = useContext(UserContext);
return (
<View>
<NavigationContainer>
<Header onPressCallback={onPressHandler} />
<Stack.Navigator initialRouteName="Home">
{user ? (
<Stack.Screen name="Home" component={GameNotes} />
) : (
<Stack.Screen name="Home" component={Home} />
)}
<Stack.Screen name="Login" component={Login} />
</Stack.Navigator>
</NavigationContainer>
</View>
);
}
This is how you usually handle interactions between two siblings components, the parent component passes a callback to one, so that the first child can notify when something happened, and then the parent takes action by changing the state for the second child to respond to.

Categories

Resources