TypeError: undefined is not an object (evaluating 'loginState.isLoading') - react-native

I am trying to authenticate a login using Async Storage with React Native. Every time I click on the SignUp button in my simulator it throws me a type error saying "TypeError: undefined is not an object (evaluating 'loginState.isLoading')". I believe the problem is how I'm defining loginState. Here is the page where I define loginState below:
import 'react-native-gesture-handler';
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet,Component, Text,TouchableWithoutFeedback, View,Image,SafeAreaView, Button, ScrollView, ActivityIndicator } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createDrawerNavigator } from '#react-navigation/drawer';
import MainTabScreen from './screens/MainTab';
import {DrawerContent} from './screens/DrawerContent';
import RootStackScreen from './screens/RootStack'
import { useEffect } from 'react';
//import { firebaseConfig } from './screens/config';
import { AuthContext } from './components/context';
import AsyncStorage from '#react-native-async-storage/async-storage';
const Drawer = createDrawerNavigator();
function App() {
// const [isLoading, setIsLoading] = React.useState(true);
// const [userToken, setUserToken] = React.useState(null);
const initialLoginState = {
isLoading: true,
userName: null,
userToken: null,
};
const loginReducer = (prevState, action) => {
switch( action.type ) {
case 'RETRIEVE_TOKEN':
return {
...prevState,
userToken: action.token,
isLoading: false,
};
case 'LOGIN':
return {
...prevState,
userName: action.id,
userToken: action.token,
isLoading: false,
};
case 'LOGOUT':
return {
...prevState,
userName: null,
userToken: null,
isLoading: false,
};
case 'REGISTER':
return {
...prevState,
userName: action.id,
userToken: action.token,
isLoading: false,
};
}
};
const [loginState, dispatch] = React.useReducer(loginReducer, initialLoginState);
const authContext = React.useMemo(() => ({
signIn: (userName,password) =>{
// setUserToken('tony');
// setIsLoading(false);
let userToken;
userToken = null;
if (userName == 'user' && password == 'pass'){
userToken = 'lexi';
}
dispatch({type:'LOGIN,',id:userName, token:userToken});
},
signOut: () => {
// setUserToken(null);
// setIsLoading(false);
dispatch({type:'LOGOUT'});
},
signUp: () => {
setUserToken('tony');
setIsLoading(false);
},
}),[])
useEffect(() => {
setTimeout(() => {
// setIsLoading(false);
dispatch({type:'RETRIEVE_TOKEN',token:'mario'});
},1000);
},[]);
if (loginState.isLoading == true)
{
return(
<View style = {{flex:1,justifyContent:'center',alignItems:'center'}}>
<ActivityIndicator size = "large"/>
</View>
);
}
return (
<AuthContext.Provider value = {authContext}>
<NavigationContainer>
{loginState.userToken !== null ? (
<Drawer.Navigator drawerContent = {props => <DrawerContent {... props}/>}>
<Drawer.Screen name="HomeDrawer" component={MainTabScreen} />
</Drawer.Navigator>
)
:
<RootStackScreen/>
}
</NavigationContainer>
</AuthContext.Provider>
);
}
export default App;

Found the error. I changed:
dispatch({type:'LOGIN,',id:userName, token:userToken});
To:
dispatch({type:'LOGIN',id:userName, token:userToken});

Related

Move code for React-Context to a dedicated file and using it

