UI 在使用 apollo 缓存和反应变量添加或删除项目后不更新

UI not updating after adding or removing items with apollo cache and reactive variables

我很难在我的 react native 应用程序上添加或删除项目 后更新我的 UI 阿波罗缓存。

稍微解释一下。我有一个 explorer Screen 其中显示了一些带有 切换 订阅 取消订阅 的项目.在另一个名为“订阅屏幕”的屏幕上,我应该显示所有我最喜欢的项目。所以我创建了一个名为 allFavoritesVar 的反应变量,我可以在其中添加或删除项目。

所以在我的cache.js中我有:

import {InMemoryCache} from '@apollo/client/core';
import {makeVar} from '@apollo/client';

export const allFavoritesVar = makeVar([]);

export const cache = new InMemoryCache({
  Query: {
    fields: {
      userFavorites: {
          read() {
            return allFavoritesVar();
          },
        },
    }
  }
})

因此,在我的资源管理器屏幕上,我正在检查 allFavoritesVar 中是否存在每个项目以使切换变为红色并通知用户这些项目已经在他们的“订阅屏幕”中。

const favExists = (flux) => {
    if (allFavoritesVar().filter((item) => item.id === flux.id).length > 0) {
      return true;
    }
    return false;
  }; 

之前使用 redux 并切换到 apollo 因为我需要在用户打开他们的应用程序时保留缓存。使用 redux 一切都变得简单得多,切换工作正常,在从商店添加或删除项目时变成红色或灰色,并且“订阅屏幕”正在自我更新。

现在,当我切换时,突变起作用了,我可以看到添加或删除了项目,但我的 ui 没有更新。当我关闭我的应用程序时,不会显示缓存的最后状态。

这是我的 Explorer 屏幕 :

import React, {useEffect, useState} from 'react';
import {
  SafeAreaView,
  StyleSheet,
  Dimensions,
  ScrollView,
  TouchableOpacity,
  Image,
  FlatList,
  ActivityIndicator,
} from 'react-native';
import {
  NetworkStatus,
  useLazyQuery,
  useMutation,
  useQuery,
} from '@apollo/client';
import {useSelector, useDispatch} from 'react-redux';
import {Box, Text} from 'react-native-design-utility';
import {Notifier} from 'react-native-notifier';
import {useTheme} from '@react-navigation/native';
import ErrorIcon from 'react-native-vector-icons/Ionicons';
import RefreshIcon from 'react-native-vector-icons/Ionicons';
import {theme} from '../theme/theme';
import Loading from '../components/Loading';
import CustomNotifier from '../components/CustomNotifier';
import CustomNotifierError from '../components/CustomNotifierError';
import SubscribeItem from '../components/SubscribeItem';
import {
  SUBSCRIBE_FLUXGROUP_MUTATION,
  SUBSCRIBE_FLUX_MUTATION,
  UNSUBSCRIBE_FLUXGROUP_MUTATION,
  UNSUBSCRIBE_FLUX_MUTATION,
} from '../graphql/mutations/fluxMutations';
import {
  GET_EXPLORER_CATEGORIES_QUERY,
  GET_EXPLORER_SLIDES_QUERY,
} from '../graphql/queries/explorerQueries';
import ToggleIcon from '../components/ToggleIcon';
import {HEIGHT} from '../utils/constants';
import {ALL_FAVORITES_QUERY} from '../graphql/queries/userQueries';
import {allFavoritesVar, cache} from '../utils/cache';
import {FLUX_QUERY} from '../graphql/queries/fluxesQueries';

const WIDTH = Dimensions.get('window').width;
const PAGE_SIZE = 10;

