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