Logging out of react app results in nullpointer in homescreen - react-native

I'm working on a react application with a login page. When the user logs in successfully, the backend returns a user-object (JSON), containing some user properties (id, username, ...) aswel as JWT token. The user-object gets stored in AsyncStorage (so the user only has to log in once).
try {
await AsyncStorage.setItem('authUser', JSON.stringify(data));
} catch (e) {
console.log(e.message);
}
dispatch({ type: 'SIGN_IN', authUser:data});
The state :
const [state, dispatch] = React.useReducer(
(prevState, action) => {
switch (action.type) {
case 'RESTORE_TOKEN':
return {
...prevState,
authUser: action.authUser,
isLoading: false,
};
case 'SIGN_IN':
return {
...prevState,
isSignout: false,
authUser: action.authUser,
};
case 'SIGN_OUT':
return {
...prevState,
isSignout: true,
authUser: null,
};
}
},
{
isLoading: true,
isSignout: false,
authUser: "",
}
);
The second time i retrieve the user-object from the AsyncStorage and pass it to the state.
try {
authUser = await AsyncStorage.getItem('authUser');
} catch (e) {
console.log(e.message);
}
dispatch({ type: 'RESTORE_TOKEN', authUser: JSON.parse(authUser) });
I use a UserContext to pass the authUser to my welcome screen :
<AuthContext.Provider value={authContext}>
<UserContext.Provider value={{authUser: state.authUser }}>
<NavigationContainer>
<RootStack.Navigator mode="modal" headerMode="none">
{state.isLoading ? (
<RootStack.Screen name="Splash" component={SplashScreen} />
) : state.authUser == null ? (
<RootStack.Screen name="Login" component={AuthenticationStackScreen} />
) : (
<RootStack.Screen name="Home" component={HomeScreen} />
)}
</RootStack.Navigator>
</NavigationContainer>
</UserContext.Provider>
</AuthContext.Provider>
In my homescreen I print a welcome message containing the username and a signOut button :
function HomeScreen(props) {
const { signOut } = React.useContext(AuthContext);
const { authUser } = React.useContext(UserContext);
return (
<View style={{ flex:1, alignItems: 'center', justifyContent: 'center', backgroundColor:'cyan' }}>
<Text>Hallo {authUser.username}</Text>
<Button title="Log out" onPress={signOut} />
<Text>HomeScreen </Text>
</View>
);
}
When a user logs in or was already logged in, the Homescreen is shown correctly with the correct username. The problem appears when the logout button is clicked. The signOut method in the authContext gets called, which logs out the user and removes the object from the AsyncStorage :
signOut: async () => {
try {
await AsyncStorage.removeItem('authUser');
} catch (e) {
console.log(e.message);
}
dispatch({ type: 'SIGN_OUT' })
}
The problem :
The state changes to SIGN_OUT which brings me back to the login page. But since i've cleared the user-object, the welcome notification throws an error because {authUser.username} is null:
TypeError: null is not an object (evaluating 'authUser.username')
I didn't expect the WelcomeScreen to be re-rendered when clicking the sign-out button. Did i mess up the structure of my application, or why is this? Any help on how to fix this would be greatly appreciated. Thanks in advance!

According to React documentation useContext will always re-render when the context value changes, so that's why HomeScreen is been called again.
Another thing you need to fix is the authUser initial state to be equal to authUser signOut state:
const [state, dispatch] = React.useReducer(
(prevState, action) => {
switch (action.type) {
case 'RESTORE_TOKEN':
return {
...prevState,
authUser: action.authUser,
isLoading: false,
};
case 'SIGN_IN':
return {
...prevState,
isSignout: false,
authUser: action.authUser,
};
case 'SIGN_OUT':
return {
...prevState,
isSignout: true,
authUser: null,
};
}
},
{
isLoading: true,
isSignout: false,
authUser: "", //MAKE THIS THE SAME AS SIGN_OUT (null)
}
);

Related

Authentication flows | React Navigation 6 - How to get webToken?

