Tìm kiếm trang web

reactjs - State không cập nhật giá trị của nó khi sử dụng AsyncStorage trong react-native


Tôi đang cố gắng lưu một giá trị vào bộ nhớ bất đồng bộ (async storage) và sau đó điều hướng đến trang phù hợp tùy vào kết quả giá trị từ Async storage. Tôi có thể lưu dữ liệu trong AsyncStorage nhưng trạng thái của tôi không cập nhật, tôi phải tải lại ứng dụng để trạng thái được cập nhật. Đây là mã của tôi:

Ở đây tôi có một màn hình Chào mừng/Định hướng. Tôi muốn màn hình này chỉ hiển thị cho những người dùng mới của ứng dụng. Vì vậy, khi người dùng nhấn nút tiếp tục, tôi muốn lưu một giá trị vào bộ nhớ Async để lần sau họ đăng nhập, họ không phải xem lại trang định hướng nữa. Đây là trang Định hướng của tôi:

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

Trong AppNavigator của tôi, tôi muốn quyết định người dùng nên xem điều hướng nào. Nhưng khi tôi nhấn vào trang tiếp tục, ứng dụng của tôi không điều hướng đến TabsNavigator. Nó vẫn ở trên trang Onboarding của tôi, nhưng nếu tôi làm mới ứng dụng thì ứng dụng sẽ điều hướng đến TabsNavigator của tôi. Đây là mã nơi tôi xác định người dùng nên ở đâu tùy thuộc vào việc họ là người dùng mới hay người dùng “cũ”:

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

Câu trả lời #1

Việc thiết lập một giá trị trong bộ nhớ bất đồng bộ sẽ không kích hoạt việc render lại AppNavigator của bạn. Do đó, nếu người dùng nhấn continue button, thì sẽ không có gì xảy ra về mặt hình ảnh, vì trạng thái của AppNavigator vẫn chưa thay đổi. Nếu bạn làm mới ứng dụng, cờ mà bạn đã thiết lập trước đó bằng cách sử dụng hàm setItem sẽ được tải lại trong AppNavigator khi render lần đầu. Đây là lý do tại sao nó hoạt động sau khi làm mới ứng dụng.

Đối với loại vấn đề này, tôi gợi ý bạn nên sử dụng Context để kích hoạt sự thay đổi trạng thái trong AppNavigator.

Đây là một ví dụ tối giản về cách điều này sẽ hoạt động. Tôi đã thêm các chú thích trong mã để hướng dẫn bạn.

Vì sự đơn giản, chúng ta sẽ đưa ra giả định sau:

Chúng tôi có hai màn hình trong một Stack, một cái là WelcomeScreen, cái còn lại được gọi là HomeScreen.

Lưu ý rằng chúng tôi sử dụng kết xuất có điều kiện cho các màn hình tùy thuộc vào bối cảnh ứng dụng của chúng tôi. Bạn có thể thêm bất kỳ màn hình nào bạn muốn, thậm chí cả các bộ điều hướng (điều này sẽ cần thiết nếu các bộ điều hướng của bạn được lồng nhau, nhưng mẫu vẫn giữ nguyên).

Ứng dụng

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

Bây giờ, trong WelcomeScreen của bạn, bạn cần truy cập vào context và thiết lập trạng thái sau khi giá trị bất đồng bộ đã được lưu.

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