I followed the tutorial from react-navigation for the authentication context:
https://reactnavigation.org/docs/auth-flow/
But now i want to move my context out of the App.js in something like AuthContext.js
but i dont succeed in using it in App.js, but there is no tutorial for i tried multiple times but it always break something, here's my code:
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
import { NavigationContainer } from "#react-navigation/native";
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import * as SecureStore from 'expo-secure-store';
import * as React from 'react';
import SignInScreen from "./screens/SignInScreen";
import SignUpScreen from "./screens/SignUpScreen";
import HomeScreen from "./screens/HomeScreen";
import SplashScreen from "./screens/SplashScreen";
import { getTokens } from "./services/AuthServices";
import "react-native-gesture-handler";
const Stack = createBottomTabNavigator();
export const AuthContext = React.createContext();
export default function App({ navigation }) {
const [state, dispatch] = React.useReducer(
(prevState, action) => {
switch (action.type) {
case 'RESTORE_TOKEN':
return {
...prevState,
accessToken: action.accesToken,
isLoading: false,
};
case 'SIGN_IN':
return {
...prevState,
isSignout: false,
isSignedIn: true,
accessToken: action.token,
};
case 'SIGN_OUT':
const deleteTokens = async () => {
try {
await SecureStore.deleteItemAsync("access_token");
await SecureStore.deleteItemAsync("refresh_token");
} catch (error) {
console.log(error);
}
};
deleteTokens();
return {
...prevState,
isSignout: true,
accessToken: null,
};
}
},
{
isLoading: true,
isSignout: false,
isSignedIn: false,
accessToken: null,
refreshToken: null
}
);
React.useEffect(() => {
// Fetch the token from storage then navigate to our appropriate place
const bootstrapAsync = async () => {
let accessToken;
try {
accessToken = await SecureStore.getItemAsync('access_token');
} catch (e) {
console.log(e)
}
// After restoring token, we may need to validate it in production apps
// This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away.
dispatch({ type: 'RESTORE_TOKEN', accessToken: accessToken });
};
bootstrapAsync();
}, []);
const authContext = React.useMemo(
() => ({
signIn: async (data) => {
let tokens = await getTokens(data);
if (tokens.access_token && tokens.refresh_token) {
try {
await SecureStore.setItemAsync("access_token", tokens.access_token);
await SecureStore.setItemAsync("refresh_token", tokens.refresh_token);
} catch (error) {
console.log(error);
}
dispatch({ type: 'SIGN_IN', token: tokens.access_token });
}
console.log(tokens);
},
signOut: () => dispatch({ type: 'SIGN_OUT' }),
signUp: async (data) => {
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
}),
[]
);
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer>
<Stack.Navigator>
{state.isLoading ? (
<Stack.Screen
name="Splash"
component={SplashScreen}
options={{ title: "Splash" }}
/>
) : state.accessToken != null ? (
<>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: "Home" }}
/>
</>
) : (
<>
<Stack.Screen
name="SignIn"
component={SignInScreen}
options={{ title: "SignIn" }}
/>
<Stack.Screen
name="SignUp"
component={SignUpScreen}
options={{ title: "SignUp" }}
/>
</>
)}
</Stack.Navigator>
</NavigationContainer>
</AuthContext.Provider>
);
}

i can't connect redux with react-native app

I have a problem with redux and react-native because I can't connect the state of the "login" view.
I would like to upload the user data and the token in the store.
In "AppState" View, I manage the actions :
import * as type from "../redux/actionTypes";
import {initialState} from "../redux/initialState";
export default function LoginStateReducer(state = initialState, action) {
switch( action.type ) {
case type.SET_LOGIN_STATE:
return {
...state,
user: action.user,
token: action.token,
isLoading: false,
isLoggedIn: true,
};
case type.SET_LOGOUT_STATE:
return {
...state,
user: null,
token: null,
isLoading: false,
isLoggedIn: false,
};
default:
return state;
}
};
In "AppView", I manage the state :
import React, { useState, useEffect, useReducer, useMemo } from 'react';
import { View, ActivityIndicator } from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
import Login from './login/LoginViewContainer';
import { AuthContext } from './config/Context';
import * as type from '../redux/actionTypes'
import { initialState } from '../redux/initialState';
import LoginStateReducer from './AppState';
import Navigator from './navigation/Navigator';
const App = () => {
const [isLoading, setIsLoading] = useState(true);
const [loginState, dispatch] = useReducer(LoginStateReducer, initialState);
const authContext = useMemo(() => ({
signIn: async(data) => {
setIsLoading(false);
const token = String(data.token);
const user= data.user;
try {
await AsyncStorage .setItem('token', token);
} catch(e) {
console.log(e);
}
dispatch({ type: type.SET_LOGIN_STATE, user: user, token: token });
},
signOut: async() => {
setIsLoading(false);
try {
await AsyncStorage .removeItem('token');
} catch(e) {
console.log(e);
}
dispatch({ type: type.SET_LOGOUT_STATE });
},
}), []);
if( loginState.isLoading ) {
return(
<View style={{flex:1,justifyContent:'center',alignItems:'center'}}>
<ActivityIndicator size="large" />
</View>
);
}
return (
<AuthContext.Provider value={authContext}>
{ loginState.token !== null ? (
<Navigator onNavigationStateChange={() => {}} uriPrefix="/app" />
)
:
<Login />
}
</AuthContext.Provider>
);
}
export default App;
In "AppViewContainer", I connect the reducer with the state :
import { connect } from 'react-redux';
import { compose, lifecycle } from 'recompose';
import { Platform, UIManager } from 'react-native';
import AppView from './AppView';
import LoginStateReducer from './AppState';
export default compose(
connect(
state => ({
user: state.user,
token: state.token,
}),
dispatch => ({
LoginStateReducer: () => dispatch(LoginStateReducer()),
})
),
lifecycle({
componentDidMount() {
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental &&
UIManager.setLayoutAnimationEnabledExperimental(true);
}
this.props.LoginStateReducer();
},
}),
)(AppView);
In the "reducer", I have :
import { combineReducers } from 'redux';
// ## Generator Reducer Imports
import LoginStateReducer from '../modules/AppState';
export default combineReducers({
login: LoginStateReducer,
});
When I call the store once connected, it has not changed and I don't understand why :
"login": {"isLoading": true, "isLoggedIn": false, "token": "", "user": ""}
Can you tell me if i am missing something?
Thank you in advance for your feedback