When I learn authentication flow of react navigation 6.0, I read the sample code which used redux, it used dummy token, but In my react native project, I have to get the real token from server, So I tried to add some code in the sample project.
import * as React from 'react';
import { Button, Text, TextInput, View } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { useMutation, gql } from '#apollo/client';
import {
ApolloClient,
ApolloProvider,
createHttpLink,
InMemoryCache
} from '#apollo/client';
import { setContext } from 'apollo-link-context';
const SIGNIN_USER = gql`
mutation signIn($email: String!, $password: String!) {
signIn(email: $email, password: $password)
}
`;
// for example, my server address
const API_URI='https://xxxx.herokuapp.com/api';
const cache = new InMemoryCache();
const client = new ApolloClient({
uri: API_URI,
cache: new InMemoryCache()
});
const AuthContext = React.createContext();
function SplashScreen() {
return (
<View>
<Text>Loading...</Text>
</View>
);
}
function HomeScreen() {
const { signOut } = React.useContext(AuthContext);
return (
<View>
<Text>Signed in!</Text>
<Button title="Sign out" onPress={signOut} />
</View>
);
}
function SignInScreen() {
const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState('');
const { signIn } = React.useContext(AuthContext);
return (
<View>
<TextInput
placeholder="Username"
value={username}
onChangeText={setUsername}
/>
<TextInput
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<Button title="Sign in" onPress={() => signIn({ username, password })} />
</View>
);
}
const Stack = createStackNavigator();
export default function App({ navigation }) {
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 {
// Restore token stored in `SecureStore` or any other encrypted storage
// userToken = await SecureStore.getItemAsync('userToken');
} catch (e) {
// Restoring token failed
}
// 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', token: userToken });
};
bootstrapAsync();
}, []);
const authContext = React.useMemo(
() => ({
signIn: async ({username, password}) => {
useMutation(SIGNIN_USER, {
variables:{
email: username,
password: password
},
onCompleted: data => {
console.log(data);
}
});
// In a production app, we need to send some data (usually username, password) to server and get a token
// We will also need to handle errors if sign in failed
// After getting token, we need to persist the token using `SecureStore` or any other encrypted storage
// In the example, we'll use a dummy token
dispatch({ type: 'SIGN_IN', token: data.signIn });
},
signOut: () => dispatch({ type: 'SIGN_OUT' }),
signUp: async (data) => {
// In a production app, we need to send user data to server and get a token
// We will also need to handle errors if sign up failed
// After getting token, we need to persist the token using `SecureStore` or any other encrypted storage
// In the example, we'll use a dummy token
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
}),
[]
);
return (
<AuthContext.Provider value={authContext}>
<ApolloProvider client={client}>
<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',
// When logging out, a pop animation feels intuitive
animationTypeForReplace: state.isSignout ? 'pop' : 'push',
}}
/>
) : (
// User is signed in
<Stack.Screen name="Home" component={HomeScreen} />
)}
</Stack.Navigator>
</NavigationContainer>
</ApolloProvider>
</AuthContext.Provider>
);
}
And I get the error,
WARN Possible Unhandled Promise Rejection (id: 0):
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
I searched it, it is because I use hooks in useMemo.
https://reactjs.org/warnings/invalid-hook-call-warning.html
To avoid confusion, it’s not supported to call Hooks in other cases:
πŸ”΄ Do not call Hooks in class components.
πŸ”΄ Do not call in event handlers.
πŸ”΄ Do not call Hooks inside functions passed to useMemo, useReducer, or useEffect.
But, I have to send some data to server to get a token, and save token by Securestore or something else, If I can't write this section here, where?
I have no idea about where to write this section of code. My understanding to Redux in this situation:
when a user click button of 'sign in', call the function of signIn which is declared in authContext, useMemo, in use useMemo, get the token, save the token, then dispatch to notify data change, then go to the reducer.
After read a basic tutorial of redux, I have finished that by:
In SignInScreen, get the web token by 'login' function, which is triggered by the button of 'Sign in'
After execute it successfully, invoke the 'signIn' to update the state(by call dispatch).
I was confused with the name of 'signIn' function before, which is the same with my eventHandler function of 'signIn' button in SignInScreen. Actually, they are in duty of different logic.
import * as React from 'react';
import { Button, Text, TextInput, View } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { useMutation, gql } from '#apollo/client';
import {
ApolloClient,
ApolloProvider,
createHttpLink,
InMemoryCache
} from '#apollo/client';
import { setContext } from 'apollo-link-context';
const SIGNIN_USER = gql`
mutation signIn($email: String!, $password: String!) {
signIn(email: $email, password: $password)
}
`;
const API_URI='https://jseverywhere.herokuapp.com/api';
const cache = new InMemoryCache();
const client = new ApolloClient({
uri: API_URI,
cache: new InMemoryCache()
});
const AuthContext = React.createContext();
function SplashScreen() {
return (
<View>
<Text>Loading...</Text>
</View>
);
}
function HomeScreen() {
const { signOut } = React.useContext(AuthContext);
return (
<View>
<Text>Signed in!</Text>
<Button title="Sign out" onPress={signOut} />
</View>
);
}
function SignInScreen() {
const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState('');
const { signIn } = React.useContext(AuthContext);
const [logIn, {loading, error}] = useMutation(SIGNIN_USER, {
variables:{
email: username,
password: password
},
onCompleted: data => {
console.log(data);
signIn(data);
}
});
if(loading)
return <Text>Loading...</Text>;
if(error)
return <Text>Error--{error.message}</Text>;
return (
<View>
<TextInput
placeholder="Username"
value={username}
onChangeText={setUsername}
/>
<TextInput
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<Button title="Sign in" onPress={() => logIn()} />
</View>
);
}
const Stack = createStackNavigator();
export default function App({ navigation }) {
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 {
// Restore token stored in `SecureStore` or any other encrypted storage
// userToken = await SecureStore.getItemAsync('userToken');
} catch (e) {
// Restoring token failed
}
// 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', token: userToken });
};
bootstrapAsync();
}, []);
const authContext = React.useMemo(
() => ({
signIn: async (data) => {
// In a production app, we need to send some data (usually username, password) to server and get a token
// We will also need to handle errors if sign in failed
// After getting token, we need to persist the token using `SecureStore` or any other encrypted storage
// In the example, we'll use a dummy token
dispatch({ type: 'SIGN_IN', token: data.signIn });
},
signOut: () => dispatch({ type: 'SIGN_OUT' }),
signUp: async (data) => {
// In a production app, we need to send user data to server and get a token
// We will also need to handle errors if sign up failed
// After getting token, we need to persist the token using `SecureStore` or any other encrypted storage
// In the example, we'll use a dummy token
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
}),
[]
);
return (
<AuthContext.Provider value={authContext}>
<ApolloProvider client={client}>
<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',
// When logging out, a pop animation feels intuitive
animationTypeForReplace: state.isSignout ? 'pop' : 'push',
}}
/>
) : (
// User is signed in
<Stack.Screen name="Home" component={HomeScreen} />
)}
</Stack.Navigator>
</NavigationContainer>
</ApolloProvider>
</AuthContext.Provider>
);
}

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>
);
}

