Cover Image for Apollo क्लाइंट में Relay-शैली का उपयोग करके GraphQL में पेजिनेशन समस्याओं का समाधान
[Apollo][GraphQL][Relay][React]
26 जून 2024

Apollo क्लाइंट में Relay-शैली का उपयोग करके GraphQL में पेजिनेशन समस्याओं का समाधान

GraphQL एक शक्तिशाली उपकरण है जिसमें कई लाभ हैं। हालांकि, किसी भी उपकरण की तरह, इसमें भी कमजोरियाँ हैं, और उनमें से एक है पेजिनेशन। यह हमेशा बॉक्स से बाहर काम नहीं करता है, इसके लिए कस्टम कैश मर्ज फ़ंक्शन और म्यूटेशन के बाद मैन्युअल अपडेट की आवश्यकता होती है। इस लेख में, हम चैट संदेशों के उदाहरण का उपयोग करके relay-शैली पेजिनेशन का अन्वेषण करेंगे।

Apollo क्लाइंट में Relay-शैली पेजिनेशन को लागू करना

Apollo क्लाइंट में relay-शैली पेजिनेशन के लिए एक अंतर्निहित कैश मर्ज फ़ंक्शन है। आप इसे यहाँ पा सकते हैं।

इसे अपने प्रोजेक्ट में एकीकृत करने के लिए, अपने InMemoryCacheConfig में typePolicies कॉन्फ़िगरेशन में relay-शैली पेजिनेशन फ़ंक्शन जोड़ें:

export const cache = new InMemoryCache({
  typePolicies: {
    Dialog: {
      fields: {
        messages: relayStylePagination([]),
      },
    },
  },
});

OptimisticResponse और कैश अपडेट को संभालना

यदि आप optimisticResponse जोड़ना चाहते हैं और update में कैश को अपडेट करना चाहते हैं, तो आप इसे पेजिनेशन और कर्सर के साथ कैसे जोड़ सकते हैं?

आशावादी अपडेट

optimisticResponse आपको अस्थायी रूप से कैश में डेटा जोड़ने की अनुमति देता है जैसे कि अनुरोध पहले ही सफल हो गया हो। यह एक सहज और उत्तरदायी उपयोगकर्ता इंटरफ़ेस बनाने के लिए उपयोगी है।

optimisticResponse का उपयोग करने का उदाहरण:

const [sendMessage] = useMutation(SEND_MESSAGE_MUTATION, {
  optimisticResponse: {
    __typename: "Mutation",
    sendMessage: {
      __typename: "Message",
      id: "temp-id", // अस्थायी पहचानकर्ता
      text: newMessageText,
      createdAt: new Date().toISOString(),
      sender: currentUser,
    },
  },
  update(cache, { data: { sendMessage } }) {
    // म्यूटेशन के बाद कैश अपडेट लॉजिक
  },
});

update के लिए cache.modify फ़ंक्शन लिखना

हम अपने cache.modify के लिए update के लिए चरण-दर-चरण वर्णन करेंगे।

  1. update पैरामीटर के साथ एक म्यूटेशन बनाएं:

    const [mutation] = useMutation(SEND_MESSAGE_MUTATION, {
      update(cache, { data }) {
        if (data?.sendMessage) {
          // कैश अपडेट कोड यहाँ होगा
        }
      },
    });
  2. हमारे संदेश संवाद के लिए dataId प्राप्त करें:

    const dataId = cache.identify({
      __typename: dialog.__typename,
      id: dialog.id,
    });
  3. जाँचें कि dataId प्राप्त हुआ है या नहीं:

    if (!dataId) return;
  4. कैश को संशोधित करें:

    cache.modify<Dialog>({
      id: dataId,
      fields: {
        messages(existingMessages, { isReference, readField }) {
          // संदेश अपडेट लॉजिक यहाँ होगा
        },
      },
    });
  5. मौजूदा संदेशों की एक प्रति बनाएँ:

    const existingEdges = (existingMessages.edges || []).slice();
  6. कर्सर के साथ भ्रम से बचने के लिए पहले संदेश से कर्सर को हटा दें:

    const lastMessage = { ...existingEdges[0] };
    const cursor = isReference(existingEdges[0])
      ? readField<string>("cursor", existingEdges[0])
      : existingEdges[0].cursor;
    delete lastMessage.cursor;
    existingEdges[0] = lastMessage;
  7. एक नया संदेश बनाएँ और इसे सूची की शुरुआत में जोड़ें:

    const edge = {
      __typename: "MessageEdge",
      cursor,
      node: sendMessage,
    };
    existingEdges.unshift(edge);
  8. संदेशों की अद्यतन सूची लौटाएँ:

    return {
      ...existingMessages,
      edges: existingEdges,
    };

update फ़ंक्शन का पूरा उदाहरण

