ウェブサイト検索

reactjs - react-nativeでAsyncStorageを使用してもStateが値を更新しない


私は値を非同期ストレージに保存し、その後、非同期ストレージからの値の結果に応じて正しいページに遷移しようとしています。AsyncStorageにデータを保存することはできますが、状態が更新されず、状態を更新するためにはアプリを再読み込みする必要があります。こちらが私のコードです:

ここにはウェルカム/オンボーディング画面があります。この画面は新しいアプリユーザーのみに表示したいです。ですので、ユーザーが続行ボタンを押したときに、次回ログイン時にオンボーディングページを再度表示しないように、Async Storageに値を保存したいです。こちらが私のオンボーディングページです:

const WelcomeScreen: FC<IWelcomeScreen> = ({ navigation }) => {
  const { width, height } = Dimensions.get("window");

  const btnText = "Contiunue";
  const title = "Book";
  const subTitle = "Fab";

  let [fontsLoaded] = useFonts({
    PinyonScript_400Regular,
  });

  const continueBtn = async () => {
    try {
      await AsyncStorage.setItem('@viewedOnboarding', 'true');
    } catch (error) {
      console.log('Error @setItem: ', error);
    };
  };

  if (!fontsLoaded) {
    return <Text>...Loading</Text>;
  } else {
    return (
      <View style={containerStyle(height, width).container}>
        <ImageBackground
          resizeMode={"cover"}
          style={styles.image}
          source={require("../assets/model.jpg")}
        >
          <LinearGradient
            colors={["#00000000", "#000000"]}
            style={styles.gradient}
          >
            <View style={styles.container}>
              <View style={styles.logoTextContainer}>
                <Text style={styles.logoText}>{title}</Text>
                <Text style={styles.logoText}>{subTitle}</Text>
              </View>

              <ContinueBtn label={btnText} callback={continueBtn} />
            </View>
          </LinearGradient>
        </ImageBackground>
      </View>
    );
  }
};

私のAppNavigatorでは、ユーザーがどのナビゲーションを見るべきかを決めたいです。しかし、続行ページを押すと、アプリはTabsNavigatorに遷移せず、Onboardingページに留まります。しかし、アプリをリフレッシュすると、アプリはTabsNavigatorに遷移します。ここでは、ユーザーが新しいユーザーか「古い」ユーザーかに応じて、どこにいるべきかを決定するコードです:

const WelcomeScreen: FC<IWelcomeScreen> = ({ navigation }) => {
  const { width, height } = Dimensions.get("window");

  const btnText = "Contiunue";
  const title = "Book";
  const subTitle = "Fab";

  let [fontsLoaded] = useFonts({
    PinyonScript_400Regular,
  });

  const continueBtn = async () => {
    try {
      await AsyncStorage.setItem('@viewedOnboarding', 'true');
    } catch (error) {
      console.log('Error @setItem: ', error);
    };
  };

  if (!fontsLoaded) {
    return <Text>...Loading</Text>;
  } else {
    return (
      <View style={containerStyle(height, width).container}>
        <ImageBackground
          resizeMode={"cover"}
          style={styles.image}
          source={require("../assets/model.jpg")}
        >
          <LinearGradient
            colors={["#00000000", "#000000"]}
            style={styles.gradient}
          >
            <View style={styles.container}>
              <View style={styles.logoTextContainer}>
                <Text style={styles.logoText}>{title}</Text>
                <Text style={styles.logoText}>{subTitle}</Text>
              </View>

              <ContinueBtn label={btnText} callback={continueBtn} />
            </View>
          </LinearGradient>
        </ImageBackground>
      </View>
    );
  }
};

回答 #1

非同期ストレージに値を設定しても、AppNavigator の再レンダリングはトリガーされません。したがって、ユーザーが continue button を押しても、AppNavigator の状態が変わっていないため、視覚的には何も起こりません。アプリをリフレッシュすると、以前に setItem 関数を使って設定したフラグが、初回レンダリング時に AppNavigator に再読み込みされます。これが、アプリをリフレッシュした後に動作する理由です。

この種の問題については、AppNavigatorでの状態変更をトリガーするためにContextを使用することをお勧めします。

これがどのように機能するかの最小限の例です。コードにコメントを追加して、案内しています。

簡単のために、次の仮定を置くことにします。

私たちはスタックに2つの画面を持っています。1つはWelcomeScreenで、もう1つはHomeScreenと呼ばれています。

私たちはアプリケーションのコンテキストに応じて画面を条件付きでレンダリングすることに注意してください。必要な画面は何でも追加できますし、ナビゲーター全体を追加することもできます(ナビゲーターが入れ子になっている場合はこれが必要ですが、パターン自体は同じです)。

アプリ

export const AppContext = React.createContext()

const App = () => {
  // it is important that the initial state is undefined, since
  // we need to wait for the async storage to return its value 
  // before rendering anything
  const [hasViewedOnboarding, setHasViewedOnboarding] = React.useState()

  const appContextValue = useMemo(
    () => ({
      hasViewedOnboarding,
      setHasViewedOnboarding,
    }),
    [hasViewedOnboarding]
  )

  // retrieve the onboarding flag from the async storage in a useEffect
  React.useEffect(() => {
       const init = async () => {
          const value = await AsyncStorage.getItem('@viewedOnboarding')
          setHasViewedOnboarding(value != null ? JSON.parse(value) : false)
       }
       init()
  }, [])

  // as long as the flag has not been loaded, return null
  if (hasViewedOnboarding === undefined) {
    return null
  }

  // wrap everything in AppContext.Provider an pass the context as a value
  return (
      <AppContext.Provider value={appContextValue}>
        <NavigationContainer>
           <Stack.Navigator>
             {!hasViewedOnboarding ? (
                <Stack.Screen name="Welcome" component={WelcomeScreen} />
              ) : (
                <Stack.Screen
                  name="Home"
                  component={HomeScreen}
                />
              )}}
           </Stack.Navigator>
        </NavigationContainer>
     </AppContext.Provider>
  )
}

さて、あなたのWelcomeScreenでは、コンテキストにアクセスし、非同期で値が保存された後に状態を設定する必要があります。

const WelcomeScreen: FC<IWelcomeScreen> = ({ navigation }) => {
  
  // access the context

  const { setHasViewedOnboarding } = useContext(AppContext)
   
  const { width, height } = Dimensions.get("window");

  const btnText = "Contiunue";
  const title = "Book";
  const subTitle = "Fab";

  let [fontsLoaded] = useFonts({
    PinyonScript_400Regular,
  });

  const continueBtn = async () => {
    try {
      await AsyncStorage.setItem('@viewedOnboarding', 'true');
      setHasViewedOnboarding(true)
    } catch (error) {
      console.log('Error @setItem: ', error);
    };
  };

  if (!fontsLoaded) {
    return <Text>...Loading</Text>;
  } else {
    return (
      <View style={containerStyle(height, width).container}>
        <ImageBackground
          resizeMode={"cover"}
          style={styles.image}
          source={require("../assets/model.jpg")}
        >
          <LinearGradient
            colors={["#00000000", "#000000"]}
            style={styles.gradient}
          >
            <View style={styles.container}>
              <View style={styles.logoTextContainer}>
                <Text style={styles.logoText}>{title}</Text>
                <Text style={styles.logoText}>{subTitle}</Text>
              </View>

              <ContinueBtn label={btnText} callback={continueBtn} />
            </View>
          </LinearGradient>
        </ImageBackground>
      </View>
    );
  }
};