【问题标题】:Data not merging, Apollo 3 pagination with field policies数据未合并,Apollo 3 分页与字段策略
【发布时间】:2021-02-01 21:19:54
【问题描述】:

这是我的两个文件。我正在尝试用我自己的数据来模拟这个沙盒的结果:https://codesandbox.io/embed/stoic-haze-ispw2?codemirror=1

基本上我可以看到数据已获取并更新缓存,但我的组件 ResourceSection 数据列表未更新。

[更新] 根据反馈进行了一些重大更改。查询已从组件中删除,我创建了一个 skipLimitPagination 函数。查询有效,但我的缓存没有更新或将数据放入其中。

import React from "react";
import { BrowserRouter as Router } from "react-router-dom";
import "./App.css";
import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";
import Home from "./screens";
import { skipLimitPagination } from './utils/utilities'

const client = new ApolloClient({
  uri: `https://graphql.contentful.com/content/v1/spaces/${process.env.REACT_APP_SPACE_ID}/?access_token=${process.env.REACT_APP_CDA_TOKEN}`,
  cache: new InMemoryCache({
   typePolicies: {
     Query: {
       fields: {
         resourceCollection: {items: skipLimitPagination()}
       }
     }
   }
  }),
});

function App() {
  return (
    <ApolloProvider client={client}>
      <Router>
        <Home />
      </Router>
    </ApolloProvider>
  );
}

export default App;
import React, { useState } from "react";
import Navbar from "../components/Navbar";
import MobileNav from "../components/MobileNav";
import HeroSection from "../components/HeroSection";
import FeaturesSection from "../components/FeatureSection";
import Split from "../components/SplitWindow";
import Loading from "../components/Loading";
import { useQuery, gql } from "@apollo/client";
import Resource from "../components/ResourceSection";
import Contact from "../components/ContactSection";
import Footer from "../components/Footer";

const MASS_COLLECTION = gql`
query($skip: Int) {
  resourceCollection(limit: 5, skip: $skip ) {
 items {
   type
   category
   title
   link
   bgColor
   color
 }
},
splitSectionCollection(order: splitId_ASC) {
 items {
   splitId
   lightBg
   left
   lightText
   darkText
   image {
     url
   }
   alt
   heading
   content {
     json
   }
 }
} 

}
`;

const Home = () => {
  const [isOpen, setIsOpen] = useState(false);

  const { loading, error, data, fetchMore } = useQuery(MASS_COLLECTION, {
    variables: {
      skip: 0,
    },
  });

  if (loading) return <Loading />;
  if (error) return <p>Error</p>;

  const toggle = () => {
    setIsOpen(!isOpen);
  };
  return (
    <>
      <MobileNav isOpen={isOpen} toggle={toggle} />
      <Navbar toggle={toggle} />
      <HeroSection />
      <FeaturesSection />
      {data.splitSectionCollection.items.map((item) => {
        return <Split item={item} key={item.splitId} />;
      })}
      <Resource data={data.resourceCollection.items} fetchMore={fetchMore}/>
      <Contact />
      <Footer />
    </>
  );
};

export default Home;

import React, { useState, useCallback } from "react";
import {
  ResourceContainer,
  ResourcesWrapper,
  ResourceRow,
  TextWrapper,
  Column1,
  Heading,
  Content,
  Column2,
  ImgWrap,
  Img,
  Form,
  FormSelect,
  FormOption,
  // LinkContainer,
  // LinkWrapper,
  // LinkIcon,
  // LinkTitle,
  // LoadMore,
  // ButtonWrapper,
} from "./ResourceElements";