const [mutation] = useMutation(SEND_MESSAGE_MUTATION, {
  update(cache, { data }) {
    if (data?.sendMessage) {
      try {
        const dataId = cache.identify({
          __typename: dialog.__typename,
          id: dialog.id,
        });

        if (!dataId) return;

        cache.modify<Dialog>({
          id: dataId,
          fields: {
            messages(existingMessages, { isReference, readField }) {
              const existingEdges = (existingMessages.edges || []).slice();
              const lastMessage = { ...existingEdges[0] };
              const cursor = isReference(existingEdges[0])
                ? readField<string>("cursor", existingEdges[0])
                : existingEdges[0].cursor;
              delete lastMessage.cursor;
              existingEdges[0] = lastMessage;

              const edge = {
                __typename: "MessageEdge",
                cursor,
                node: sendMessage,
              };
              existingEdges.unshift(edge);

              return {
                ...existingMessages,
                edges: existingEdges,
              };
            },
          },
        });
      } catch (error) {
        console.error("कैश अपडेट करते समय त्रुटि:", error);
      }
    }
  },
});

कैश अपडेट करते समय संदेशों को डुप्लिकेट करना

नए डेटा प्राप्त करते समय, आपको कैश में डुप्लिकेट संदेशों की समस्या का सामना करना पड़ सकता है। इससे बचने के लिए, हम relayStylePagination फ़ंक्शन में डुप्लिकेशन जोड़ेंगे।

डुप्लिकेशन के साथ relayStylePagination फ़ंक्शन

  1. आवश्यक मॉड्यूल आयात करें और प्रकारों को परिभाषित करें:

    import { __rest } from "tslib";
    import { FieldPolicy, Reference } from "@apollo/client";
    import {
      RelayFieldPolicy,
      TExistingRelay,
      TRelayEdge,
      TRelayPageInfo,
    } from "@apollo/client/utilities/policies/pagination";
    import { mergeDeep } from "@apollo/client/utilities";
    import { ReadFieldFunction } from "@apollo/client/cache/core/types/common";
    
    type KeyArgs = FieldPolicy<any>["keyArgs"];
  2. सहायक फ़ंक्शन परिभाषित करें:

    • अतिरिक्त फ़ील्ड प्राप्त करने के लिए फ़ंक्शन:

      const notExtras = ["edges", "pageInfo"];
      const getExtras = (obj: Record<string, any>) => __rest(obj, notExtras);
    • एक खाली डेटा ऑब्जेक्ट बनाने के लिए फ़ंक्शन:

      function makeEmptyData(): TExistingRelay<any> {
        return {
          edges: [],
          pageInfo: {
            hasPreviousPage: false,
            hasNextPage: true,
            startCursor: "",
            endCursor: "",
          },
        };
      }
    • एज नोड आईडी प्राप्त करने के लिए फ़ंक्शन:

      type IsReferenceFunction = (obj: any) => obj is Reference;
      
      type GetEdgeNodeIdPayload = {
        edge: TRelayEdge<Reference>;
        isReference: IsReferenceFunction;
        readField: ReadFieldFunction;
        idKey?: string;
      };
      
      function getEdgeNodeId({
        edge,
        isReference,
        readField,
        idKey,
      }: GetEdgeNodeIdPayload): string | undefined {
        const node = isReference(edge)
          ? readField<string>("node", edge)
          : edge.node;
      
        if (node) {
          return isReference(node)
            ? readField<string>(idKey || "id", node)
            : (node as any)?.id;
        }
        return undefined;
      }
  3. डुप्लिकेशन के साथ relayStylePagination फ़ंक्शन:

    export function relayStylePagination<TNode extends Reference = Reference>(
      keyArgs: KeyArgs = false,
      idKey?: string
    ): RelayFieldPolicy<TNode> {
      return {
        keyArgs,
    
        read(existing, { canRead, readField }) {
          if (!existing) return existing;
    
          const edges: TRelayEdge<TNode>[] = [];
          let firstEdgeCursor = "";
          let lastEdgeCursor = "";
          existing.edges.forEach((edge) => {
            if (canRead(readField("node", edge))) {
              edges.push(edge);
              if (edge.cursor) {
                firstEdgeCursor = firstEdgeCursor || edge.cursor || "";
                lastEdgeCursor = edge.cursor || lastEdgeCursor;
              }
            }
          });
    
          if (edges.length > 1 && firstEdgeCursor === lastEdgeCursor) {
            firstEdgeCursor = "";
          }
    
          const { startCursor, endCursor } = existing.pageInfo || {};
    
          return {
            ...getExtras(existing),
            edges,
            pageInfo: {
              ...existing.pageInfo,
              startCursor: startCursor || firstEdgeCursor,
              endCursor: endCursor || lastEdgeCursor,
            },
          };
        },
    
        merge(existing, incoming, { args, isReference, readField }) {
          if (!existing) {
            existing = makeEmptyData();
          }
    
          if (!incoming) {
            return existing;
          }
    
          const incomingEdges: typeof incoming.edges = [];
          const incomingIds = new Set();
    
          if (incoming.edges) {
            incoming.edges.forEach((edge) => {
              if (isReference((edge = { ...edge }))) {
                edge.cursor = readField<string>("cursor", edge);
              }
    
              const nodeId = getEdgeNodeId({
                edge,
                isReference,
                readField,
                idKey,
              });
              if (nodeId) incomingIds.add(nodeId);
    
              incomingEdges.push(edge);
            });
          }
    
          if (incoming.pageInfo) {
            const { pageInfo } = incoming;
            const { startCursor, endCursor } = pageInfo;
            const firstEdge = incomingEdges[0];
            const lastEdge = incomingEdges[incomingEdges.length - 1];
    
            if (firstEdge && startCursor) {
              firstEdge.cursor = startCursor;
            }
            if (lastEdge && endCursor) {
              lastEdge.cursor = endCursor;
            }
            const firstCursor = firstEdge && firstEdge.cursor;
            if (firstCursor && !startCursor) {
              incoming = mergeDeep(incoming, {
                pageInfo: {
                  startCursor: firstCursor,
                },
              });
            }
          }
    
          let prefix: typeof existing.edges = [];
          let afterIndex = -1;
          let beforeIndex = -1;
    
          existing.edges.forEach((edge, index) => {
            const nodeId = getEdgeNodeId({
              edge,
              isReference,
              readField,
              idKey,
            });
            /**
             * डुप्लिकेट हटाएं
             */
            if (!(nodeId && incomingIds.has(nodeId))) prefix.push(edge);
    
            if (edge.cursor === args?.after) afterIndex = index;
            if (edge.cursor === args?.before) beforeIndex = index;
          });
    
          let suffix: typeof prefix = [];
    
          if (args && args.after) {
            if (afterIndex >= 0) {
              prefix = prefix.slice(0, afterIndex + 1);
            }
          } else if (args && args.before) {
            suffix = beforeIndex < 0 ? prefix : prefix.slice(beforeIndex);
            prefix = [];
          } else if (incoming.edges) {
            prefix = [];
          }
    
          const edges = [...prefix, ...incomingEdges, ...suffix];
    
          const pageInfo: TRelayPageInfo = {
            ...incoming.pageInfo,
            ...existing.pageInfo,
          };
    
          if (incoming.pageInfo) {
            const {
              hasPreviousPage,
              hasNextPage,
              startCursor,
              endCursor,
              ...extras
            } = incoming.pageInfo;
    
            Object.assign(pageInfo, extras);
    
            if (!prefix.length) {
              if (void 0 !== hasPreviousPage)
                pageInfo.hasPreviousPage = hasPreviousPage;
              if (void 0 !== startCursor) pageInfo.startCursor = startCursor;
            }
            if (!suffix.length) {
              if (void 0 !== hasNextPage) pageInfo.hasNextPage = hasNextPage;
              if (void 0 !== endCursor) pageInfo.endCursor = endCursor;
            }
          }
    
          return {
            ...getExtras(existing),
            ...getExtras(incoming),
            edges,
            pageInfo,
          };
        },
      };
    }

