I have a react-native app that stores login credentials in a jwt. My logout function looks like this:
logOut={() => {
AsyncStorage.getAllKeys()
.then((keys) => AsyncStorage.multiRemove(keys))
.then(() =>
navigation.reset({
index: 0,
routes: [{ name: 'Login' }],
key: null,
}),
)
My StackNavigator (in App.jsx) looks like this:
<NavigationContainer >
<Stack.Navigator>
{valid_token ? (
consented ? (
<>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Nav" component={Nav} /> // Nav is the component with the login function
</>
): (
<>
<Stack.Screen name="ConsentForm" component={ConsentForm} />
</>
)
) : (
<>
<Stack.Screen name='Login'>
{(props) => <Login setExpired={setValidToken} setConsented={setConsented} />}
</Stack.Screen>
</>
)}
</Stack.Navigator>
</NavigationContainer>
valid_token
is set based on the value of the jwt, which is in async storage. I am clearing out the token in the logout function, however, App.jsx doesn't see that. I also am not sure if I really have to pass the setValidToken
and setConsented
props throughout the whole app (the nav bar, which contains the logout, is on every screen). When I don't pass props and run the code as is, I get this error:
The action 'RESET' with payload {"index":0,"routes":[{"name":"Login"}],"key":null} was not handled by any navigator.
I tried replacing the navigation.reset
part of the logout function with navigation.dispatch(StackActions.popToTop())
- this takes me to the Home page.
Is passing props throughout the whole app my only option?
EDIT: I tried adding a context called AuthContext as per the answer below. I am able to log in with the new AuthContext, however my error for log out is the exact same. I tried just having the navigate (without removing the jwt token) but it doesn't make a difference. I've added more code below to hopefully give more clarity.
Here's my AuthContext.jsx:
import {createContext } from "react";
const AuthContext = createContext();
export default AuthContext;
Here's my new App.jsx:
import React, { useEffect } from 'react'
// import lots of other stuff
import AuthContext from './AuthContext';
export default function App() {
const [consented, setConsented] = React.useState(false) // I'll figure this out later
const [loggedIn, setLoggedIn] = React.useState(false)
const Stack = createNativeStackNavigator()
const getData = async () => {
return await AsyncStorage.getItem('@myapp:jwt');
}
useEffect(() => {
getData().then((value) => {
value = JSON.parse(value)
if(value == null){
setLoggedIn(false)
}else{
const now = moment()
setLoggedIn(!(now.isAfter(moment(value?.expiry))))
setConsented(value?.consented)
}
})
})
return (
<AuthContext.Provider
value={{
loggedIn,
setLoggedIn
}}>
<NavigationContainer >
<Stack.Navigator>
{loggedIn ? (
consented ? (
<>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Nav" component={Nav} />
</>
): (
<>
<Stack.Screen name="ConsentForm" component={ConsentForm} />
</>
)
) : (
<>
<Stack.Screen name='Login' component={Login}/>
</>
)}
</Stack.Navigator>
</NavigationContainer>
</AuthContext.Provider>
)
}
And finally, here's relevant stuff from my Login.jsx which should be the child component of App.jsx:
# many imports
import AuthContext from '../AuthContext'
const Login = () => {
const navigation = useNavigation();
const [text, onChangeText] = React.useState(null)
const [number, onChangeNumber] = React.useState(null)
const {loggedIn, setLoggedIn} = React.useContext(AuthContext)
const postLoginInformation = () => {
fetch(`${MYAPP_URL}/login/`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: text,
password: number,
}),
})
.then(async (res) => {
const response = await res.json()
const jwt = JSON.parse(response).jwt
const decoded_jwt = jwt_decode(jwt))
await AsyncStorage.setItem(
'@myapp:jwt',
JSON.stringify(decoded_jwt),
)
setLoggedIn(true)
//setConsented(decoded_jwt.consented) // I'll figure this out later
}).catch((e) => console.log('login error', e, `${MYAPP_URL}/login/`))
}
return (
<SafeAreaView style={styles.container}>
<View>
<TextInput
style={styles.input}
onChangeText={onChangeText}
value={text}
placeholder="Username"
/>
<TextInput
style={styles.input}
secureTextEntry
onChangeText={onChangeNumber}
value={number}
placeholder="Password"
/>
<TouchableOpacity
style={{ height: 100 }}
onPress={() => postLoginInformation()}
>
<Image
source={require('../images/login/login_button.png')}
/>
</TouchableOpacity>
</View>
</SafeAreaView>
)
}
export default Login
Appreciate any help! Thank you
Do not manually navigate when using conditionally rendering screens. Logout should just invalidate a state and login screen will automatically appear. Use Context to manage the state instead of passing props. Here is a super simple example:
See Authentication flows for details.