Состояние не обновляется в React Native

Я делаю страницу уведомлений своего собственного приложения React. Он имеет бесконечную прокрутку и опции "потянуть, чтобы обновить". При входе на страницу он работает, и он также работает с обновлением. Проблема возникает, когда я прокручиваю вниз, потому что кажется, что он вызывает сервер для получения новых уведомлений, но не объединяется с массивом.

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;

Если я прокручиваю до конца, запускается функция fetchMoreNotifications, и я получаю это в консоли:

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

Как видите, там написано "существующий массив из 0 элементов", даже если ранее я сохранял уведомления. Может быть, у него проблемы с зависимостью useCallback?

2 ответа

Решение

Проблема:

Есть 2 основных проблемы, одна с page и второй с notifications, из-за useCallback а также dependencies, функция useCallback всегда будет указывать на старые значения, которые не находятся в зависимостях, пока одна из зависимостей не будет обновлена.


1) Решение page выпуск:

Проходить newPage как параметр getNotifications, из-за асинхронного поведения setPage он не будет обновляться напрямую

А во второй раз, чтобы получить обновленное значение страницы, вы можете передать page как зависимость.

2) Решение notification выпуск:

Обновите уведомление прямо из его предыдущего значения состояния с помощью setState(prevState => newState).


Решение:

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

РАБОЧАЯ ДЕМО:

Проверьте журнал консоли на предмет обновленного значения страницы, и уведомление будет отображаться на HTML.

Отредактируйте nostalgic-sound-r07x2

ПРИМЕЧАНИЕ. Некоторые части кода удалены, чтобы улучшить читаемость кода и устранить проблему.

Проблема действительно простая. Функция getNotifications создается с использованиемuseCallback и не использовал notificationsкак зависимость. Теперь, когда уведомления обновляются, функция getNotication по-прежнему обращается к старым значениям уведомлений из-за закрытия.

Также обратите внимание, что вы вызываете getNotifications на fetchMoreNotifications сразу после установки состояния страницы, но состояние страницы также связано с закрытием и не будет обновляться в том же повторном рендеринге

Решение здесь состоит в том, чтобы использовать функциональный подход для setNotifications и использовать useEffect для запуска 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]);
Другие вопросы по тегам