React context undefined

I have a basic authentication form created that works when I have all the code within the App.js file.
However, when I attempt to refactor the pages into separate files, the context is throwing an exception of it being empty.
App.js
import 'react-native-gesture-handler';
import * as React from 'react';
import { Button, Text, TextInput, View, StyleSheet } from 'react-native';
import AsyncStorage from '#react-native-community/async-storage';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import AuthContext from './src/utils/AuthContext';
import HomeScreen from './src/components/HomeScreen';
import SignInScreen from './src/components/SignInScreen';
import SplashScreen from './src/components/SplashScreen';
const Stack = createStackNavigator();
export default function App() {
const [state, dispatch] = React.useReducer(
(prevState, action) => {
switch (action.type) {
case 'RESTORE_TOKEN':
return {
...prevState,
userToken: action.token,
isLoading: false,
};
case 'SIGN_IN':
return {
...prevState,
isSignout: false,
userToken: action.token,
};
case 'SIGN_OUT':
return {
...prevState,
isSignout: true,
userToken: null,
};
}
},
{
isLoading: true,
isSignout: false,
userToken: null,
}
);
React.useEffect(() => {
// Fetch the token from storage then navigate to our appropriate place
const bootstrapAsync = async () => {
let userToken;
try {
userToken = await AsyncStorage.getItem('userToken');
} catch (e) {
// Restoring token failed
console.log("Restoring token failed" + e);
}
dispatch({ type: 'RESTORE_TOKEN', token: userToken });
};
bootstrapAsync();
}, []);
const authContext = React.useMemo(
() => ({
signIn: async data => {
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
signUp: async data => {
dispatch({ type: 'TO_SIGNUP_PAGE' });
},
signOut: () =>
dispatch({ type: 'SIGN_OUT' }),
}),
[]
);
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer>
<Stack.Navigator>
{state.isLoading ? (
// We haven't finished checking for the token yet
<Stack.Screen name="Splash" component={SplashScreen} />
) : state.userToken == null ? (
// No token found, user isn't signed in
<Stack.Screen name="SignIn" component={SignInScreen} options={{ title: 'Sign in', animationTypeForReplace: state.isSignout ? 'pop' : 'push',}} />
) : (
// User is signed in
<Stack.Screen name="Home" component={HomeScreen} />
)}
</Stack.Navigator>
</NavigationContainer>
</AuthContext.Provider>
);
}
AuthContext.js
import { createContext } from 'react';
const AuthContext = createContext();
export default AuthContext;
HomeScreen.js
import React, { useContext } from 'react';
import { View, Text, TextInput, Button } from 'react-native';
import AuthContext from '../utils/AuthContext';
const HomeScreen = () => {
const { signOut } = useContext(AuthContext);
return (
<AuthContext>
<Text>Signed in!</Text>
<Button title="Sign out" onPress={signOut} />
</AuthContext>
);
}
export default HomeScreen;
Error:
I have spent many hours on this and looked through countless tutorials, for the life of me I can't figure out what I'm missing...
You are not exporting the context. Your App.js file exports the App component but you are trying to access the AuthContext from that file.
The best approach to take is to place the context in a separate file and import it to both App.js and HomeScreen.js
Your context file should look like this
import { createContext } from 'react';
const AppContext = createContext();
export default AppContext;
And you can import in other files like below
import AppContext from './AppContext';

Unrecognized font family 'entypo'

