添加或删除带有 apollo 缓存和反应变量的项目后 UI 不更新

时间:2021-07-06 13:02:28

标签: react-native caching graphql apollo apollo-client

在使用 apollo 缓存的 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 没有更新。当我关闭我的应用程序时,不会显示缓存的最后状态。

这是我的浏览器屏幕

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;

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

2 个答案:

答案 0 :(得分:0)

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

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

答案 1 :(得分:0)

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