How to import AuthContext created in App.js in other components

I am following the procedures mentioned in React navigation Docs for implementing Authentication using Expo secure Store. https://reactnavigation.org/docs/auth-flow/
I tried to minimise their code snippet as short as possible if this is not clear please refer this snippet
//some imports
const AuthContext = React.createContext();
function SplashScreen() {
//some jsx
}
function HomeScreen() {
const { signOut } = React.useContext(AuthContext);
return (
//somejsx
);
}
function SignInScreen() {
const { signIn } = React.useContext(AuthContext);
return (//somejsx);
}
const Stack = createStackNavigator();
export default function App({ navigation }) {
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,
}
);
const authContext = React.useMemo(
() => ({
signIn: () => dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' }),
signOut: () => dispatch({ type: 'SIGN_OUT' }),
signUp: () => dispatch({ type: 'SIGN_IN', token: 'dummy' });
}),
[]
);
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer>
<Stack.Navigator>
{state.isLoading ? (
<Stack.Screen name="Splash" component={SplashScreen} />
) : state.userToken == null ? (
<Stack.Screen
name="SignIn"
component={SignInScreen}
/>
) : (
<Stack.Screen name="Home" component={HomeScreen} />
)}
</Stack.Navigator>
</NavigationContainer>
</AuthContext.Provider>
);
}
Now Coming to my Issue, In the given snippet they used the Authcontext they created in the login and register screens directly
const { signOut } = React.useContext(AuthContext);
But in my use case scenario i have to use the signOut function outside the App component, in my signOut screen.
When i tried to use the AuthContext in a separate file, react native is throwing up an error "can't find variable AuthContext". I encoded My App component with the and also provided the value with correct syntax.
I am not sure if i have to import the AuthContext in the login and register components.
Any help would be highly appreciated, Thanks in Advance.
You need to wrap the compenent where you need to use the context, like this as example
<AuthenticationProvider>
<TheComponent/>
</AuthenticationProvider>
You can first create a component called Provider
const Providers: FC = (props) => (
<AuthenticationProvider>
{props.children}
</AuthenticationProvider>
);
export default Providers;
and then pass your component as children
In App.js:
export const AuthContext = React.createContext();
In SignInScreen.js:
import { AuthContext } from 'App.js'

Why is my code causing my component to show up for a split second?

