I'm using React Navigation on my react native app and following its authentication flow.
app.js will render either authentication screen or home screen based on user auth state called userProfile.
userProfile will be passed from App component to children component via React context called userContext, and some dispatch functions will be passed as well called authContext.
const App: () => React$Node = () => {
const [state, dispatch] = React.useReducer(
(prevState, action) => {
switch (action.type) {
case SIGN_IN:
return {
...prevState,
userProfile: action.userProfile,
};
case SIGN_OUT:
return {
...prevState,
userProfile: null,
};
}
},
{
isLoading: true,
userProfile: null,
}
)
React.useEffect(() => {
isMountedRef.current = true;
return () => (isMountedRef.current = false);
}, []);
const authContext = React.useMemo(
() => ({
signIn: async userProfile => {
try {
await AsyncStorage.setItem('userProfile', JSON.stringify(userProfile))
dispatch({ type: SIGN_IN, userProfile })
} catch (error) {
console.log(error)
}
},
signOut: async () => {
try {
await AsyncStorage.removeItem('userProfile');
dispatch({ type: SIGN_OUT })
} catch (error) {
console.log(error)
}
}
}),
[]
);
return (
<AuthContext.Provider value={{authContext, userContext: state.userProfile}}>
<NavigationContainer ref={navigationRef}>
<Stack.Navigator>
{console.log('app render: ', state.userProfile)}
{
state.userProfile == null ?
(<Stack.Screen name="AuthContainer" component={AuthContainer} options={{ headerShown: false }} />) :
(<Stack.Screen name="MainContainer" component={MainContainer} options={{ headerShown: false }} />)
}
</Stack.Navigator>
</NavigationContainer>
</AuthContext.Provider>
);
};
In one of the nested children component ProfileScreen under home screen or under MainContainer in above code, I'm trying to consume the userContext to show user information on screen, and a sign out button is using a dispatch sign out function to update the user auth state to null in the App component.
function ProfileScreen() {
const { authContext, userContext } = React.useContext(AuthContext)
console.log('profile render: ', userContext)
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Hello {userContext.username}!</Text>
<Text>Profile Screen</Text>
<Button
title="Sign Out"
onPress={() => authContext.signOut()}
/>
</View>
);
}
export default ProfileScreen;
After dispatch the sign out function, I expect the app to navigate to auth screen AuthContainer to ask user to sign in again since at this time my userProfile state in App component should be null. However, App component is still trying to render the ProfileScreen which throws an error userContext is null.
From my log, after I dispatch sign out function in ProfileScreen, it shows
app render: null ---> App re-render
profile render: null ---> profileScreen re-render
profile render: null ---> profileScreen re-render
auth container ---> finally start rendering Auth Screen
then immediately throw userContext is null error
Can anyone kindly help me understand why App component tries to render profileScreen when userProfile state is null? And why does the profileScreen re-render twice?
Thans you so much