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.
Related
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.
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.
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>
);
}
}
I am trying to create a simple Auth flow in my Expo application.
I've reacreated my project here so you can see what I am doing
Right now what happens in my app is I can register a user using Firebase authtentication, but can't navigate to the authenticated flows. Nothing happens after I click register, but I can see the user in firebase.
I have been reading the React Navigaion docs and tried implementing something similar to what they do for the Auth flow there, but have not had any success.
I think the issue has to do with how I am getting the token in App.js and my use of useEffect
App.js
import React, { useEffect } from 'react';
import * as eva from '#eva-design/eva';
import { Provider as AuthProvider } from './src/context/AuthContext';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { createStackNavigator } from '#react-navigation/stack';
import { ApplicationProvider } from '#ui-kitten/components';
import { NavigationContainer } from '#react-navigation/native';
import LandingScreen from './src/screens/LandingScreen';
import RegisterScreen from './src/screens/RegisterScreen';
import LoginScreen from './src/screens/LoginScreen';
import HomeScreen from './src/screens/HomeScreen';
import DetailScreen from './src/screens/DetailScreen';
const Drawer = createDrawerNavigator();
const Stack = createStackNavigator();
/*eslint-disable */
export default function App() {
let token;
useEffect(() => {
// Fetch the token from storage then navigate to our appropriate place
const localSignin = async () => {
try {
token = await AsyncStorage.getItem('userToken');
} catch (e) {
// Restoring token failed
}
};
console.log(token);
localSignin();
}, []);
return (
<ApplicationProvider {...eva} theme={eva.light}>
<AuthProvider>
<NavigationContainer>
{token ? (
<>
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Details" component={DetailScreen} />
</Drawer.Navigator>
</>
) : (
<>
<Stack.Navigator>
<Stack.Screen name="Landing" component={LandingScreen} />
<Stack.Screen name="Register" component={RegisterScreen} />
<Stack.Screen name="Login" component={LoginScreen} />
</Stack.Navigator>
</>
)}
</NavigationContainer>
</AuthProvider>
</ApplicationProvider>
);
}
Should I be using my AuthContext here somewhere? I am very new to react development so I am sorry if this is somewhat unclear.
Your "token" is manipulated asynchronously, so it should be a state variable so your component may re-render when it got changed. To achieve that, you should replace your token declaration with
const [token, setToken] = React.useState(undefined);
After that, when you want to change your token, instead of reassigning to the token variable, call setToken with the desired value. Like this:
setToken(await AsyncStorage.getItem('userToken'));
The final code should look like this
import React, { useEffect } from 'react';
import * as eva from '#eva-design/eva';
import { Provider as AuthProvider } from './src/context/AuthContext';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { createStackNavigator } from '#react-navigation/stack';
import { ApplicationProvider } from '#ui-kitten/components';
import { NavigationContainer } from '#react-navigation/native';
import LandingScreen from './src/screens/LandingScreen';
import RegisterScreen from './src/screens/RegisterScreen';
import LoginScreen from './src/screens/LoginScreen';
import HomeScreen from './src/screens/HomeScreen';
import DetailScreen from './src/screens/DetailScreen';
const Drawer = createDrawerNavigator();
const Stack = createStackNavigator();
/*eslint-disable */
export default function App() {
const [token, setToken] = React.useState(undefined);
useEffect(() => {
// Fetch the token from storage then navigate to our appropriate place
const localSignin = async () => {
try {
setToken(await AsyncStorage.getItem('userToken'));
} catch (e) {
// Restoring token failed
}
};
console.log(token);
localSignin();
}, []);
return (
<ApplicationProvider {...eva} theme={eva.light}>
<AuthProvider>
<NavigationContainer>
{token ? (
<>
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Details" component={DetailScreen} />
</Drawer.Navigator>
</>
) : (
<>
<Stack.Navigator>
<Stack.Screen name="Landing" component={LandingScreen} />
<Stack.Screen name="Register" component={RegisterScreen} />
<Stack.Screen name="Login" component={LoginScreen} />
</Stack.Navigator>
</>
)}
</NavigationContainer>
</AuthProvider>
</ApplicationProvider>
);
}
I followed the docs of React 5 for Drawer Navigation in react native but getting this error. Here is my Code
import React from 'react'
import {
View,
Button,
Text,
} from 'react-native'
import { createDrawerNavigator } from '#react-navigation/drawer';
import { NavigationContainer } from '#react-navigation/native';
import Screen1 from './DrawerScreens/Screen1';
import Screen2 from './DrawerScreens/Screen2';
import Screen3 from './DrawerScreens/Screen3';
const Drawer = createDrawerNavigator();
function Navigations()
{
return(
<NavigationContainer>
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={Screen1} />
<Drawer.Screen name="Settings" component={Screen2} />
<Drawer.Screen name="Contacts" component={Screen3} />
</Drawer.Navigator>
</NavigationContainer>
);
}
export default Navigations;
I am new to react native, so don't know what to do
You only need to declare one < NavigationContainer > in the top component, example:
function SecondComponent() {
return (
<Tab.Navigator>
<Tab.Screen name="Feed" component={Feed} />
<Tab.Screen name="Messages" component={Messages} />
</Tab.Navigator>
);
}
function FirstComponent() {
return (
<NavigationContainer> {/* this is the only NavigationContainer */}
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Stack.Navigator>
</NavigationContainer>
);
}
pass independent={true} to both of the Navigation container .
<NavigationContainer independent={true}>
</NavigationContainer>
But you will not be able to navigate between the screens of two separate navigation container
If you want to navigate between them then you have to maintain single navigation container.
If you have only one NavigationContainer in your app, please check if you're importing screens correctly.
In my case, I've mistakenly imported the entry route file which contains NavigationContainer itself.
This can happen if you're using same file names for entry route files (e.g. Root.tsx or Main.tsx) and let IDE import files automatically.
You should have only one NavigationContainer in your whole application and it should be in the root level, which is the App class. So here you can see two different components, Navigation and App.
In Navigation component, Drawer Navigator helps to navigate different screens. Import this Navigation component in App component and wrap this with NavigationContainer.
// **** Navigation component ****
import React from 'react';
import {
View,
Button,
Text,
} from 'react-native';
import { createDrawerNavigator } from '#react-navigation/drawer';
import Screen1 from './DrawerScreens/Screen1';
import Screen2 from './DrawerScreens/Screen2';
import Screen3 from './DrawerScreens/Screen3';
const Drawer = createDrawerNavigator();
function Navigation() {
return (
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={Screen1} />
<Drawer.Screen name="Settings" component={Screen2} />
<Drawer.Screen name="Contacts" component={Screen3} />
</Drawer.Navigator>
);
}
export default Navigation;
// **** App component ****
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import {Navigation} from './Navigation';
function App() {
return (
<NavigationContainer>
<Navigation />
</NavigationContainer>
);
}
export default App;
I faced similar type of error while using Stack Navigator and this approach works for me.
In my case I have used BOTTOM NAVIGATION so it has that <NavigationContainer>
just removing <NavigationContainer> in BOTTOM NAVIGATION and only keeping it between <Stack.Navigator> resolved my problem
I think this will helpful for someone ✌