I am trying to wrap one of my navigators with User Context that I created. I have achieved this before in other projects but I am encountering an issue. I Tried following this solution but it doesn't seem to be the same issue I am encountering. I can't exactly tell what is wrong here.
App.js Code :
import React, { useContext, useEffect } from "react";
import { View, Text, AsyncStorage, Button } from "react-native";
import { createStackNavigator } from "#react-navigation/stack";
import HomeScreen from "./src/screens/HomeScreen";
import LoginScreen from "./src/screens/login";
import CalendarScreen from "./src/screens/Calendar";
import SignUpScreen from "./src/screens/signUp";
import { scale, vs } from "react-native-size-matters";
import { createDrawerNavigator } from "#react-navigation/drawer";
import { createMaterialBottomTabNavigator } from "#react-navigation/material-bottom-tabs";
import { Icon } from "react-native-elements";
import UserContext, { UserProvider } from "./src/screens/Context/UserContext";
import { NavigationContainer } from "#react-navigation/native";
const Tab = createMaterialBottomTabNavigator();
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
const signedIn = true; //this is for testing
const drawer_style = {
backgroundColor: "#202B35",
activeTintColor: "#000",
width: 200,
};
const drawer_item_style = {
activeTintColor: "orange",
inactiveTintColor: "#fff",
itemStyle: { marginVertical: vs(10) },
};
const non_user_stack = () => {
<Stack.Navigator>
<Stack.Screen
name="Sign in - Cal "
component={LoginScreen}
options={({ navigation }) => ({
headerShown: true,
headerTintColor: "orange",
headerStyle: {
backgroundColor: "#202B35",
},
})}
/>
<Stack.Screen
name="Sign up - Cal "
component={SignUpScreen}
options={({ navigation }) => ({
headerShown: true,
headerTintColor: "orange",
headerStyle: {
backgroundColor: "#202B35",
},
})}
/>
</Stack.Navigator>;
};
const UserMenu = () => {
return (
<NavigationContainer>
<Drawer.Navigator
initialRouteName="Home"
drawerStyle={drawer_style}
drawerContentOptions={drawer_item_style}
drawerType="front"
>
<Drawer.Screen name="Home" component={MyStack} />
</Drawer.Navigator>
</NavigationContainer>
);
};
const MyStack = () => {
return(
<Stack.Navigator>
<Stack.Screen
name="Cal"
component={BottomNav}
options={({ navigation }) => ({
headerShown: true,
headerTintColor: "orange",
headerStyle: {
backgroundColor: "#202B35",
},
headerLeft: (props) => (
<Icon
size={25}
name={"ios-menu"}
iconStyle={{
fontSize: scale(30),
color: "orange",
margin: 5,
marginLeft: 10,
}}
type="ionicon"
color="orange"
onPress={() => {
navigation.toggleDrawer();
}}
/>
),
})}
/>
</Stack.Navigator>
)
};
export default App = () => {
const { isSignedIn, check_and_set_signin_status } = useContext(UserContext); //<-- causes crash
return (
isSignedIn === "false" ? (
<UserProvider>
<UserMenu />
</UserProvider>
) : (
<non_user_stack></non_user_stack>
);
);
};
UserContext.js :
import React, { useState, useEffect } from "react";
import * as Permissions from "expo-permissions";
import axios from "axios";
import { AsyncStorage } from "react-native";
//import registerForPushNotificationsAsync from "../Hooks/registerForPushNotifications";
import Constants from "expo-constants";
const UserContext = React.createContext();
const IS_SIGNEDIN = "is_signed_in";
export const UserProvider = ({ children }) => {
const [isSignedIn, setSignIn] = useState(null);
const [didAuthenticate, setAuthenticated] = useState(null);
//Check if this user already signed in before and didnt log out since their last session
//used for conditional rendering
const check_and_set_signin_status = async () => {
const signed_in = await AsyncStorage.getItem(IS_SIGNEDIN);
if (signed_in == null || signed_in == "false") {
await AsyncStorage.setItem(IS_SIGNEDIN, "false");
setSignIn("false");
} else {
setSignIn("true");
}
};
return (
<UserContext.Provider
value={{
isSignedIn, // well use this for conditional rendering
check_and_set_signin_status,
}}
>
{children}
</UserContext.Provider>
);
};
The Error :
there is some mistake in your code
you are not exporting UserContext but you are importing UserContext
in App.js file
you are trying to use useContext and provider in same file but you
have to useContext inside of Provider child component
you are non_user_stack with first letter capital but you have to
make first letter capital
UserContext.js : you have to export UserContext in this file
import React, { useState, useEffect } from "react";
import { Text } from 'react-native'
import * as Permissions from "expo-permissions";
import axios from "axios";
import { AsyncStorage } from "react-native";
//import registerForPushNotificationsAsync from "../Hooks/registerForPushNotifications";
import Constants from "expo-constants";
const UserContext = React.createContext();
export default UserContext;
const IS_SIGNEDIN = "is_signed_in";
export const UserProvider = ({ children }) => {
const [isSignedIn, setSignIn] = useState(null);
const [didAuthenticate, setAuthenticated] = useState(null);
const check_and_set_signin_status = async () => {
const signed_in = await AsyncStorage.getItem(IS_SIGNEDIN);
if (signed_in == null || signed_in == "false") {
await AsyncStorage.setItem(IS_SIGNEDIN, "false");
setSignIn("false");
} else {
setSignIn("true");
}
};
return (
<UserContext.Provider
value={{
isSignedIn, // well use this for conditional rendering
check_and_set_signin_status,
}}
>
{children}
</UserContext.Provider>
);
};
App.js Code :
const App = () => {
const { isSignedIn, check_and_set_signin_status } = useContext(UserContext); //<-- causes crash
console.log( isSignedIn, check_and_set_signin_status ,"useContext")
return isSignedIn === "false" ? (
<UserMenu />
) : (
<Non_user_stack></Non_user_stack>
);
};
const jsx = () => (
<UserProvider>
<App />
</UserProvider>
);
export default jsx;
In my case I imported badly
import ThemeContext from '../contexts/theme-context';
Instead
import { ThemeContext } from '../contexts/theme-context';
You should always check what you're exporting from your context folder
In my case I import { LocalContext } from ".././services/location/location.context";
instead of import { LocationContext } from ".././services/location/location.context";
Mine was:
I mistakenly imported the file (languageContext.js) instead of its function {LanguageContext} so now it goes like this where I called my context.
import {LanguageContext} from "../../Context/languageContext";
const { language } = useContext(languageContext);
i18n.locale = language;
Related
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.
i want to show a screen depending on a state.
So when I click on the bottom left tab, if there is a valid user, I want to be redirected to the UserProfile-screen, else redirect to the LoginScreen.
The react-native navigation confuses me, i just cant see whats wrong.
So in LoginRoutes.tsx I just try to change this behaviour by using true / false
Thanks in Advance.
What I have so far:
BottomTabBar.tsx:
export const BottomTabNavigator = () => {
const colorScheme = useColorScheme();
return (
<Tab.Navigator
screenOptions={{
tabBarShowLabel: true,
tabBarStyle: {
backgroundColor: "#292929",
borderTopWidth:0
},
tabBarInactiveTintColor: "#919191",
tabBarInactiveBackgroundColor: "#292929",
tabBarActiveTintColor: Colors[colorScheme].text,
headerShown: false,
}}
>
<Tab.Screen
name="LoginRoutes"
component={LoginRoutes}
options={{ title: "Login", headerShown: false}}
/>
<Tab.Screen
name="SettingsTabScreen"
component={SettingsTabScreen}
options={{ headerShown: false,title:"test" }}
/>
</Tab.Navigator>
);
};
LoginRoutes.tsx
import * as React from "react";
import { ActivityIndicator, View, Text } from "react-native";
import AsyncStorage from "#react-native-async-storage/async-storage";
import { createStackNavigator } from "#react-navigation/stack";
import NavigationContainer from "./UserProfileStack";
import LoginScreen from "../Screens/LoginScreen";
import UserProfile from "../components/UserProfile";
import Colors from "../constants/Colors";
import { UserProfileInfo } from "../constants/Types";
function LoginRoutes({ navigation }: { navigation: any }) {
const [loading, setLoading] = React.useState(true);
const [user, setUser] = React.useState(null);
const Stack = createStackNavigator();
React.useEffect(() => {
// check if the user is logged in or not
AsyncStorage.getItem("user")
.then((userString) => {
if (userString) {
console.log("-----------EXISTING------------");
console.log(JSON.parse(userString).id);
setUser(JSON.parse(userString));
} else {
console.log("not logged in, showing LoginPage");
}
setLoading(false);
})
.catch((err) => {
console.log(err);
setLoading(false);
});
}, []);
if (loading) {
return (
<View
style={{
height: "100%",
justifyContent: "center",
backgroundColor: Colors.dark.background,
}}
>
<ActivityIndicator
size="large"
style={{ backgroundColor: Colors.dark.background }}
/>
<Text
style={{
color: Colors.dark.text,
marginTop: 10,
alignSelf: "center",
}}
>
retrieving userdata...
</Text>
</View>
);
}
return (
<NavigationContainer>
<Stack.Navigator>
{true ? (
<Stack.Screen name="LoginScreen" component={LoginScreen} />
) : (
<Stack.Screen name="UserProfile" component={UserProfile} />
)}
</Stack.Navigator>
</NavigationContainer>
);
}
export default LoginRoutes;
The stackNavigator:
import { createStackNavigator } from "react-navigation-stack";
import { createAppContainer } from "react-navigation";
import LoginScreen from "../Screens/LoginScreen";
import UserProfile from "../components/UserProfile";
import { UserProfileInfo } from "../constants/Types";
import { StackNavigationProp } from '#react-navigation/stack';
export type UserProfileStackParams = {
LoginScreen: undefined,
UserProfile: { profileInfo: UserProfileInfo };
};
const screens = {
LoginScreen: {
screen: LoginScreen,
navigationOptions: {headerShown: false,gestureEnabled:false},
},
UserProfile: {
screen: UserProfile,
navigationOptions: {headerShown: false,gestureEnabled:false},
},
};
// home stack navigator screens
const UserProfileStack = createStackNavigator(screens);
export default createAppContainer(UserProfileStack);
UserProfile.tsx
type Props = {
navigation: StackNavigationProp<UserProfileStackParams, "UserProfile">
loggedInUser: {}
};
const DATA = [
{
// contains valid data
},
];
export const UserProfile: React.FC<Props> = ({ navigation}) => {
const [steamID, setSteamID] = React.useState({ id: null, watchLists: null });
const [profileInfo, setProfileInfo] = React.useState<UserProfileInfo>(null);
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
// check if the user is logged in or not
AsyncStorage.getItem("user")
.then((userString) => {
if (userString) {
console.log("logged in.");
setSteamID(JSON.parse(userString));
fetchUserProfile(steamID.id).then((response) => {
setProfileInfo(response);
});
} else {
console.log("not logged in, showing LoginPage");
}
setLoading(false);
})
.catch((err) => {
console.log(err);
setLoading(false);
});
}, []);
return (
<>
<Button
title="btn"
onPress={() => {
navigation.navigate("LoginScreen")
}}
></Button>
export default UserProfile;
Inside the BottomTabNavigator you can check if the user is logged in. You can get the user in the same way you're getting it in UserProfile.tsx file.
export const BottomTabNavigator = () => {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
// check for user
}, []);
return (
<Tab.Navigator
screenOptions={{ ... }}
>
<Tab.Screen
name="LoginRoutes"
component={user ? UserScreen : LoginScreen}
/>
</Tab.Navigator>
);
};
Alternatively, you can look into getting the user through Context Provider so that you don't have to check the storage every time you want to see if the user is logged in.
Read more about that here:
How To Manage User State with React Context
React Context
I am trying to implement Auth flow using useContext
When I click the button on SignInScreen the state inside that screen changes and rerenders itself, showing token string under a button. Unfortunatelly App is still showing Signin screen, it doesnt detect that the state has changed.
App.tsx:
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import HomeScreen from './screens/HomeScreen';
import ProfileScreen from './screens/ProfileScreen';
import SignUpScreen from './screens/SignUpScreen';
import SignInScreen from './screens/SignInScreen';
import { AuthProvider, useAuth } from './context/AuthContext';
const Stack = createNativeStackNavigator();
export default function App() {
const { token } = useAuth();
return (
<AuthProvider>
<SafeAreaProvider>
<NavigationContainer>
<Stack.Navigator>
{token === null ? (
<>
<Stack.Screen name="Signin" component={SignInScreen} />
<Stack.Screen name="Signup" component={SignUpScreen} />
</>
) : (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</>
)}
</Stack.Navigator>
</NavigationContainer>
</SafeAreaProvider>
</AuthProvider>
);
}
AuthContext.tsx:
import * as React from 'react'
interface AuthState {
token: string | null
isLoading: boolean
publicAddress: string | null
}
type AuthAction = { type: 'SIGN_IN'; token: string } | { type: 'SIGN_OUT' }
type AuthPayload = string
interface AuthContextActions {
signIn: (data: AuthPayload) => void
signOut: () => void
}
interface AuthContextType extends AuthState, AuthContextActions {}
const AuthContext = React.createContext<AuthContextType>({
token: null,
isLoading: true,
publicAddress: null,
// eslint-disable-next-line #typescript-eslint/no-empty-function
signIn: () => {},
// eslint-disable-next-line #typescript-eslint/no-empty-function
signOut: () => {},
})
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const [state, dispatch] = React.useReducer(AuthReducer, {
token: null,
isLoading: true,
publicAddress: null,
})
const authActions: AuthContextActions = React.useMemo(
() => ({
signIn: async (token: string) => {
dispatch({ type: 'SIGN_IN', token })
console.log(state)
//await setToken(token) //TODO:
},
signOut: async () => {
//await removeToken() // TODO: use Vars
dispatch({ type: 'SIGN_OUT' })
},
}),
[]
)
return (
<AuthContext.Provider value={{ ...state, ...authActions }}>
{children}
</AuthContext.Provider>
)
}
const AuthReducer = (prevState: AuthState, action: AuthAction): AuthState => {
switch (action.type) {
case 'SIGN_IN':
console.log(action.token)
return {
...prevState,
token: action.token, //TODO:
isLoading: false
}
case 'SIGN_OUT':
return {
...prevState,
isLoading: false,
token: null
}
}
}
export const useAuth = (): AuthContextType => {
const context = React.useContext(AuthContext)
if (!context) {
throw new Error('useAuth must be inside an AuthProvider with a value')
}
return context
}
SignInScreen.tsx:
import React from "react";
import {View, Text, Button} from 'react-native'
import { useAuth } from "../context/AuthContext";
export default function SignInScreen(){
const {signIn, token} = useAuth();
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button title="Login with metamask" onPress={() => {
console.log('pressed')
signIn('token')
}}/>
<Text>{token}</Text>
</View>
);
}
I am trying to pass a parameter from a screen to the Tab Navigator. In this I want to use it. Following the corresponding lines from my App.tsx:
const Tab = createBottomTabNavigator();
const AppStack = () => {
const [chatcount, setChatcount] = React.useState(0);
...
chatbadge = (number) => {
setChatcount(number)
};
...
return(
<Tab.Navigator
initialRouteName="..."
tabBarOptions={{
activeTintColor: 'rgba(255,0,35, 1)',
inactiveTintColor: 'rgba(255,0,35, 0.7)',
inactiveBackgroundColor: 'rgb(255, 235, 238)',
activeBackgroundColor: 'rgb(255, 235, 238)',
style: {backgroundColor: 'rgb(255, 235, 238)'},
}}
>
...
<Tab.Screen name="Chats" component={() => <Chats chatbadge={this.chatbadge}/>}/>
...
</Tab.Navigator>
)
};
const App = () => {
return(
<ActionSheetProvider>
<AuthContext.Provider value={authContext} >
<NavigationContainer>
<AppStack/>
</NavigationContainer>
</AuthContext.Provider>
</ActionSheetProvider>
);
}
export default App;
Now I want to set the chatcount in my chats screen /Chats.tsx:
import * as React from "react";
import { Image, StyleSheet, View, Text, FlatList, TouchableOpacity, AsyncStorage, Platform, TextInput, SafeAreaView } from "react-native";
...
import { createStackNavigator } from '#react-navigation/stack';
import { NavigationContainer } from '#react-navigation/native';
...
import Chat from "../screens/Chat";
...
const Stack = createStackNavigator();
function NavStack() {
return (
<Stack.Navigator screenOptions={{ headerShown: false}}>
<Stack.Screen name="Chats" component={Chats}/>
<Stack.Screen name="Chat" component={Chat}/>
...
</Stack.Navigator>
);
}
class Chats extends React.Component<{}, State> {
constructor(props) {
super(props);
this._isMounted = false;
}
state = {
data: [],
value: ""
}
componentDidMount() {
this._isMounted = true;
this._isMounted && this.fetchData()
this._isMounted && setInterval(() => this.fetchData(), 1000);
}
componentWillUnmount(){
this._isMounted = false;
}
fetchData = async () => {
if(this._isMounted){
const response = await fetch('...', {
....
});
const json = await response.json();
this.setState({data: json});
this.props.chatbadge(json)
}
}
...
render(){
...
const {navigation} = this.props
return(
<SafeAreaView style={styles.container}>
...
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
...
});
this.props.chatbadge(json) shows an error that this.props.chatbadge is not defined. console.log(this.props.chatbadge) displayes "undefined".
I would be very happy about help.
Best regards
Lukas
I am making a react-native application.
I am trying to find out how can I show or hide the login component on the basis of a token stored. If the token is present in the storage, it would skip the login screen to the dashboard.
Else it will show login screen.
Here is the code to the app.
import React, { useState } from 'react';
import { StyleSheet, Text, View, Button, AsyncStorage } from 'react-native';
import Login from './components/Login';
import Dashboard from './components/Dashboard';
export default function App() {
var [ token, setToken ] = useState('');
function loginHandler(recievedToken) {
setToken(recievedToken);
}
async function _readData() {
try {
const value = await AsyncStorage.getItem('Token');
console.log(value);
if (value !== null) {
// We have data!!
loginHandler(value)
} else {
loginHandler(null);
}
} catch (error) {
// Error retrieving data
}
};
return (
<View>
<Login getToken={_readData()} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
You can put conditional statement in your code.
Try this code out.
import React, { useState } from 'react';
import { StyleSheet, Text, View, Button, AsyncStorage, ActivityIndictor } from 'react-native';
import Login from './components/Login';
import Dashboard from './components/Dashboard';
export default function App() {
var [ token, setToken ] = useState('');
var [ loading, setLoading ] = useState(true);
function loginHandler(recievedToken) {
setToken(recievedToken);
setLoading(false);
}
async function _readData() {
try {
const value = await AsyncStorage.getItem('Token');
console.log(value);
if (value !== null) {
// We have data!!
loginHandler(value)
} else {
loginHandler(null);
}
} catch (error) {
// Error retrieving data
}
};
if( loading ) {
<ActivityIndictor/>
}
if(token == null){
return (
<View>
<Login getToken={_readData()} />
</View>
);
} else {
return (
<View>
<Dashboard />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Let me know if some error or correction.
Here AuthStack is Login Screen and BottomNavigation is DashBoard.
import React, { useEffect, useState } from "react";
import { NavigationContainer } from "#react-navigation/native";
import AuthStackScreen from "./routes/AuthStack";
import BottomNavigation from "./routes/BottomNavigator";
import firebase from "./config/firebase";
export default App = () => {
const [isLogin, setIsLogin] = useState(false);
const checkLoginIn = () => {
firebase.auth().onAuthStateChanged(user => {
if (user) {
setIsLogin(true);
} else {
setIsLogin(false);
}
});
};
useEffect(() => {
checkLoginIn();
}, []);
return (
<NavigationContainer>
{isLogin ? <BottomNavigation /> : <AuthStackScreen />}
</NavigationContainer>
);
};
In my case
I just used initialRouteName
import React, {Component} from 'react';
import {NavigationContainer} from '#react-navigation/native';
import {createNativeStackNavigator} from '#react-navigation/native-stack';
import AsyncStorage from '#react-native-async-storage/async-storage';
import Home from './components/Home';
import Login from './components/Login';
const Stack = createNativeStackNavigator();
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
routeName: 'Login',
};
}
async componentDidMount() {
const token = JSON.parse(await AsyncStorage.getItem('token'));
if (token !== null) {
this.setState({
routeName: 'Bluetooth',
});
}
SplashScreen.hide();
}
render() {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName={this.state.routeName}
screenOptions={{
headerShown: false,
}}>
<>
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Home" component={Home} />
</>
</Stack.Navigator>
</NavigationContainer>
);
}
}