const ResourceSection = ({ data, fetchMore }) => {
  console.log(data)

  const handleClick = useCallback(() => {
    fetchMore({
      variables: {
        skip:
          data 
            ? data.length
            : 0,
      },
    });
  }, [fetchMore, data]);

  return (
    <ResourceContainer lightBg={true} id="resource">
      <ResourcesWrapper>
        <ResourceRow left={true}>
          <Column1>
            <TextWrapper>
              <Heading lightText={false}>Resources</Heading>
              <Content darkText={true} className="split_cms">
                Cyber Streets strives in sharing education resources to all.
                Below you can find an exhaustive list of resources covering
                everything from computer programming to enterneurship. "Be
                knowledgeable in your niche, provide some information free of
                charge, and share other trustworthy people's free resources
                whenever possible..." - Heather Hart
              </Content>
            </TextWrapper>
          </Column1>
          <Column2>
            <ImgWrap>
              <Img
                src="/assets/images/Resource.svg"
                alt="Two looking at computer screen svg"
              />
            </ImgWrap>
          </Column2>
        </ResourceRow>
        <Form action="">
          <FormSelect
          // onChange={(e) => {
          //   setCategory(e.target.value);
          //   // setLimit(5);
          // }}
          >
            <FormOption value="">Filter by category</FormOption>
            <FormOption value="MEDIA">Media</FormOption>
            <FormOption value="TEDX">Ted Talks</FormOption>
            <FormOption value="INTERNET SAFETY/AWARENESS">
              Internet safety &amp; awareness
            </FormOption>
            <FormOption value="K-12/COMPUTER SCIENCE">
              k-12 &amp; computer science
            </FormOption>
            <FormOption value="CODING">Programming</FormOption>
            <FormOption value="CYBER/IT OPERATIONS">
              Cyber &and; IT operations
            </FormOption>
            <FormOption value="ROBOTICS">Robotics</FormOption>
            <FormOption value="CLOUD">Cloud</FormOption>
            <FormOption value="SCIENCE">Science</FormOption>
            <FormOption value="PROFESSIONAL DEVELOPMENT">
              Professional Development
            </FormOption>
            <FormOption value="3D PRINTING">3D Printing</FormOption>
            <FormOption value="ART">Art</FormOption>
            <FormOption value="MOOC">Massive Open Online Courses</FormOption>
            <FormOption value="GAMES">Games &amp; Challenges</FormOption>
            <FormOption value="OTHER">Other</FormOption>
          </FormSelect>
        </Form>
        <div className="list">
          {data.map((resource, i) => (
            <div key={resource.title} className="item">
              {resource.title}
            </div>
          ))}
        </div>

        <button className="button" onClick={handleClick}>
          Fetch!
        </button>
      </ResourcesWrapper>
    </ResourceContainer>
  );
};

export default ResourceSection;

点击获取更多按钮后我的缓存。两个独立的资源集合,应该合并吗?我通过 apollo chrome 插件获得了这些信息。

我正在使用内容丰富的 graphql API:

这是我的资源集合参数和字段:

ResourceCollection
ARGS
skip: Int = 0
limit: Int = 100
preview: Boolean
locale: String
where: ResourceFilter
order: [ResourceOrder]

Fields
total: Int!
skip: Int!
limit: Int!
items: [Resource]!
export function skipLimitPagination(keyArgs) {
    return {
      keyArgs,
      merge(existing, incoming, { args }) {
        const merged = existing ? existing.slice(0) : [];
        if (args) {
          const { skip = 0 } = args;
          for (let i = 0; i < incoming.length; ++i) {
            merged[skip + i] = incoming[i];
          }
        } else {
  
          merged.push.apply(merged, incoming);
        }
        return merged;
      },
    };
  }

我已经连续三天研究这个问题了。我尝试了更新查询的旧方法,但它没有按预期工作,所以现在我正在尝试最新的阿波罗技术。请帮忙:(

【问题讨论】:

  • 有人吗? ;(我一定是过于复杂了

标签: graphql apollo apollo-client


【解决方案1】:

根查询的类型只是Query,而不是RootQuery,我认为这就是为什么您对RootQuery.resourceCollection 字段的配置没有任何效果的原因。

换句话说,试试这个:

new InMemoryCache({
  typePolicies: {
    Query: { // not RootQuery
      fields: {
        resourceCollection: offsetLimitPagination(),
      },
    },
  },
})

我还建议您在组件之外创建 RESOURCE_COLLECTION 查询,这样您就不会在每次渲染组件时都创建新的 DocumentNode

【讨论】:

  • 谢谢,澄清一下,我应该将根查询提取到父组件并将数据作为道具传递下来?如果有帮助,这是我的仓库:github.com/MarkellRichards/Cyber-Streets-Front-End
  • 嗯,也许每次创建一个新的 documentNode 都会导致我在使用之前使用的 updateQuery 语法加载更多内容时出现可笑的屏幕闪烁。我现在在工作,但我一回到家就试试这个!祈祷,这是我发布项目之前的最后一件事。
  • 我在午休时间有时间编辑我的代码。我按照你的指示做了,并更新了上面的代码。现在的问题是,当我为 resourceCollection 使用 typePolicies 时,它返回未定义。另请注意:我将两个查询移到一个大查询中。 FetchMore 仍然调用下一个 5 但不更新任何东西....
【解决方案2】:

我遇到了几乎相同的问题并找到了解决方案。问题是 Apollo 站点上的所有示例都假定响应对象的第一个元素是您的项目数组。

这不是 Contentful 的工作方式,数组总是嵌套在集合中的 items 中。例如,您的 resourceCollection 有一个包含您所有资源的属性 items。所以你必须合并items,但返回整个resourceCollection,看起来像这样:

new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        resourceCollection: {
          keyArgs: false,
          merge(existing, incoming) {
            if (!incoming) return existing
            if (!existing) return incoming // existing will be empty the first time 

            const { items, ...rest } = incoming;

            let result = rest;
            result.items = [...existing.items, ...items]; // Merge existing items with the items from incoming
 
            return result
          }
        }
      }
    }
  }
})

