简体   繁体   English

State 不会在 React Native 上更新

[英]State doesn't update on React Native

I'm doing the notification page of my react native app.我正在做我的反应原生应用程序的通知页面。 It has infinite scroll and "pull to refresh" options.它具有无限滚动和“拉动刷新”选项。 Entering to the page it works, and it works also pulling to refresh.进入它工作的页面,它也可以拉动刷新。 The problem occurs when I scroll down because it seems it calls server to fetch new notifications but it doesn't concatenate to the array.当我向下滚动时会出现问题,因为它似乎调用服务器来获取新通知,但它没有连接到数组。

import React, { useState, useEffect, useCallback, Component } from "react";
import {
  View,
  Text,
  FlatList,
  Button,
  Platform,
  ActivityIndicator,
  StyleSheet,
  ScrollView,
  RefreshControl,
  SafeAreaView,
} from "react-native";
import { useSelector, useDispatch } from "react-redux";
import i18n from "i18n-js";
import Colors from "../../constants/Colors";
import { getNotificationList } from "../../utils/NotificationsUtils";
import Card from "../../components/UI/Card";

const NotificationsScreen = (props) => {
  const [refreshing, setRefreshing] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  const [page, setPage] = useState(0);
  const [notifications, setNotifications] = useState([]);
  const [error, setError] = useState();

  const dispatch = useDispatch();

  const onRefresh = useCallback(async () => {
    setRefreshing(true);
    setNotifications([]);
    setPage(0);

    console.log("-- Refreshing --");

    getNotifications().then(() => {
      setRefreshing(false);
    });
  }, [dispatch, setRefreshing]);

  const fetchMoreNotifications = useCallback(async () => {
    const newPage = page + 7;
    setPage(newPage);
    console.log(
      "FETCH MORE from page " + newPage + " on array of " + notifications.length
    );

    getNotifications().then(() => {
      setIsLoading(false);
    });
  }, [dispatch, getNotifications]);

  const getNotifications = useCallback(async () => {
    setError(null);
    setIsLoading(true);
    try {
      console.log("Get from page " + page);
      // let fromRecord = (page - 1) * 7;
      const retrievedNotifications = await getNotificationList(
        page,
        7,
        true,
        false
      );
      console.log(
        "Setting " +
          retrievedNotifications.response.notifications.length +
          " new notifications on an already existing array of " +
          notifications.length +
          " elements"
      );

      let updatedNews = notifications.concat(
        retrievedNotifications &&
          retrievedNotifications.response &&
          retrievedNotifications.response.notifications
      );
      setNotifications(updatedNews);
    } catch (err) {
      setError(err.message);
    }
    setIsLoading(false);
  }, [dispatch, setIsLoading, setNotifications, setError]);

  useEffect(() => {
    setIsLoading(true);
    getNotifications(page).then(() => {
      setIsLoading(false);
    });
  }, [dispatch, getNotifications]);

  return (
    <View>
      {error ? (
        <View style={styles.centered}>
          <Text>Error</Text>
        </View>
      ) : refreshing ? (
        <View style={styles.centered}>
          <ActivityIndicator size="large" color={Colors.primary} />
        </View>
      ) : !notifications || !notifications.length ? (
        <View style={styles.centered}>
          <Text>No data found</Text>
        </View>
      ) : (
        <FlatList
          refreshControl={
            <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
          }
          data={notifications}
          keyExtractor={(notification) => notification.notificationQueueId}
          onEndReached={fetchMoreNotifications}
          onEndReachedThreshold={0.5}
          initialNumToRender={4}
          renderItem={(itemData) => (
            <View
              style={{
                marginTop: 10,
                height: 150,
                width: "100%",
              }}
            >
              <Card style={{ height: 150, backgroundColor: "white" }}>
                <Text style={{ fontSize: 16, color: Colors.black }}>
                  {itemData.item.text}
                </Text>
              </Card>
            </View>
          )}
        />
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  centered: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
});

export default NotificationsScreen;

If I scroll to end it triggers 'fetchMoreNotifications' function and I get this in the console:如果我滚动到结束它会触发 'fetchMoreNotifications' function 我在控制台中得到这个:

FETCH MORE from page 7 on an array of 0
Get from page 0
Setting 7 new notifications on an already existing array of 0 elements
FETCH MORE from page 7 on an array of 0
Get from page 0
Setting 7 new notifications on an already existing array of 0 elements
FETCH MORE from page 7 on an array of 0
Get from page 0
Setting 7 new notifications on an already existing array of 0 elements
...and so on

As you can see it says 'existing array of 0 elements' even if previously I saved notifications.如您所见,即使我之前保存了通知,它也会显示“现有 0 个元素的数组”。 Maybe it has some issue with useCallback's dependency?也许它与 useCallback 的依赖关系有一些问题?

Issue:问题:

There are 2 main issues, one with page and second with notifications , due to useCallback and dependencies , useCallback function will always point to the old values which are not in dependencies until one of the dependencies for updated.有两个主要问题,一个是page问题,第二个是notifications问题,由于useCallbackdependencies ,useCallback function 将始终指向旧值,这些旧值不依赖于更新的依赖项之一。


1) The solution to page issue: 1) page问题的解决方案:

