Displaying welcome screen only once

Viewed 560

I have a React Native application and I want to display a welcome screen when the application is first installed.

I have a welcome screen, and when the user clicks on the continue button, I have an async storage value that changes so I know if I should display the screen or not.

In app.js, I check for that value to determine if I should display Home or Welcome.

This is what I have in app.tsx:

      const [shouldDisplayWelcome, setShouldDisplayWelcome] = useState<boolean>(true);
      useEffect(() => {
        StorageManager.getInstance().getData(Key.firstTime).then((firstTime) => {
          if (firstTime != undefined) {
            setShouldDisplayWelcome(firstTime);
          }
        })    
      }, []);
    
      return (
        <ThemeProvider>
          <Provider store={store}>
            {
                <NavigationContainer>
                  <Stack.Navigator>
                    {
                      shouldDisplayWelcome && <Stack.Screen
                        name="Welcome"
                        component={Welcome}
                        options={{ headerShown: false }}
                      />
                    }
                    <Stack.Screen
                      name="Home"
                      component={Home}
                      options={{ headerShown: false }}
                    />
                    <Stack.Screen
                      name="Settings"
                      component={Settings}
                      options={{ headerShown: false }}
                    />
                  </Stack.Navigator>
                </NavigationContainer>
            }
          </Provider>
        </ThemeProvider>

It is working, but when the user closes the app and then comes back to it, there is a flicker that shows the Welcome screen and goes right to the Home screen. I guess that's because getting the data is async operation, but I couldn't figure out how to solve this, so if there is no need to display the Welcome screen, it won't show it for half a second before getting the answer from StorageManager.

Edit: I tried to 'hold' the application until the async function finishes, like this:

 const chooseScreen = async () => {
    await StorageManager.getInstance().getData(Key.firstTime).then((result) => {
      if (result != undefined) {
        setShouldDisplayWelcome(result);
      }
    })
  }

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

But this also didn't work.

2 Answers

I got a workaround to fix this issue, not sure if that's the best solution, but it does work. What I did was to create another state named loading and set it to true. Then, after the async function finishes, I set loading to false in the finally block. I set App.tsx to render my NavigationContainer only when loading is false which only happens after the async operation finished. The issue where the welcome screen was flickering at the start not happening anymore.

const App = () => {
  const [shouldDisplayWelcome, setShouldDisplayWelcome] = useState<boolean>(true);
  const [loading, setLoading] = useState<boolean>(true);

  const chooseScreen = async () => {
    await StorageManager.getInstance().getData(Key.firstTime).then((result) => {
      if (result != undefined) setShouldDisplayWelcome(result);
    }).finally(() => {
      setLoading(false);
    })
  }

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

  return (
    <ThemeProvider>
      <Provider store={store}>
        {
          !loading &&
          <NavigationContainer>
            ....
          </NavigationContainer>
        }
      </Provider>
    </ThemeProvider>

You can use a custom hook to get the asynchronous value. While waiting/loading, you should return "null" or something else intelligent.

onboardingIsLoading gives you the loading state

import useGetOnboardingStatus from "../../utils/useGetOnboardingStatus";

const { isFirstLaunch, isLoading: onboardingIsLoading } =
    useGetOnboardingStatus();

Hook:

import React, { useState } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";

async function checkIfFirstLaunch() {
  try {
    const hasFirstLaunched = await AsyncStorage.getItem("@user_onboarded");
    if (hasFirstLaunched === null) {
      return true;
    }
    return false;
  } catch (error) {
    return false;
  }
}

const useGetOnboardingStatus = () => {
  const [isFirstLaunch, setIsFirstLaunch] = useState(false);
  const [isFirstLaunchIsLoading, setIsFirstLaunchIsLoading] = useState(true);

  React.useEffect(async () => {
    const firstLaunch = await checkIfFirstLaunch();
    setIsFirstLaunch(firstLaunch);
    setIsFirstLaunchIsLoading(false);
  }, []);

  return {
    isFirstLaunch: isFirstLaunch,
    isLoading: isFirstLaunchIsLoading,
  };
};

export default useGetOnboardingStatus;
Related