这将返回 resourceCollection 与合并的 items

【讨论】:

  • 哇,我忘记了这个问题,但感谢您的炸弹回复。我去看看,几个月没碰那个项目了。
  • 太棒了!绝对有效。在跟踪所有价差操作员的操作时遇到问题,您会考虑对此进行扩展吗?
  • 嗨@Darvanen,很抱歉回复晚了。在这一行const { items, ...rest } = incoming; 中,我们将incoming 的值解构为一个名为items 的常量,并将...rest 传播到其他常量变量中。使用扩展运算符的另一行result.items = [...existing.items, ...items]; 我们创建一个新数组,其值为...existing.items...items。重复值将以这种方式合并在一起。这对你有帮助吗?
  • 感谢您的回复,无论时机如何 :) 我最终还是弄清楚了,但很高兴能用文字表达并确认我认为正在发生的事情
【解决方案3】:

我使用了@Lingertje 的答案一段时间,但一直遇到重复问题,特别是如果我的组件由于用户在没有重新渲染应用程序的情况下再次导航而重新渲染时。

查看 Apollo 客户端文档中的 merging arrays of non-normalised objects

您需要为 ResourceCollection 类型的 items 字段设置字段策略,如下所示(示例底部):

import { InMemoryCache } from '@apollo/client'

const mergeItemsById = (existing: any[], incoming: any[], { readField, mergeObjects }) => {
  const merged: any[] = existing ? existing.slice(0) : [];
  const itemIdToIndex: Record<string, number> = Object.create(null);
  if (existing) {
    existing.forEach((item, index) => {
      itemIdToIndex[readField("id", item)] = index;
    });
  }
  incoming.forEach(item => {
    const id = readField("id", item);
    const index = itemIdToIndex[id];
    if (typeof index === "number") {
      // Merge the new item data with the existing item data.
      merged[index] = mergeObjects(merged[index], item);
    } else {
      // First time we've seen this item in this array.
      itemIdToIndex[id] = merged.length;
      merged.push(item);
    }
  });
  return merged;
}

export const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        resourceCollection: {
          keyArgs: ['preview', 'locale', 'where', 'order'],
        },
      },
    },
    ResourceCollection: {
      fields: {
        items: {
          merge: mergeItemsById
        }
      }
    }
  },
})

如果您在items 上的唯一键不是id,则将“id”的三个引用更改为您的唯一键。

然后只需在您设置客户端的位置导入此缓存定义(当它变得如此大时,它比内联执行要少得多):

const client = new ApolloClient({
  uri: `https://graphql.contentful.com/content/v1/spaces/${process.env.REACT_APP_SPACE_ID}/?access_token=${process.env.REACT_APP_CDA_TOKEN}`,
  cache: ### import the cache object here ###
});

奖励:请参阅我在 Query 类型的 resourceCollection 字段上设置了 keyArgs,这可确保您在更改这些参数时不会重复使用缓存的结果。

【讨论】:

  • 我不得不承认...我并不完全理解合并功能,这主要是从文档 O.o 转载的
猜你喜欢
  • 2020-10-25
  • 2021-02-16
  • 2021-09-01
  • 2018-09-08
  • 1970-01-01
  • 2010-12-04
  • 1970-01-01
  • 2021-09-15
相关资源
最近更新 更多