Pass newPage as param to getNotifications , due to async behavior of setPage it will not get updated directly newPage作为参数传递给getNotifications ,由于setPage的异步行为,它不会直接更新

And on the second time, to get the updated value of page you can pass page as a dependency.第二次,要获取页面的更新值,您可以将page作为依赖项传递。

2) The solution to the notification issue: 2) notification问题的解决方法:

Update the notification directly from its prev state value with setState(prevState => newState) .使用setState(prevState => newState)直接从其 prev state 值更新通知。


Solution:解决方案:

  const fetchMoreNotifications = useCallback(async () => {
    const newPage = page + 7;
    setPage(newPage);
    console.log(
      "FETCH MORE from page " + newPage + " on array of " + notifications.length
    );
    getNotifications(newPage).then(() => { // <---- Pass as param
      setIsLoading(false);
    });
  }, [page]); // <---- Add page as dependency 

  const getNotifications = useCallback(
    async page => { // <---- Get page as a param
      setError(null);
      setIsLoading(true);
      try {
        console.log("Get from page " + page);
        // let fromRecord = (page - 1) * 7;
      const retrievedNotifications = await getNotificationList(
        page,
        7,
        true,
        false
      );

      setNotifications(prevNotification => prevNotification.concat(
        retrievedNotifications &&
          retrievedNotifications.response &&
          retrievedNotifications.response.notifications
      )); // <---- Setting up state directly from previous value, instead of getting it from clone version of use callback
      } catch (err) {
        console.log(err);
        setError(err.message);
      }
      setIsLoading(false);
    },
    [setIsLoading, setNotifications, setError]
  );

WORKING DEMO :工作演示

Check the console log for updated page value and notification will be rendered on Html it self检查控制台日志以获取更新的页面值,通知将在 Html 上呈现

编辑 nostalgic-sound-r07x2

NOTE: Removed some of your code just to improve code readability and debug the issue注意:删除了一些代码只是为了提高代码的可读性和调试问题

The problem is really simple.问题真的很简单。 The getNotifications function is created using useCallback and hasn't used notifications as a dependency. getNotifications function 是使用useCallback创建的,并且没有使用notifications作为依赖项。 Now when notifications updates, the getNotications function is still referring to the old notifications values due to closure.现在当通知更新时,由于关闭,getNotications function 仍然引用旧的通知值。

Also note that you call getNotifications on fetchMoreNotifications immediately after setting page state but page state too is bound by closure and will not update in the same re-render另请注意,您在设置页面 state 后立即在fetchMoreNotifications上调用 getNotifications,但页面 state 也受关闭约束,不会在相同的重新渲染中更新

The solution here is to use the function approach to setNotifications and use useEffect to trigge4r getNotification on page change这里的解决方案是使用 function 方法来设置通知并使用 useEffect 在页面更改时触发 4r getNotification

const fetchMoreNotifications = useCallback(async () => {
    const newPage = page + 7;
    setPage(newPage);
  }, [dispatch, getNotifications]);

  useEffect(() => {
    setIsLoading(true);
    getNotifications(page).then(() => {
      setIsLoading(false);
    });
  }, [dispatch, page, getNotifications]);

const getNotifications = useCallback(async () => {
    setError(null);
    setIsLoading(true);
    try {
      console.log("Get from page " + page);
      // let fromRecord = (page - 1) * 7;
      const retrievedNotifications = await getNotificationList(
        page,
        7,
        true,
        false
      );

      setNotifications(prevNotification => prevNotification.concat(
        retrievedNotifications &&
          retrievedNotifications.response &&
          retrievedNotifications.response.notifications
      ));
    } catch (err) {
      setError(err.message);
    }
    setIsLoading(false);
  }, [dispatch, setIsLoading, setNotifications, setError]);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM