StackRoutes.tsx Component (used in App.tsx)
export const StackRoutes = () => {
const Stack = createNativeStackNavigator()
return (
<NavigationContainer>
<Stack.Navigator initialRouteName={Path.introductionScreen}>
<Stack.Screen
name={Path.onBoardingChooseAliasScreen}
options={Options.onBoardingChooseAliasScreen}
component={OnBoardingChooseAlias}
/>
</Stack.Navigator>
</NavigationContainer>
)
}
Options.tsx component (being used in StackRoutes.tsx)
import React from 'react'
import { NativeStackNavigationOptions } from '#react-navigation/native-stack'
import { CustomBackButton } from './CustomBackButton'
const onBoardingChooseAliasScreen: NativeStackNavigationOptions = {
headerLeft: () => { return <CustomBackButton />}
}
export default {
onBoardingChooseAliasScreen,
}
CustomBackButton.tsx component (being used in Options.tsx)
import React from 'react'
import styled from 'styled-components/native'
import SVGimg from 'src/shared/images/greenMarker.svg'
export const CustomBackButton: React.FC<any> = ({ navigation }) => {
return (
<Wrapper onPress={() => navigation.goBack()}>
<SVGimg />
</Wrapper>
)
}
const Wrapper = styled.Pressable``
The CustomBackButton gets displayed as desired, but the goBack() function doesnt work. If i use default back button it works, so i know there is a stack to fall back on.
How do I make the goBack function work?
It seems like CustomBackButton does not have access to navigation
can you try with useNavigation hook
import React from 'react'
import styled from 'styled-components/native'
import SVGimg from 'src/shared/images/greenMarker.svg'
import { useNavigation } from '#react-navigation/native';
export const CustomBackButton: React.FC<any> = () => {
const navigation = useNavigation()
return (
<Wrapper onPress={() => navigation.goBack()}>
<SVGimg />
</Wrapper>
)
}
const Wrapper = styled.Pressable``
Related
I'm learning react native but I'm having difficulty with navigation, it's returning the error that navigation has not been initialized yet.
I looked for some tutorials, I tried some other ways, I went to the react native navigation documentation and, incredible as it may seem, it's the same as in the documentation... not even the GPT chat haha it didn't help me.
Can someone with experience in react native give me a light?
app.tsx:
import { NavigationContainer } from '#react-navigation/native';
import { createAppContainer } from 'react-navigation';
import StackNavigator from './app/index/navigator';
const AppContainer = createAppContainer(StackNavigator);
const App = () => {
return (
<NavigationContainer>
<AppContainer />
</NavigationContainer>
);
}
export default App;
navigator.tsx?
import { createStackNavigator } from 'react-navigation-stack';
import Index from '.';
import AddNewGrocery from '../components/addNewGrocery'
const StackNavigator = createStackNavigator({
home: { screen: Index, navigationOptions: { headerShown: false } },
addNewGrocery: { screen: AddNewGrocery, navigationOptions: { headerShown: false } },
});
export default StackNavigator;
index.tsx:
const Index = () => {
return (
<View style={styles.container}>
<Text style={styles.title}>Gestão de Compras</Text>
<LastFiveGrocery />
<MonthAverageSpend />
<TotalSpend />
<AddButton />
<StatusBar
translucent={false}
backgroundColor={'rgba(43, 43, 43, 1)'}
barStyle='light-content' />
</View>
);
}
AddButton.tsx:
import React from 'react';
import { StyleSheet, View, TouchableOpacity } from 'react-native';
import { Ionicons } from '#expo/vector-icons';
import { useNavigation } from '#react-navigation/native';
const AddButton = () => {
const navigation = useNavigation();
const handleAddButtonPress = () => {
navigation.navigate('addNewGrocery' as never);
}
return (
<TouchableOpacity style={styles.addButtonContainer} onPress={handleAddButtonPress}>
<View style={styles.addButton}>
<Ionicons name="ios-add" size={36} color="white" />
</View>
</TouchableOpacity>
);
}
I already tried to use it this way:
AddButton:
const { navigate } = useNavigation<StackNavigationProp<ParamListBase>>();
const handleAddButtonPress = () => {
navigate('addNewGrocery');
}
I've also tried using it this way:
navigator:
const StackNavigator = createAppContainer(createStackNavigator({
Home: { screen: Index },
addNewGrocery: AddNewGrocery,
}));
app.tsx:
import StackNavigator from './app/index/navigator';
const App = () => {
return (
<StackNavigator />
);
}
export default App;
You are using 2 different navigation library in simultaneously:
#react-navigation
react-navigation
Remove react-navigation and refactor the App.js file as below:
import { NavigationContainer } from '#react-navigation/native';
import StackNavigator from './app/index/navigator';
const App = () => {
return (
<NavigationContainer>
<StackNavigator />
</NavigationContainer>
);
}
export default App
StackNavigator should be implemented as per documentation -
https://reactnavigation.org/docs/stack-navigator/#api-definition
I'm getting this error when trying to import a font with expo font.
null is not an object" (evaluating 'dispatcher.useState')
But the error is for sure related to my font import.
import { NavigationContainer, useNavigation } from '#react-navigation/native';
import { FontAwesome } from '#expo/vector-icons';
import { MaterialCommunityIcons } from '#expo/vector-icons';
import {createStackNavigator} from '#react-navigation/stack';
import { AuthContext } from "./context";
import BottomTabNavigator from "./navigation/tabsNav";
import * as firebase from "firebase";
import Myapp from './instanceFirebase';
import AuthStackScreen from './navigation/authStack';
import { ActivityIndicator } from 'react-native'
import FlashMessage from "react-native-flash-message";
const instanceFirebase = Myapp;
import "firebase/functions";
import "firebase/firestore";
import * as Font from 'expo-font';
console.log("font", Font)
const [fontState, setFontState] = React.useState(false);
const MainStack = createStackNavigator();
const MainStackScreen = () => {
return(
<MainStack.Navigator initialRouteName="mainPage" >
<MainStack.Screen
name="mainPage"
component={BottomTabNavigator} />
</MainStack.Navigator>
)
}
const RootStack = createStackNavigator();
const RootStackScreen = ({ userToken }) => (
<RootStack.Navigator headerMode="none">
{userToken ? (
<RootStack.Screen
name="App"
component={MainStackScreen}
/>
) : (
<RootStack.Screen
headerMode="none"
name="Authstack"
component={AuthStackScreen}
options={{
animationEnabled: false
}}
/>
)}
</RootStack.Navigator>
);
export default function token() {
const [userToken, setUserToken] = React.useState(null);
const Context = {
instanceFirebase: instanceFirebase,
signOut: () => {
setUserToken(null);
},
signIn:() => {
setUserToken(true);
}
}
React.useEffect(() => {
const fetchFont = async () => {
await Font.loadAsync({
Jost: require('./assets/fonts/Jost-Regular.ttf'),
Jost_black: require('./assets/fonts/Jost-Black.ttf')
})
setFontState(true)
}
fetchFont()
firebase.auth().onAuthStateChanged(user => {
if(user){
setUserToken(true)
} else {
setUserToken(null)
}
})
}, [])
if (!fontState) {
return <ActivityIndicator />
} else {
return (
<AuthContext.Provider value={instanceFirebase}>
<NavigationContainer>
<RootStackScreen userToken={userToken} />
<FlashMessage position="top" style={{zIndex:99, flex:1, marginTop:30}}/>
</NavigationContainer>
</AuthContext.Provider>
)
}
}
I checked that expo-font is well import, and it is fine.
I don't understand what is happening, when i comment the code related to the import of the font my app just work. So my error is for sure related to the font import.
I do things correctly ?
Thanks per advance for your help
The right answer from Med El Mobarik:
Indeed i am not supposed to use the useState outside a funcionnal component
const [fontState, setFontState] = React.useState(false);
Nevertheless is there a way to load the font in the root page (at the start of the app/Where the instance of my app is launching) ? I want to avoid to load the font on each page.
Hi I want to add a function to record the value of input and want to use it in all components and input is in another component and I want to pass a function from App.js to that component but it does not work please help me out \
Please note that the function should be in the App.js passed to Component.js through a Navigator
//App.JS
import React,{useState, useEffect} from "react";
import {
Text,
Link,
HStack,
Center,
Heading,
Switch,
useColorMode,
NativeBaseProvider,
extendTheme,
VStack,
Box,
} from "native-base";
import NativeBaseIcon from "./components/NativeBaseIcon";
import { Platform } from "react-native";
import { createMaterialBottomTabNavigator } from '#react-navigation/material-bottom-tabs';
import HomeScreen from "./Comp/HomeScreen";
import { NavigationContainer } from "#react-navigation/native";
import Location from "./Comp/Location";
export default function App() {
const [api, setapi] = useState(process.env.REACT_APP_API_KEY)
const api_1 = () => {
setapi(process.env.REACT_APP_API_KEY_2)
}
const api_2 = () => {
setapi(process.env.REACT_APP_API_KEY_3)
}
const [value, setvalue] = useState("London")
const handlechange = (e) => {
setvalue(e.target.value)
console.log('value :>> ', value);
}
const Tab = createMaterialBottomTabNavigator();
return (
<NativeBaseProvider>
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen initialParams={{value:value, change:handlechange}} name="Location" component={Location} />
</Tab.Navigator>
</NavigationContainer>
</NativeBaseProvider>
);
}
//Component.js
import { Center, Input, Text } from 'native-base'
import React, {useState} from 'react'
import { ImageBackground } from 'react-native'
import BG from "./Img/BG.png"
export default function Location({ navigation, route }) {
// const [value, setvalue] = useState("London")
// const handlechange = (e) => {
// setvalue(e.target.value)
// console.log('value :>> ', value);
// }
const { change, value } = route.params;
return (
<ImageBackground source={BG}>
<Center minH={"100vh"} w={"90vw"} alignSelf={"center"} alignContent={"center"}>
<Input color={"white"} size="xl" onChange={change} variant="rounded" placeholder="Enter Location" />
<Text color={"white"}></Text>
</Center>
</ImageBackground>
)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I want to push a screen but its not working:
TypeError: navigation.push is not a function. (In 'navigation.push('Restaurants', {
name: params
})', 'navigation.push' is undefined)
Explore.tsx
import { useNavigation } from '#react-navigation/core';
import { NativeStackScreenProps } from '#react-navigation/native-stack';
import React from 'react';
import { StyleSheet, Text, View, Pressable, ScrollView } from 'react-native';
import { RootStackParams } from '../../App';
import Card from '../components/Card';
type PropNav = NativeStackScreenProps<RootStackParams, 'Explore'>;
const Explore = ({ navigation }: PropNav) => {
const handleNavigate = (params: string) => {
navigation.push('Restaurants', { name: params });
};
return (
<View style={{marginTop: 200}}>
<ScrollView>
<Card name="Zum Profil" onPress={(params) => handleNavigate(params)} />
</ScrollView>
</View>
)
};
export default Explore;
App.tsx
import { StatusBar } from 'expo-status-bar';
import { useState } from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';
import Explore from './src/screens/Explore';
import { NavigationContainer, NavigatorScreenParams } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import Profile from './src/screens/Profile';
import Restaurants from './src/screens/Restaurants';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import ProfileIcon from './src/icon/ProfileIcon';
export type RootStackParams = {
Explore: undefined;
Profile: undefined;
RestaurantsStack: NavigatorScreenParams<RestaurantTypesParms>;
Restaurants: {
name: string
}
}
const RootStack = createBottomTabNavigator<RootStackParams>();
export type RestaurantTypesParms = {
Restaurants: {
name: string
}
}
const RestaurantStack = createNativeStackNavigator<RestaurantTypesParms>();
const RestaurantScreen = () => {
return ( <RestaurantStack.Navigator>
<RestaurantStack.Screen name="Restaurants" component={Restaurants} />
</RestaurantStack.Navigator>)
};
export default function App() {
const [text, setText] = useState('');
return (
<NavigationContainer>
<RootStack.Navigator initialRouteName='Explore'
screenOptions={{
headerShown: false,
tabBarActiveTintColor: 'purple'
}}
>
<RootStack.Screen name="Explore" component={Explore}
options={{
tabBarIcon: ({ color, size }) => <ProfileIcon color={color} size={size} />,
tabBarLabel: "Explore"
}}
/>
<RootStack.Screen name="Profile" component={Profile} />
<RootStack.Screen name="RestaurantsStack" component={RestaurantScreen} />
</RootStack.Navigator>
</NavigationContainer>
);
}
But if I use navigation.navigate then it works....
........................................................................................................................................................................................................................................
The push method for the navigation prop is added additional for the StackNavigator. You can compare this with the official documentation.
The stack navigator adds the following methods to the navigation prop:
push
Pushes a new screen to top of the stack and navigate to it. The method accepts following arguments:
name - string - Name of the route to push onto the stack.
params - object - Screen params to pass to the destination route.
You are not using a Stack.Navigator. You need to install #react-navigation/native-stack and use a different navigator to make this work. I would suggest that you go through this documentation where a full working example is provided.
you can try this.
const handleNavigate = (params: string) => {
navigation.navigate('Restaurants', { name: params });
};
or
const handleNavigate = (params: string) => {
navigation.replace('Restaurants', { name: params });
};
or
const handleNavigate = (params: string) => {
navigation.navigate('RestaurantScreen', { name: params });
};
So I built a login system with React Native, React Navigation and AsyncStorage. If the user clicks on a button he gets logged in and the value of the AsyncStorage Key "#loginuser" gets refreshed. Now I expected
that the screen automatically gets refreshed, but I have to close the App and start it again - This is not optimal.
(I also saw React-Native/React navigation redirection after login with redux post, but it is very old)
App.js
import React from 'react';
import { Text, View, StyleSheet, Button, TouchableOpacity, TextInput } from 'react-native';
import DefaultStackNavigation from './components/Navigation/Navigation';
const App = () => {
return(
<View>
<DefaultStackNavigation />
</View>
)
}
export default App;
Navigation.js
import React, {useEffect, useState} from 'react';
//React Native
import { Text, View, StyleSheet} from 'react-native';
//Screens
import HomeScreen from '../HomeScreen/HomeScreen'
import AddScreen from "../AddScreen/AddScreen";
import NotificationScreen from "../NotificationScreen/NotificationScreen";
import MenuScreen from "../MenuScreen/MenuScreen";
import SearchScreen from "../SearchScreen/SearchScreen";
import PostJobScreen from "../PostJobScreen/PostJobScreen";
import JobOfferScreen from "../JobOfferScreen/JobOfferScreen";
import ProfileScreen from "../ProfileScreen/ProfileScreen";
import NoneLoggedinScreen from "../NoneLoggedinScreen/NoneLoggedinScreen"
import SignupModal from "../NoneLoggedinScreen/SignupModal"
//React Navigation
import { createStackNavigator } from "#react-navigation/stack";
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
//Third Party
import AsyncStorage from '#react-native-async-storage/async-storage';
const Tab = createBottomTabNavigator();
//Tab bar
const HomeTabs = () => {
return (
<Tab.Navigator tabBarOptions={{style:{height: 50}}, {showIcon: true}, {showLabel: false}} >
<Tab.Screen name="Home" component={HomeScreen}/>
<Tab.Screen name="SearchScreen" component={SearchJobStack}/>
<Tab.Screen name="AddScreen" component={AddScreen}/>
<Tab.Screen name="NotificationScreen" component={NotificationScreen} />}}/>
<Tab.Screen name="MenuScreen" component={MenuScreen}/>}}/>
</Tab.Navigator>
);
}
const Stack = createStackNavigator();
const STORAGE_KEY = '#loginStatus'
const DefaultStackNavigation = () => {
const [loginStatus, setLoginStatus] = useState()
const readData = async () => {
try {
const isLoggedIn = JSON.parse(await AsyncStorage.getItem(STORAGE_KEY))
console.log(isLoggedIn)
if (isLoggedIn !== null) {
setLoginStatus(isLoggedIn)
}
} catch (e) {
alert('Failed to fetch the data from storage')
}
}
readData()
return loginStatus ? (
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}} independent={false}>
<Stack.Screen name="HomeTabs" component={HomeTabs} />
<Stack.Screen name="PostJobScreen" component={PostJobScreen} />
<Stack.Screen name="ProfileScreen" component={ProfileScreen}/>
</Stack.Navigator>
</NavigationContainer>
) : (
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}} independent={false}>
<Stack.Screen name="NoneLoggedinScreen" component={NoneLoggedinScreen} />
<Stack.Screen name="SignupModal" component={SignupModal} />
</Stack.Navigator>
</NavigationContainer>
);
};
export default DefaultStackNavigation;
NoneLoggedinScreen.js
import React, { useState, useEffect } from 'react';
import { Text, View, Button} from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
const STORAGE_KEY = '#loginStatus'
const SignupModal = () => {
const [loginStatus, setLoginStatus] = useState(false)
const saveData = async (parmLoginStatus) => {
try {
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(parmLoginStatus))
alert('Data successfully saved -> Logged In')
console.log(loginStatus)
} catch (e) {
alert('Failed to save the data to the storage')
}
}
const onSubmitLogin = () => {
setLoginStatus(true)
saveData(true)
}
return(
<View>
<Text>Login Page. Press the button to log in and stay logged in</Text>
<Button title="Log in" onPress={() => onSubmitLogin()}/>
</View>
);
};
const styles = StyleSheet.create({
App: {
flex: 1,
backgroundColor: "white"
},
});
export default SignupModal;
Now I was expecting the page to reload and I can use the app. Unfortunately it doesn't. The data is saved and the login status is set to true, but I have to restart the app to use it. The perfidious thing is, if I write the AsyncStorage login logic from the NoneLoggedinScreen.js file into the App.js file, the app works fine -> However, this is not an alternative for me, because the general structure of the app is, I think, built relatively sensibly.
Also when the user tries to be redirected manually (by button) with navigation.navigate("HomeTabs") after logging in doesn't work, and I get an error that Home doesn't exist, which is also not understandable, because the navigation has actually been set to Logged in now. Has anyone ever had this problem?
This are my dependencies by the way
"dependencies": {<br>
"#react-native-async-storage/async-storage": "^1.15.1",<br>
"#react-native-community/masked-view": "^0.1.10",<br>
"#react-navigation/bottom-tabs": "^5.11.7",<br>
"#react-navigation/native": "^5.9.2",<br>
"#react-navigation/stack": "^5.14.2",<br>
"react": "16.13.1",<br>
"react-native": "0.63.4",<br>
"react-native-gesture-handler": "^1.10.1",<br>
"react-native-reanimated": "^1.13.2",<br>
"react-native-safe-area-context": "^3.1.9",<br>
"react-native-screens": "^2.17.1",<br>
}
In your authentication strategy, there is a hidden assumption that when you save data using AsyncStorage in NoneLoggedInScreen, the data will immediately be read using the readData function in DefaultStackNavigation. The reason why this does not happen is that readData is only called when DefaultStackNavigation renders/re-renders. This will not happen when one of its children sets some data in local storage.
There are many ways to fix this. Using redux with redux-persist or another state-management and persistence setup, or a purpose-built context-based solution (see eg. this Kent C. Dodds article on Authentication in React Applications).
The second, minor issue that you mention:
navigation.navigate("HomeTabs") after logging in doesn't work, and I get an error that Home doesn't exist
does make sense, and the reason behind it is that you conditionally render two different NavigationContainers. They don't know about each others routes, so you can't navigate between them.
To fix this, you should render one NavigationContainer and conditionally render one of the Stack.Navigators as its child.
To manage authentication flow in a smooth way we should make two seperate navigators for the navigation. I find this workflow the best as of now. It works perfectly and is clean.
So what I usually do in my Projects that I create two navigators AuthNavigator.js and AppNavigator.js and then use conditional rendering.
So what you can do is create a folder called navigation where your App.js is located. Then inside navigation folder create two files called AppNavigator.js and AuthNavigator.js.
Your AuthNavigator.js should look like this
import React from "react";
import NoneLoggedinScreen from "../NoneLoggedinScreen/NoneLoggedinScreen";
import SignupModal from "../NoneLoggedinScreen/SignupModal";
import { createStackNavigator } from "#react-navigation/stack";
const Stack = createStackNavigator();
const AuthNavigator = () => {
return (
<Stack.Navigator screenOptions={{ headerShown: false }} independent={false}>
<Stack.Screen name="NoneLoggedinScreen" component={NoneLoggedinScreen} />
<Stack.Screen name="SignupModal" component={SignupModal} />
</Stack.Navigator>
);
};
export default AuthNavigator;
Your AppNavigator.js should look like this
import React from "react";
import HomeScreen from "../HomeScreen/HomeScreen";
import AddScreen from "../AddScreen/AddScreen";
import NotificationScreen from "../NotificationScreen/NotificationScreen";
import MenuScreen from "../MenuScreen/MenuScreen";
import PostJobScreen from "../PostJobScreen/PostJobScreen";
import ProfileScreen from "../ProfileScreen/ProfileScreen";
import { createStackNavigator } from "#react-navigation/stack";
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs";
const Tab = createBottomTabNavigator();
//Tab bar
const HomeTabs = () => {
return (
<Tab.Navigator
tabBarOptions={
({ style: { height: 50 } }, { showIcon: true }, { showLabel: false })
}
>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="SearchScreen" component={SearchJobStack} />
<Tab.Screen name="AddScreen" component={AddScreen} />
<Tab.Screen name="NotificationScreen" component={NotificationScreen} />
<Tab.Screen name="MenuScreen" component={MenuScreen} />
</Tab.Navigator>
);
};
const Stack = createStackNavigator();
const AppNavigator = () => {
return (
<Stack.Navigator screenOptions={{ headerShown: false }} independent={false}>
<Stack.Screen name="HomeTabs" component={HomeTabs} />
<Stack.Screen name="PostJobScreen" component={PostJobScreen} />
<Stack.Screen name="ProfileScreen" component={ProfileScreen} />
</Stack.Navigator>
);
};
export default AppNavigator;
And your App.js should look like this
import React, { useState, useEffect } from 'react';
import { NavigationContainer } from '#react-navigation/native';
import AuthStorage from './auth/storage';
import AppNavigator from './navigation/AppNavigator';
import AuthNavigator from './navigation/AuthNavigator';
const App = () => {
const [loginStatus, setLoginStatus] = useState(false);
// I am using useEffect hook but I would prefer using `AppLoading` to restore token if it exists
useEffect(() => {
readData();
}, []);
const readData = async () => {
try {
const isLoggedIn = JSON.parse(await AuthStorage.getItem());
console.log(isLoggedIn);
if (isLoggedIn !== null) {
setLoginStatus(isLoggedIn);
}
} catch (e) {
alert('Failed to fetch the data from storage');
}
};
return (
<NavigationContainer>
{loginStatus ? <AppNavigator /> : <AuthNavigator />}
</NavigationContainer>
);
};
export default App;
Create a folder called auth where your App.js is located. Inside that create a file called storage.js. Now inside storage.js paste this code
import AsyncStorage from '#react-native-async-storage/async-storage';
const AuthToken = '#loginStatus';
const storeItem = async (value) => {
try {
await AsyncStorage.setItem(AuthToken, JSON.stringify(value));
return true;
} catch (e) {
return false;
}
};
const getItem = async () => {
try {
const jsonValue = await AsyncStorage.getItem(AuthToken);
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch (e) {
return null;
}
};
export default { storeItem, getItem };