I'm using the create react native app by the expo team to build an app. Using Icon component from react-native-elements to create a react navigation header feature. Snippet below:
const Navigator = new createStackNavigator({
Home: {
screen: Home,
path: '/',
navigationOptions: ({ navigation }) => ({
title: 'Home',
headerStyle: {
backgroundColor: 'black'
},
headerLeft: (
<Icon
name="menu"
size={30}
type="entypo"
style={{ paddingLeft: 10 }}
/>
),
}),
},
})
I encountered this error:
After numerous iterations, I found this supposed work around 1st and 2nd by the expo team and implemented it this way below for the app but still encountering the same problems.
import Expo from "expo";
import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { AppLoading, Asset, Font } from 'expo';
import { FontAwesome, Ionicons } from '#expo/vector-icons';
import { connect } from 'react-redux'
import { Auth } from 'aws-amplify';
import AuthTabs from './auth/Tabs';
import Nav from './navs/Navigator';
import Home from "./components/Home";
class App extends React.Component {
state = {
user: {},
isLoading: true,
isLoadingComplete: false,
};
async componentDidMount() {
StatusBar.setHidden(true)
try {
const user = await Auth.currentAuthenticatedUser()
this.setState({ user, isLoading: false })
} catch (err) {
this.setState({ isLoading: false })
}
}
async componentWillReceiveProps(nextProps) {
try {
const user = await Auth.currentAuthenticatedUser()
this.setState({ user })
} catch (err) {
this.setState({ user: {} })
}
}
render() {
if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
return(
<AppLoading
startAsync={this._loadResourcesAsync}
onError={this._handleLoadingError}
onFinish={this._handleFinishLoading}
/>
);
}
else{
if (this.state.isLoading) return null
let loggedIn = false
if (this.state.user.username) {
loggedIn = true
}
if (loggedIn) {
return (
<Nav />
)
}
return (
<AuthTabs />
)
}
}
_loadResourcesAsync = async () => {
console.log("fonts loading..")
const entypoFont = {
'entypo': require('../node_modules/#expo/vector-icons/fonts/Entypo.ttf')
};
const fontAssets = cacheFonts([ FontAwesome.font, Ionicons.font, entypoFont ]);
console.log("loaded all fonts locally")
await Promise.all([...fontAssets]);
console.log("promisified all fonts")
};
_handleLoadingError = error => {
console.warn(error);
};
_handleFinishLoading = () => {
this.setState({ isLoadingComplete: true });
};
}
function cacheFonts(fonts){
return fonts.map(font => Font.loadAsync(font))
}
const mapStateToProps = state => ({
auth: state.auth
})
export default connect(mapStateToProps)(App)
What are my doing wrong and how can it be configured appropriately? Thank you

React-Native/React navigation redirection after login with redux