const ExplorerScreen = ({navigation}) => {
  const {colors, dark} = useTheme();
  const [limit, setLimit] = useState(PAGE_SIZE);
  const [isError, setError] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const {
    data: explorerData,
    loading: explorerLoading,
    error,
    refetch,
  } = useQuery(GET_EXPLORER_CATEGORIES_QUERY, {
    fetchPolicy: 'cache-first',
    errorPolicy: 'all',
  });
  const {data: favoritesData, loading: favLoading} =
    useQuery(ALL_FAVORITES_QUERY);
  const {data: slidesData, loading: slidesLoading} = useQuery(
    GET_EXPLORER_SLIDES_QUERY,
    {
      fetchPolicy: 'cache-first',
    },
  );
  const [subscribeToFlux] = useMutation(SUBSCRIBE_FLUX_MUTATION);
  const [subscribeToFluxGroup] = useMutation(SUBSCRIBE_FLUXGROUP_MUTATION);
  const [unsubscribeFromFlux] = useMutation(UNSUBSCRIBE_FLUX_MUTATION);
  const [unsubscribeFromFluxGroup] = useMutation(
    UNSUBSCRIBE_FLUXGROUP_MUTATION,
  );

  const addFav = (flux) => {
    const explorerFav = allFavoritesVar([...allFavoritesVar(), flux]);
    console.log('explorerFav: ', explorerFav);
    return explorerFav;
  };

  const favExists = (flux) => {
    if (allFavoritesVar().filter((item) => item.id === flux.id).length > 0) {
      return true;
    }
    return false;
  };

  const handleAddFavorite = async (flux) => {
    if (flux.__typename === 'FluxGroup') {
      addFav(flux);
      Notifier.showNotification({
        title: 'Vous êtes abonné à ce groupe de flux',
        Component: CustomNotifier,
        componentProps: {
          alertType: 'info',
        },
      });
      await subscribeToFluxGroup({
        variables: {
          id: parseInt(flux.id),
          frequency: 'all',
        },
      });
    } else {
      addFav(flux);
      Notifier.showNotification({
        title: 'Vous êtes abonné à ce flux',
        Component: CustomNotifier,
        componentProps: {
          alertType: 'info',
        },
      });
      await subscribeToFlux({
        variables: {
          id: parseInt(flux.id),
          frequency: 'all',
        }
      });
    }
  };

  const handleRemoveFavorite = async (flux) => {
    if (flux.__typename === 'FluxGroup') {
      Notifier.showNotification({
        title: 'Vous êtes désabonné de ce groupe de flux',
        Component: CustomNotifierError,
        componentProps: {
          alertType: 'error',
        },
      });
      await unsubscribeFromFluxGroup({
        variables: {
          id: parseInt(flux.id),
        },
        update: (cache, {data}) => {
          const existingFavs = cache.readQuery({
            query: ALL_FAVORITES_QUERY,
          });
          //console.log('DATA UPDATE:', data);
          const newFavs = existingFavs.userFavorites.filter(
            (item) => item.id !== flux.id,
          );
          console.log('DATA UPDATE:', newFavs);
          cache.writeQuery({
            query: ALL_FAVORITES_QUERY,
            data: {userFavorites: [newFavs, ...existingFavs.userFavorites]},
          });
        },
      });
    } else {
      Notifier.showNotification({
        title: 'Vous êtes désabonné de ce flux',
        Component: CustomNotifierError,
        componentProps: {
          alertType: 'error',
        },
      });
      await unsubscribeFromFlux({
        variables: {
          id: parseInt(flux.id),
        },
        update: (cache, {data}) => {
          const existingFavs = cache.readQuery({
            query: ALL_FAVORITES_QUERY,
          });
          //console.log('DATA UPDATE:', data);
          const newFavs = existingFavs.userFavorites.filter(
            (item) => item.id !== flux.id,
          );
          console.log('DATA UPDATE:', newFavs);
          cache.writeQuery({
            query: ALL_FAVORITES_QUERY,
            data: {userFavorites: [newFavs, ...existingFavs.userFavorites]},
          });
        },
      });
    }
  };

  function sliceIntoChunks(arr, chunkSize) {
    const res = [];
    for (let i = 0; i < arr.length; i += chunkSize) {
      const chunk = arr.slice(i, i + chunkSize);
      res.push(chunk);
    }
    return res;
  }

  useEffect(() => {
    if (error) {
      setIsLoading(true);
      setError(error.message);
      setIsLoading(false);
    }
  }, [error]);

  const SeeMore = ({onPress}) => {
    return (
      <TouchableOpacity onPress={onPress}>
        <Text
          size={15}
          mr="sm"
          color={dark ? 'primary' : colors.text}
          style={styles.letSpacing}>
          Tout Voir
        </Text>
      </TouchableOpacity>
    );
  };

  const renderHeader = () => {
    if (slidesLoading) {
      return (
        <ScrollView
          horizontal
          showsHorizontalScrollIndicator={false}
          contentContainerStyle={{
            paddingHorizontal: theme.space.sm,
            paddingTop: theme.space.sm,
            height: HEIGHT / 4.8,
            justifyContent: 'center',
            alignItems: 'center',
            width: WIDTH,
          }}>
          <ActivityIndicator color={theme.color.primary} size={24} />
        </ScrollView>
      );
    }
    return (
      <>
        <ScrollView
          horizontal
          showsHorizontalScrollIndicator={false}
          contentContainerStyle={{
            paddingHorizontal: theme.space.sm,
            paddingTop: theme.space.sm,
            height: HEIGHT / 4.8,
          }}>
          {slidesData.explorer_slides.map((slide) => {
            const type = slide.item.__typename;
            return (
              <TouchableOpacity
                key={slide.id}
                onPress={() =>
                  navigation.navigate(
                    type === 'Flux'
                      ? 'SingleFlux'
                      : type === 'FluxGroup'
                      ? 'MultipleFlux'
                      : 'FluxCategory',
                    {
                      headerTitle: slide.item.name,
                      headerItem: slide.item,
                      itemId: slide.item.id,
                      headerText:
                        slide.item.__typename !== 'FluxCategory'
                          ? slide.item.description
                          : null,
                    },
                  )
                }>
                <Box
                  mx="xs"
                  bg="primary"
                  w={WIDTH - 120}
                  h={150}
                  radius="sm"
                  align="center"
                  justify="center"
                  overflow="hidden">
                  <Image
                    source={{uri: slide.image.uri}}
                    style={styles.imgCat}
                    resizeMode="cover"
                  />
                </Box>
              </TouchableOpacity>
            );
          })}
        </ScrollView>
        <Box mt="md" h={1} w={WIDTH} bg={dark ? 'grey' : 'lightBorder'} />
      </>
    );
  };

  const renderItem = ({item, index}) => {
    return (
      <Box key={item - index} mb={8}>
        {item.map((section, index) => {
          const multiple = section.__typename === 'FluxGroup';
          const subscribed = section.subscribed;
          return (
            <TouchableOpacity
              key={section.id}
              onPress={() =>
                !multiple
                  ? navigation.navigate('SingleFlux', {
                      headerTitle: section.name,
                      itemId: section.id,
                      headerItem: section,
                      subscribed: subscribed,
                      itemExist: exists(section),
                    })
                  : navigation.navigate('MultipleFlux', {
                      headerTitle: section.name,
                      itemId: section.id,
                      headerItem: section,
                      subscribed: subscribed,
                      itemExist: exists(section),
                    })
              }>
              <SubscribeItem
                flux={section}
                id={section.id}
                channel={section.name}
                title={
                  section.description
                    ? section.description
                    : `Toutes les actualités sur ${section.name}`
                }
                icon={section.image?.uri ? `${section.image?.uri}` : null}
                custom={section.customChannel}
                pushNumber={section.frequency_numbers_all}
                multiple={multiple}
                button={
                  <>
                    {/* <ToggleIcon
                    favorite={exists(section)}
                    onPress={() =>
                      exists(section)
                        ? handleRemoveFavorite(section)
                        : handleAddFavorite(section)
                    }
                  /> */}
                    <ToggleIcon
                      favorite={favExists(section)}
                      onPress={() =>
                        favExists(section)
                          ? handleRemoveFavorite(section)
                          : handleAddFavorite(section)
                      }
                    />
                  </>
                }
              />
            </TouchableOpacity>
          );
        })}
      </Box>
    );
  };

  const renderCategories = () => {
    if (!explorerData) {
      return (
        <Box py="sm">
          <Text mb="sm" center color="lightGrey">
            Catégories en chargement
          </Text>
          <Loading />
        </Box>
      );
    }
    if (explorerData) {
      return explorerData.explorer_categories.map((section) => {
        const sectionData = sliceIntoChunks(section.related, 3);
        return (
          <>
            <Box
              w={WIDTH}
              key={section.id}
              dir="row"
              justify="between"
              align="center">
              <Text
                size="xl"
                pt="sm"
                pb="2xs"
                ml="sm"
                color={dark ? 'white' : 'black'}
                style={styles.header}>
                {section.name}
              </Text>
              <SeeMore
                onPress={() =>
                  navigation.navigate('FluxCategory', {
                    headerTitle: section.name,
                    headerItem: section,
                    itemId: section.id,
                    headerText: null,
                  })
                }
              />
            </Box>
            <Box>
              <FlatList
                horizontal
                pagingEnabled={true}
                showsHorizontalScrollIndicator={false}
                contentContainerStyle={styles.contentContainerStyle}
                data={section ? sectionData : []}
                renderItem={renderItem}
                extraData={favoritesData}
                keyExtractor={(item, index) => item + index}
                onEndReachedThreshold={0}
              />
              <Box h={1} bg={dark ? 'grey' : 'lightBorder'} mb="sm" />
            </Box>
          </>
        );
      });
    }
  };

  if (error) {
    return (
      <Box f={1} justify="center" align="center">
        <Box mb="xs">
          <ErrorIcon
            name="cloud-offline-outline"
            color={dark ? theme.color.lightGrey : 'grey'}
            size={32}
          />
        </Box>
        <Text
          size="md"
          center
          color={dark ? 'lightGrey' : 'grey'}
          style={styles.letSpacing}>
          Une erreur s'est produite
        </Text>
        <Text
          size="sm"
          color={dark ? 'lightGrey' : 'grey'}
          style={styles.letSpacing}>
          Réessayez plus tard
        </Text>
        <TouchableOpacity onPress={() => refetch()}>
          <Box mt="sm">
            <RefreshIcon name="refresh" size={24} color={theme.color.primary} />
          </Box>
        </TouchableOpacity>
      </Box>
    );
  }

  if (isLoading) {
    return <Loading />;
  }

  return (
    <SafeAreaView
      style={[styles.container, {backgroundColor: colors.background}]}>
      <ScrollView showsVerticalScrollIndicator={false}>
        <Box>{renderHeader()}</Box>
        {renderCategories()}
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    width: WIDTH,
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  searchBar: {
    width: WIDTH,
    backgroundColor: theme.color.secondary,
    borderBottomColor: theme.color.secondary,
    borderTopColor: theme.color.secondary,
  },
  inputBar: {
    backgroundColor: theme.color.black,
    borderRadius: theme.space.md,
  },
  header: {
    fontFamily: 'System',
    fontWeight: '700',
    letterSpacing: 0,
  },
  icon: {
    width: 25,
    height: 25,
    borderRadius: 6,
    backgroundColor: theme.color.primary,
    overflow: 'hidden',
  },
  iconNull: {
    width: 25,
    height: 25,
    borderRadius: 6,
    backgroundColor: theme.color.primary,
    overflow: 'hidden',
  },
  imgCat: {
    width: '100%',
    height: 150,
  },
  letSpacing: {
    letterSpacing: 0,
  },
});
export default ExplorerScreen;

我错过了什么吗?还是我做的完全错了哈哈? 如果您需要有关我的代码的更多信息,请随时询问 :)

试试这个。 UI 将在您删除项目或添加项目后立即刷新:

    await unsubscribeFromFlux({
      variables: {
        id: parseInt(flux.id),
      },
      refetchQueries: GET_EXPLORER_SLIDES_QUERY
    });

事实证明,您不能持久化反应变量!所以我只是在突变后重新获取查询并更新我的缓存 :) 现在一切都很好!谢谢!