डुप्लिकेशन कैसे काम करता है

  1. नए नोड आईडी incomingIds का सेट बनाना:

    जब incoming.edges डेटा को संसाधित किया जाता है, तो प्रत्येक एज नोड के लिए एक आईडी nodeId बनाई जाती है, जिसे incomingIds सेट में जोड़ा जाता है।

    const incomingEdges: typeof incoming.edges = [];
    const incomingIds = new Set();
    
    if (incoming.edges) {
      incoming.edges.forEach((edge) => {
        if (isReference((edge = { ...edge }))) {
          edge.cursor = readField<string>("cursor", edge);
        }
    
        const nodeId = getEdgeNodeId({ edge, isReference, readField, idKey });
        if (nodeId) incomingIds.add(nodeId);
    
        incomingEdges.push(edge);
      });
    }
  2. मौजूदा डेटा existing.edges से डुप्लिकेट हटाना:

    मौजूदा डेटा को संसाधित करते समय, प्रत्येक एज नोड को incomingIds सेट में उपस्थिति के लिए जाँच की जाती है। यदि नोड पहले से ही नए डेटा में मौजूद है, तो इसे परिणाम सरणी prefix में नहीं जोड़ा जाता है।

    let prefix: typeof existing.edges = [];
    
    existing.edges.forEach((edge, index) => {
      const nodeId = getEdgeNodeId({ edge, isReference, readField, idKey });
      if (!(nodeId && incomingIds.has(nodeId))) prefix.push(edge);
    
      if (edge.cursor === args?.after) afterIndex = index;
      if (edge.cursor === args?.before) beforeIndex = index;
    });
  3. बिना डुप्लिकेट के सरणियों को मर्ज करना:

    मौजूदा डेटा से डुप्लिकेट हटाने के बाद, सरणियों prefix, incomingEdges, और suffix को एक नई सरणी edges में संयोजित किया जाता है, जिसे फिर कैश में लौटाया जाता है।

    const edges = [...prefix, ...incomingEdges, ...suffix];

इस प्रकार, हम कैश को अपडेट करते समय डुप्लिकेट संदेशों से बचते हैं, क्लाइंट एप्लिकेशन में डेटा की स्थिरता बनाए रखते हैं।

अगला पढ़ें

हमारे समुदाय से जुड़ें