I have a problem during the login my redirection is not done, I use a rail API for the back, I can recover the user, but when I click on login the page reloads and does not navigate on the home screen.
In my AuthActions.js after the dispatch I added NavigationActions.navigate ('Home'), this should allow me to go to the home page
AppNavigator.js
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { addNavigationHelpers, StackNavigator, TabNavigator } from 'react-navigation';
import { addListener } from '../../utils/redux';
import LoginScreen from '../login/LoginForm';
import RegisterScreen from '../register/RegisterForm';
import LogoutScreen from '../logout/Logout';
import Home from '../home/HomeList';
import Hair from '../hair/HairList';
export const Tabs = TabNavigator({
screen1: {
screen: Home
}
});
export const AppNavigator = StackNavigator({
Login: { screen: LoginScreen },
Main: { screen: Home },
Hair: { screen: Hair },
},{
initialRouteName : 'Login'
});
class AppWithNavigationState extends React.Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
nav: PropTypes.object.isRequired,
};
render() {
const { dispatch, nav } = this.props;
return (
<AppNavigator
navigation={addNavigationHelpers({
dispatch,
state: nav,
addListener,
})}
/>
);
}
}
const mapStateToProps = state => ({
nav: state.nav,
});
export default connect(mapStateToProps)(AppWithNavigationState);
AuthReducers.js
import {
EMAIL_CHANGED,
PASSWORD_CHANGED,
LOGIN_USER_SUCCESS,
LOGIN_USER_FAIL,
LOGIN_USER
} from '../actions/types';
const INITIAL_STATE = {
email: '',
password: '',
user: null,
error: '',
loading: false,
};
export default (state = INITIAL_STATE, action) => {
// console.log('action =>', action);
switch (action.type) {
case EMAIL_CHANGED:
return { ...state, email: action.payload };
case PASSWORD_CHANGED:
return { ...state, password: action.payload };
case LOGIN_USER:
return { ...state, loading: true, error: '' };
case LOGIN_USER_SUCCESS:
return { ...state, ...INITIAL_STATE, user: action.payload };
case LOGIN_USER_FAIL:
return { ...state, error: 'Authentication Failed.', password: '', loading: false };
default:
return state;
}
};
// newState === oldState ? nothing : update state, so component too
AuthActions.js
import { NavigationActions } from 'react-navigation';
import Api from '../api';
import User from '../models/User';
import {
EMAIL_CHANGED,
PASSWORD_CHANGED,
LOGIN_USER_SUCCESS,
LOGIN_USER_FAIL,
LOGIN_USER
} from './types';
export const emailChanged = (text) => {
return {
type: EMAIL_CHANGED,
payload: text
};
};
export const passwordChanged = (text) => {
return {
type: PASSWORD_CHANGED,
payload: text
};
};
export const loginUser = ({ email, password }) => {
return (dispatch) => {
dispatch({ type: LOGIN_USER });
Api.userSignUp(email, password)
.then((user) => loginUserSuccess(dispatch, user))
.catch((error) => {
console.log(error);
loginUserFail(dispatch);
});
};
};
const loginUserFail = (dispatch) => {
dispatch({
type: LOGIN_USER_FAIL
});
};
const loginUserSuccess = (dispatch, user) => {
// console.log('access token =>', User.getAccessToken());
// Api.createEmployee(null);
dispatch({
type: LOGIN_USER_SUCCESS,
payload: user,
});
NavigationActions.navigate('home'); //HERE NOT REDIRECT AFTER SUCCESS TO HOME
};
LoginForm.js
import React, { Component } from 'react';
import { Text } from 'react-native';
import { connect } from 'react-redux';
import { emailChanged, passwordChanged, loginUser } from '../../actions';
import { Card, CardSection, Input, Button, Spinner } from '../common';
class LoginForm extends Component {
onEmailChange(text) {
this.props.emailChanged(text);
}
onPasswordChange(text) {
this.props.passwordChanged(text);
}
onButtonPress() {
const { email, password } = this.props;
this.props.loginUser({ email, password });
}
renderButton() {
if (this.props.loading) {
return <Spinner size="large" />;
}
return (
<Button onPress={this.onButtonPress.bind(this)}>
Login
</Button>
);
}
render() {
return (
<Card>
<CardSection>
<Input
label="Email"
placeholder="email#gmail.com"
onChangeText={this.onEmailChange.bind(this)}
value={this.props.email}
/>
</CardSection>
<CardSection>
<Input
secureTextEntry
label="Password"
placeholder="password"
onChangeText={this.onPasswordChange.bind(this)}
value={this.props.password}
/>
</CardSection>
<Text style={styles.errorTextStyle}>
{this.props.error}
</Text>
<CardSection>
{this.renderButton()}
</CardSection>
</Card>
);
}
}
const styles = {
errorTextStyle: {
fontSize: 20,
alignSelf: 'center',
color: 'red'
}
};
const mapStateToProps = ({ auth }) => {
const { email, password, error, loading } = auth;
return { email, password, error, loading };
};
export default connect(mapStateToProps, {
emailChanged, passwordChanged, loginUser
})(LoginForm);
App.js
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import AppReducer from './reducers';
import AppWithNavigationState from './components/navigator/AppNavigator';
import { middleware } from './utils/redux';
import User from './models/User.js';
import {
NavigationActions,
} from 'react-navigation';
const store = createStore(
AppReducer,
applyMiddleware(ReduxThunk),
);
class App extends Component {
state = { isLoggedIn: null }
componentWillMount() {
// Si le asyncstorage a changé (connexion, etc)
User.getCurrent().then((user) => {
console.log('GetCurrentUser =>', user);
if (user) {
this.setState({isLoggedIn: true});
this.props.navigation.navigate('Main', user);
}
else {
this.setState({isLoggedIn: false});
}
})
}
render() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
);
}
}
export default App;
I'm blocking for some day on the worries, I still is not finding a solution
use this code NavigationActions.NAVIGATE("home") works for me
You have to use componentWillReceiveProps in your Home like this:
//Home
componentWillReceiveProps(nextProps) {
if(nextProps.user) {
this.props.navigation.navigate('Home');
}
}
LoginForm.js
const mapStateToProps = state => {
return {
error: state.auth.error,
loading: state.auth.loading,
user: state.auth.user,
}
}
And you edit your AuthReducers.js like this:
case LOGIN_USER_SUCCESS:
return { ...state, ...INITIAL_STATE, user: action.user};
It works for me!