Search code examples
androidiosreact-nativemobilenavigation

How to show a screen only on first time use?


I am trying to show the license agreement screen only once. On that screen, there is an ACCEPT button, once clicked - it saved a value to AsyncStorage.

In app.js I then check the value of that key in AsyncStorage and act accordingly. My problem is that the Login screen keeps showing first when I expect the License agreement to.

I think it's an async issue but not sure how to resolve it.

I did try initially set loading to true in the useState, and this method works. But once I accept the agreement the first time and then reload the app - I see the license agreement screen for a quick second before it redirects to the login screen - not ideal.

The console log prints the value as expected, but I think the navigation renders before useEffect logic is complete.

App.js

function App() {


const [loading, setLoading] = useState(false);

  useEffect(() => {
    load();
  }, []);

  const load = async () => {
    await AsyncStorage.getItem('isAgreementAccepted').then(result => {
      console.log('RES: ', result)
        if (result == null) {
           setLoading(true);

        } else {
           setLoading(false);
        }
    })

}

  return (
    <NavigationContainer>
      <Stack.Navigator> 
         {loading && <Stack.Screen
          name="EndUserAgreement"
          component={EndUserAgreementScreen}
          options={{ headerShown: false }}
        />}
         <Stack.Screen
          name="Login"
          component={LoginScreen}
          options={{ headerShown: false }}
        />

Solution

  • like you say, you're Stack.screens are rendering before the loading state is set, so you're login screen loads on it's own, then once loading is set to true your endUserAgreement screen is loading in - but your navigation (react-navigation?) won't automatically switch your route

    you can:

    • navigate to the endUserAgreement screen using the useNavigation hook & .navigate() (render both Stack.screens in this scenario, and set the initialRouteName on the Stack.Navigator to 'login')

    • leave only the endUserAgreement screen in place if the storage call returns null, which will force the navigation to only show that screen - something like:

    {loading ? (
      <Stack.Screen
        name="EndUserAgreement"
        component={EndUserAgreementScreen}
        options={{ headerShown: false }}
      />
    ) : ( 
      <Stack.Screen
        name="Login"
        component={LoginScreen}
        options={{ headerShown: false }}
      />
      {/* ... other screens here */}
    )}
    
    • show a loading screen until your storage call is complete, then set the initialRouteName based on it's outcome:
    const [loading, setLoading] = useState(null)
    
    const load = async () => {
        await AsyncStorage.getItem('isAgreementAccepted').then(result => {
          console.log('RES: ', result)
            if (result == null) {
               setLoading(true);
            } else {
               setLoading(false);
            }
        })
    
    }
    
      return (
        <NavigationContainer>
           
          {loading === null ? (
            <LoadingScreen />
          ) : (
            <Stack.Navigator initialRouteName={loading ? 'EndUserAgreement' : 'login'}>
             <Stack.Screen
              name="EndUserAgreement"
              component={EndUserAgreementScreen}
              options={{ headerShown: false }}
            />}
             <Stack.Screen
              name="Login"
              component={LoginScreen}
              options={{ headerShown: false }}
            />
    

    so we're defaulting to null loading state, showing a loading component until it's set to true or false, then setting the initial route name to the correct screen name.