I am developing an application for a religious group, and I just implemented AsyncStorage to store the JWT received from my back end. My back end is working perfectly, and I'm able to store and delete the token on login/register and logout. But when the app is refreshed, it shows the login screen for a split second.
Here is my action:
export const fetchUser = () => {
return async (dispatch) => {
const token = await getData();
const reqObj = {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
};
if (token) {
dispatch({ type: "IS_LOADING_JWT" });
fetch(`http://${IP_ADDRESS}:4000/api/v1/auto_login`, reqObj)
.then((resp) => resp.json())
.then((current_user) => {
dispatch({ type: "LOGIN_SUCCESS", user: current_user });
dispatch({ type: "NOT_LOADING_JWT" });
});
}
};
};
Here is my helper function to retrieve the stored value-key pair:
const getData = async () => {
try {
const token = await AsyncStorage.getItem("jwt");
return token;
} catch (error) {
console.log(error);
}
};
This is my user reducer:
const user = (state = null, action) => {
switch (action.type) {
case "LOGIN_SUCCESS":
return action.user;
case "LOGOUT_USER":
return null;
default:
return state;
}
};
export default user;
Here is my 'authentication flow' with my stack navigator:
class Navigator extends Component {
componentDidMount() {
this.props.fetchUser();
}
render() {
return (
<NavigationContainer>
{this.props.jwtLoader ? (
<Stack.Navigator
screenOptions={{
title: "",
headerShown: false,
animationEnabled: false,
}}
>
<Stack.Screen name="loader" component={LoaderScreen} />
</Stack.Navigator>
) : !this.props.user ? (
<Stack.Navigator
screenOptions={{
title: "",
headerShown: false,
animationEnabled: false,
}}
>
<Stack.Screen name="Login" component={LoginScreen} />
</Stack.Navigator>
) : (
<Stack.Navigator
screenOptions={{
title: "",
headerShown: false,
animationEnabled: false,
}}
>
<Stack.Screen name="Main" component={MainScreen} />
</Stack.Navigator>
)}
</NavigationContainer>
);
}
}
My jwtLoader:
const jwtLoader = (state = false, action) => {
switch (action.type) {
case "IS_LOADING_JWT":
return true;
case "NOT_LOADING_JWT":
return false;
default:
return state;
}
};
export default jwtLoader;
My theory is that there is a delay on dispatching the action. That is why I believe my Login component shows for a split second.
I have already tried getting and setting my user during my SplashScreen load, but that doesn't work either.
Here is a demo video: Text
Can you try fetching user from the constructor instead of componentDidMount?

what is cause infinite rerenders?

So i am trying to follow the react navigation tutorial for auth flow https://reactnavigation.org/docs/auth-flow/
But for some reason its causing infinite rerenders, the screen flickering like crazy, and i have no idea why other than the dispatch in useEffect is just updating the state every render, but as far as i understand it that shouldn't be happening.
import React from "react"
import { NavigationContainer, NavigationContainerRef } from "#react-navigation/native"
import { createNativeStackNavigator } from "react-native-screens/native-stack"
import { PrimaryNavigator, AuthNavigator, DrawerNavigator } from "./primary-navigator"
import { SplashScreen } from "../screens/splash-screen"
import AsyncStorage from "#react-native-community/async-storage"
export type RootParamList = {
primaryStack: undefined,
authStack: undefined,
splashScreen: undefined,
}
const Stack = createNativeStackNavigator<RootParamList>()
const RootStack = () => {
const AuthContext = React.createContext()
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(() => {
const bootstrapAsync = async () => {
let userToken
try {
userToken = await AsyncStorage.getItem('userToken')
console.log(userToken)
} 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', token: userToken })
}
bootstrapAsync()
})
const authContext = React.useMemo(
() => ({
signIn: async data => {
// In a production app, we need to send some data (usually username, password) to server and get a token
// We will also need to handle errors if sign in failed
// After getting token, we need to persist the token using `AsyncStorage`
// In the example, we'll use a dummy token
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' })
},
signOut: () => dispatch({ type: 'SIGN_OUT' }),
signUp: async data => {
// In a production app, we need to send user data to server and get a token
// We will also need to handle errors if sign up failed
// After getting token, we need to persist the token using `AsyncStorage`
// In the example, we'll use a dummy token
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' })
},
}), [])
return (
<AuthContext.Provider value={authContext}>
<Stack.Navigator
screenOptions={{
headerShown: false,
gestureEnabled: true,
stackPresentation: "modal",
}}
>
{ state.userToken == null
? <Stack.Screen
name="authStack"
component={AuthNavigator}
options={{
headerShown: false
}}
/>
: <Stack.Screen
name="primaryStack"
component={DrawerNavigator}
options={{
headerShown: false,
}}
/>
}
</Stack.Navigator>
</AuthContext.Provider>
)
}
export const RootNavigator = React.forwardRef<
NavigationContainerRef,
Partial<React.ComponentProps<typeof NavigationContainer>>
>((props, ref) => {
return (
<NavigationContainer {...props} ref={ref}>
<RootStack />
</NavigationContainer>
)
})
RootNavigator.displayName = "RootNavigator"
You missed the dependency '[]' which is provided in the tutorial. When you provide a dependency to the useEffect it will run only when a dependency changes. This is provided in the tutorial seems like you missed it.
This is from the tutorial
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
}
// 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', token: userToken });
};
bootstrapAsync();
}, []);