I have a scenario where I'm trying to use multiple stack navigators within my React Native application.
At the moment I have a StackNavigator set up within
navigation>Home.js
I want a second StackNavigator set up for my ProfileScreen so I have created a second file navigation>ProfileStack.js
Ideally I'd really not want to have the home containing the stack screen because a user can not go from Home > EditProfileScreen. This also causes a bit of a bug where when the app on launch it loads the profile first and if you select edit profile it says "The action 'NAVIGATE' with payload {"name":"EditProfileScreen"} was not handled by any navigator."
However if I open the drawer navigator, go to the home page, then back through the drawer navigator to the profile page, I can go into edit profile. Though the stack when you swipe back goes to home and not the profile page. I understand this is because currently the Home.js contains the Stack.Screens. (It doesn't work if I don't have them there)
Big explanation but thought a lot of detail was better than minimal. The app.js renders the which contains the drawer navigator which loads on launch.
**ProfileStack.js**
import React from 'react';
import { createNativeStackNavigator } from "#react-navigation/native-stack";
import EditProfileScreen from '../screens/EditProfileScreen';
import { NavigationContainer } from '#react-navigation/native';
import ProfileScreen from '../screens/ProfileScreen';
import UserFavouritesScreen from '../screens/UserFavouritesScreen';
const Stack = createNativeStackNavigator();
function ProfileStack(props) {
return(
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="ProfileScreen" component={ProfileScreen}/>
<Stack.Screen name="EditProfileScreen" component={EditProfileScreen}/>
<Stack.Screen name="UserFavouritesScreen" component={UserFavouritesScreen}/>
</Stack.Navigator>
</NavigationContainer>
);
};
export default ProfileStack;
**Home.js**
import React from 'react';
import { createDrawerNavigator } from '#react-navigation/drawer'
import HomeScreen from '../screens/HomeScreen';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { KeyboardAvoidingView, Platform } from 'react-native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import MapScreen from '../screens/MapScreen';
import WalkingScreen from '../screens/WalkingScreen';
import TourDetail from '../screens/TourDetail';
import { TouchableOpacity } from 'react-native';
import tw from 'twrnc';
import { Icon } from "react-native-elements";
import { useNavigation } from "#react-navigation/native";
import EditProfileScreen from '../screens/EditProfileScreen';
import ProfileScreen from '../screens/ProfileScreen';
import UserFavouritesScreen from '../screens/UserFavouritesScreen';
const HomeNavigator = (props) => {
const Stack = createNativeStackNavigator();
const navigation = useNavigation();
return (
<KeyboardAvoidingView //This ensures that the keyboard does not hide whats behind
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={{flex: 1}}
keyboardVerticalOffset={Platform.OS === "ios" ? -64 : 0}
>
<TouchableOpacity
onPress={() => navigation.toggleDrawer()}
style={tw`bg-gray-100 absolute top-16 left-8 z-50 p-3 rounded-full shadow-lg`}>
<Icon name="menu" />
</TouchableOpacity>
{/* <AuthStack /> */}
<Stack.Navigator
screenOptions={{headerShown: false, }}
>
<Stack.Screen name='HomeScreen' component={HomeScreen} />
<Stack.Screen name='MapScreen' component={MapScreen} />
<Stack.Screen name='WalkingScreen' component={WalkingScreen} />
<Stack.Screen name='TourDetail' component={TourDetail} />
*<Stack.Screen name="ProfileScreen" component={ProfileScreen}/>
<Stack.Screen name="EditProfileScreen" component={EditProfileScreen}/>
<Stack.Screen name="UserFavouritesScreen" component={UserFavouritesScreen}/>*
</Stack.Navigator>
</KeyboardAvoidingView>
);
};
export default HomeNavigator;
```
Related
Problem
I am new to React Native. Recently I faced with such a problem: when I move from the login screen to the signup screen, the signup screen is loaded again each time. How I can load the screen only once and store it in RAM or do something like that, to avoid rerendering? How to add and remove screens to RAM manually through code.
Environment
MacOS 13.0.1 (M1)
React Native CLI
dependencies:
#react-navigation/native: ^6.1.2,
#react-navigation/stack: ^6.3.11,
expo-linear-gradient: ^12.0.1,
react: 18.1.0,
react-native: ^0.70.6,
react-native-color-picker: ^0.6.0,
react-native-event-listeners: ^1.0.7,
react-native-gesture-handler: ^2.9.0,
react-native-localization: ^2.3.1,
react-native-paper: ^5.1.3,
react-native-paper-dropdown: ^1.0.7,
react-native-safe-area-context: ^4.4.1,
react-native-screens: ^3.19.0,
react-native-vector-icons: ^9.2.0,
Code
App.navigator.tsx
import React from "react"
import { createStackNavigator, StackView } from "#react-navigation/stack"
import { NavigationContainer } from "#react-navigation/native"
import { SignupScreen } from "./screens/signup/signup.screen"
import { LoginScreen } from "./screens/login/login.screen"
import { HomeScreen } from "./screens/home/home.screen"
import { SettingsScreen } from "./screens/settings/settings.screen"
const Stack = createStackNavigator()
// App navigation. Used to relocate between the app's screens
const AppNavigator = () => (
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false,
headerMode: "screen",
cardStyle: {backgroundColor: "white"}}}
initialRouteName="Login">
<Stack.Screen name="Login"
component={LoginScreen} />
<Stack.Screen name="SignUp"
component={SignupScreen} />
<Stack.Screen name="Home"
component={HomeScreen} />
<Stack.Screen name="Settings"
component={SettingsScreen} />
</Stack.Navigator>
</NavigationContainer>
)
export default AppNavigator
App.tsx
import React, {useState, useEffect} from 'react';
import {Provider as PaperProvider} from 'react-native-paper';
import { EventRegister } from 'react-native-event-listeners';
import AppNavigator from './app.navigator';
import mainAppTheme, {fontTheme} from './config/theme';
import themeContext from './config/themeContext';
import { CHANGE_THEME_LITERAL } from './config/theme';
const App = () => {
// Theme update
const [theme, setTheme] = useState(mainAppTheme)
useEffect(() => {
let eventListener = EventRegister.addEventListener(CHANGE_THEME_LITERAL, (theme) => {
setTheme(theme)
})
return () => {
EventRegister.removeEventListener(eventListener.toString())
}
})
return (
<themeContext.Provider value={theme}>
<PaperProvider theme={fontTheme}>
<AppNavigator />
</PaperProvider>
</themeContext.Provider>
);
};
export default App;
Login.screen.tsx
import React, { useContext, useState } from "react";
import { SafeAreaView, View } from "react-native";
import { Button, Card, TextInput } from "react-native-paper";
import { PasswordComponent } from "../../components/password.component";
import mainAppTheme, {appTheme} from "../../config/theme";
import themeContext from "../../config/themeContext";
import { loginScreenTrans } from "../../translations/login.screen.trans"
import { loginStyle } from "./login.style";
interface LoignScreenProps{
navigation: any
}
// Login app screen. Meets new users
export const LoginScreen = (props:LoignScreenProps) => {
// Theme update
const theme: appTheme = useContext(themeContext)
loginScreenTrans.setLanguage(theme.lang)
return(
<SafeAreaView style={loginStyle().content}>
<View style={loginStyle().view}>
<Card>
<Card.Title title="BuildIn" titleStyle={loginStyle().cardTitle}></Card.Title>
<Card.Content>
<TextInput selectionColor={theme.colors.mainAppColor}
activeUnderlineColor={theme.colors.mainAppColor}
label={loginScreenTrans.email}
keyboardType="email-address"
style={loginStyle().email} />
<PasswordComponent label={loginScreenTrans.password}
color={theme.colors.mainAppColor.toString()}
style={loginStyle().password}/>
<Button uppercase={false}
textColor={theme.colors.mainAppTextColor}>
{loginScreenTrans.forgot}
</Button>
<Button buttonColor={theme.colors.mainAppColor}
textColor={"#ffffff"}
uppercase={true}
mode="contained"
onPress={() => {props.navigation.navigate("Home");
props.navigation.reset({
index: 0,
routes: [{ name: 'Home' }]})
}}>
{loginScreenTrans.login}
</Button>
<Button uppercase={true}
textColor={mainAppTheme.colors.mainAppTextColor}
onPress={() => props.navigation.navigate("SignUp")}>
{loginScreenTrans.signup}
</Button>
</Card.Content>
</Card>
</View>
</SafeAreaView>
);
}
What i tried
Next steps changed nothing in my case:
Use enableFreeze(true) and enableScreens(true) at the top of loaded screen file and top of navigation file.
Wrap my screen in React.memo()
Set freezeOnBlur: true prop into <Stack.Navigator>
navigation.dispatch(CommonActions.reset({ routes[...]
Maybe I used it incorrectly, I would be grateful if someone could explain to me why.
But it seems that the problem is specifically in the way react navigation works. And I don't know how to set it up the way I want.
This is the btn that 'controls' the navigation.
It loads the first Stack, Home, but when I press the touchable opacity to go QnScreen it drops an error: undefined is not an object (evaluating 'navigation.navigate').
I try it all, I'm falling into depression lol.
import * as React from 'react';
import { LinearGradient } from "expo-linear-gradient";
import { StyleSheet, Text, TouchableOpacity, Image } from "react-native";
export default function DocCard( { navigation } ){
return(
<TouchableOpacity
style={styles.container}
onPress={()=>{
navigation.navigate('QnScreen')
}}
>
<LinearGradient
style={styles.linearGradient}
colors={['#4822E4','transparent']}
start={{x:0, y:0}}
end={{x:1, y:1}}>
<Text style={styles.title}>Create new Doc.</Text>
<Image
style={styles.icon}
source={require('../../img/HomeScreen/doc_icon.png')}
/>
</LinearGradient>
</TouchableOpacity>
);
}
And this is the MainStack:
import { NavigationContainer, useNavigation } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import QnScreen from '../screens/QnScreen';
import Home from '../screens/Home';
import React from 'react';
const Stack = createNativeStackNavigator();
export default function MainStack(){
return(
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerShown: false
}}
>
<Stack.Screen
name='Home'
component={Home}
/>
<Stack.Screen
name='QnScreen'
component={QnScreen}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
I'm using Expo with typescript
First off, you'll need to have that navigation.navigate() call include a parameter that states which screen/screen-stack you want to navigate to. It doesn't look like that's your main issue though.
I can't tell from just the code you've posted, but judging from the 'navigation' object being undefined, it's most likely that your DocCard component is not a child of your Navigator. Where is DocCard in your component tree? Is it inside QnScreen or Home? Cause that's where it should be.
Then you'll need to receive the 'navigation' prop from one of your screens. And pass it as a prop to DocCard to be used. 'Navigation' and 'route' props are only automatically passed to screens.
I solve it!!!
The solution was using the useNavigation hook inside the component.
import { useNavigation } from "#react-navigation/native";
import { LinearGradient } from "expo-linear-gradient";
import { StyleSheet, Text, TouchableOpacity, Image } from "react-native";
export default function DocCard(){
const navigation = useNavigation();
return(
<TouchableOpacity
style={styles.container}
onPress={()=>{
navigation.navigate('QnScreen');
}}
>
<LinearGradient
style={styles.linearGradient}
colors={['#4822E4','transparent']}
start={{x:0, y:0}}
end={{x:1, y:1}}>
<Text style={styles.title}>Crear nuevo documento de consentimiento informado.</Text>
<Image
style={styles.icon}
source={require('../../img/HomeScreen/doc_icon.png')}
/>
</LinearGradient>
</TouchableOpacity>
);
}
If anybody have this problem, there's the solution:)
I am new in react native and I don't understand why it is not working
In my main file App.js , I need to have a button to go to another screen( AddList.js)
So, In app.js , I have
function App (navigation) {
const onPressHandler = () => {
navigation.navigate('AddList')
}
return (
<Pressable onPress={onPressHandler}>}
function addList(navigation) {
const onPressHandler = () => {
navigation.goBack();
}
return(
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={App}
/>
<Stack.Screen
name="FavProducts"
component={FavProducts}
/>
</Stack.Navigator>
</NavigationContainer>
)
}
There are in the same file App.js
Sorry for the question and thanks to your help
I have try with reactnative doc , but not working
If you are using #react-navigation/native then first you need to create container to handle the component state and then you have to create stack
in side the stack you need to define your screens like this.
import React from 'react';
import { NavigationContainer } from "#react-navigation/native";
import { createStackNavigator } from "#react-navigation/stack";
import AddList from './components/screens/AddList';
const Stack = createStackNavigator();
const App = () => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
component={AddList}
name="AddList"
options={{ headerShown: false }}
/>
// define your screens as well
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;
and if you have to move from one screen to another then import in your js file where you have to create the click event
import { useNavigation } from '#react-navigation/native';
const navigation = useNavigation();
and on button press
navigation.navigate('AddList')
In my react-native app I am in the process of upgrading to react-navigation v5. For AppNavigator, which is passed into my main App.js file, the old code looks like this:
import { createAppContainer, createSwitchNavigator, createStackNavigator } from 'react-navigation';
import LoginScreen from '../screens/LoginScreen';
import LoadingScreen from '../screens/LoadingScreen';
const AuthStack = createStackNavigator({
Login: LoginScreen,
Loading: LoadingScreen
});
import MainDrawerNavigator from './DrawerNavigator';
export default createAppContainer(createSwitchNavigator({
Main: MainDrawerNavigator,
Auth: AuthStack
},
{
initialRouteName: 'Auth',
}));
The code gets used in App.js like so:
<Provider store={store}>
<NavigationContainer ref={navigationRef}>
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="light-content" />}
<AppNavigator ref={nav => { navigatorRef = nav }} /> // AppNavigator used here
</View>
</NavigationContainer>
</Provider>
From what I can tell, createAppContainer is no longer the way to handle this in react-navigation v5. I'm getting an error on this import specifically:
import { createAppContainer, createSwitchNavigator, createStackNavigator } from 'react-navigation';
... partly because the directory structure for the location of these kinds of imports has changed. But from what I can tell, createAppContainer is not existent now? In other words, it's not just an issue of it being located somewhere else now. Correct me if I'm wrong.
So my question is, what should this code look like in v5? Any insight would be appreciated. I'm referring to this AppNavigator code:
import { createAppContainer, createSwitchNavigator, createStackNavigator } from 'react-navigation';
import LoginScreen from '../screens/LoginScreen';
import LoadingScreen from '../screens/LoadingScreen';
const AuthStack = createStackNavigator({
Login: LoginScreen,
Loading: LoadingScreen
});
import MainDrawerNavigator from './DrawerNavigator';
export default createAppContainer(createSwitchNavigator({
Main: MainDrawerNavigator,
Auth: AuthStack
},
{
initialRouteName: 'Auth',
}));
Firstly first you need import NavigationContainer to contain all of your navigations
import { NavigationContainer } from '#react-navigation/native'
then after that create your StackNavigator like this
import { createStackNavigator } from '#react-navigation/stack'
const Stack = createStackNavigator()
import LoginScreen from '../screens/LoginScreen'
import LoadingScreen from '../screens/LoadingScreen'
function AuthStack() {
return (
<Stack.Navigator>
<Stack.Screen
name = "LoginScreen"
component = {LoginScreen}
/>
<Stack.Screen
name = "LoadingScreen"
component = {LoadingScreen}
/>
</Stack.Navigator>
)
}
next, create the drawer navigator
import { createDrawerNavigator } from '#react-navigation/drawer'
const MainDrawer = createDrawerNavigator()
finally the App function will looks like this
import { NavigationContainer } from '#react-navigation/native'
import { createStackNavigator } from '#react-navigation/stack'
import { createDrawerNavigator } from '#react-navigation/drawer'
const Stack = createStackNavigator()
import LoginScreen from '../screens/LoginScreen'
import LoadingScreen from '../screens/LoadingScreen'
function AuthStack() {
return (
<Stack.Navigator>
<Stack.Screen
name = "LoginScreen"
component = {LoginScreen}
/>
<Stack.Screen
name = "LoadingScreen"
component = {LoadingScreen}
/>
</Stack.Navigator>
)
}
const MainDrawer = createDrawerNavigator()
export default function App() {
return (
<NavigationContainer>
<MainDrawer.Navigator
initialRouteName = "Auth"
>
<MainDrawer.Screen
name = 'Auth'
component = {AuthStack}
/>
<MainDrawer.Screen
name = 'YourDrawerScreen'
component = {YourDrawerScreen}
/>
</MainDrawer.Navigator>
</NavigationContainer>
)
}
I don't think there is Switch Navigator in React-Navigation v5
I'd like to use the React Navigation v5 (Drawer navigation) with Next.js but I have a question about their integration.
In short: React Navigation (Drawer navigation) based on the React Navigation Screens component.
It conditionally renders the correct Screen.
The problem is: Next.js has it's own routing system based on the Folder structure. eg. each file in /pages folder automatically generates an appropriate route so I can't add these files as a React Navigation Screen (at least I'm not sure it's possible at all)
How to make these tools to work together and save the Next.js SSR feature?
Example of the React Navigation Drawer:
import * as React from 'react';
import { Button, View } from 'react-native';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { NavigationContainer } from '#react-navigation/native';
function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button
onPress={() => navigation.navigate('Notifications')}
title="Go to notifications"
/>
</View>
);
}
function NotificationsScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button onPress={() => navigation.goBack()} title="Go back home" />
</View>
);
}
const Drawer = createDrawerNavigator();
export default function App() {
return (
<NavigationContainer>
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Notifications" component={NotificationsScreen} />
</Drawer.Navigator>
</NavigationContainer>
);
}
Thanks for any help!
You should use file based routing system from Nextjs on web and do your own navigation on mobile using React Navigation.
Below is my approach,
// this is how your directory might look like
- pages/
- index.tsx // this is your entry point for web
- about.tsx
App.tsx // this is your entry point for native
// pages/index.tsx
import React from 'react';
import { Text, View } from 'react-native';
const Home: React.FC = () => (
<View>
<Text>Welcome to Expo + Next.js 👋</Text>
</View>
);
export default Home;
// pages/about.tsx
import React from 'react';
import { Text, View } from 'react-native';
const About: React.FC = () => (
<View>
<Text>This is about page!</Text>
</View>
);
export default About;
Define your navigator for native app in App.tsx, it will only work on mobile so it doesn't have to be the same as what you have in pages/ folder. (actually if you only want your app run in browser, you don't need it at all.
Nextjs will handle all the route things, SSR etc... just like a normal Nextjs app when you run it in a browser.
// App.tsx
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createDrawerNavigator } from '#react-navigation/drawer';
import Home from '../pages/index';
import About from '../pages/about';
const Drawer = createDrawerNavigator();
const App: React.FC = () => (
<NavigationContainer>
<Drawer.Navigator>
<Drawer.Screen name="Home" component={Home} />
<Drawer.Screen name="About" component={About} />
</Drawer.Navigator>
</NavigationContainer>
);
export default App;
The important thing is how should you change routes when you have your navigation on native app but an automatically routing system on web?
There is a package to solve this expo-next-react-navigation, check the documentation for details! Make sure you're using the correct version of this package, if you're using React Navigation 5, you should install expo-next-react-navigation#1.1.6 at this moment.
And here is an example, it should work on both platforms,
import React from 'react';
import { FlatList, Text } from 'react-native';
import { Link } from 'expo-next-react-navigation';
const links = [
{ key: 'home', route: '' },
{ key: 'about', route: 'about' },
];
const Links: React.FC = () => (
<FlatList
data={links}
renderItem={({ item }) => (
<Link routeName={item.route}>
{item.key}
</Link>
)}
/>
);
export default Links;