'Could not identify object - getting error in graphql apollo client

I'm new to graphql I'm building real time chat app. Currently I'm making it offline first. Using react as front-end.

I'm currently caching the data on localStorage using apollo3-cache-persist. But How do I query the cache data instead of server (when I'm offline) also I want to add messages to the localStorage while I'm offline.

Display the optimistic response when the device is online I want to send the pending data to the server.

my ApolloProvider.js file in client folder

import React from "react";
import {
  ApolloClient,
  InMemoryCache,
  ApolloProvider as Provider,
  createHttpLink,
  ApolloLink,
  split,
} from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import { RetryLink } from "@apollo/client/link/retry";
import { persistCache, LocalStorageWrapper } from "apollo3-cache-persist";
import { WebSocketLink } from "@apollo/client/link/ws";
import { getMainDefinition } from "@apollo/client/utilities";
import QueueLink from "apollo-link-queue";

let httpLink = createHttpLink({
  uri: "http://localhost:4000/",
});

const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem("token");
  // return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    },
  };
});

httpLink = authLink.concat(httpLink);

const wsLink = new WebSocketLink({
  uri: `ws://localhost:4000/`,
  options: {
    reconnect: true,
    connectionParams: {
      Authorization: `Bearer ${localStorage.getItem("token")}`,
    },
  },
});
const link = new RetryLink();
const queueLink = new QueueLink();
const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === "OperationDefinition" &&
      definition.operation === "subscription"
    );
  },
  wsLink,
  httpLink
);

const cache = new InMemoryCache();

const fun = async () =>
  await persistCache({
    cache,
    storage: new LocalStorageWrapper(window.localStorage),
  });
fun();

const client = new ApolloClient({
  // link: splitLink,
  link: ApolloLink.from([splitLink, queueLink, link]),
  cache,
  name: "chat-app",
  version: "1.0.0",
  queryDeduplication: false,
  defaultOptions: {
    watchQuery: {
      fetchPolicy: "cache-and-network",
    },
  },
});

export default function ApolloProvider(props) {
  return <Provider client={client} {...props} />;
}

my messages.js file

import React, { Fragment, useEffect, useState } from "react";
import { gql, useLazyQuery, useMutation, InMemoryCache } from "@apollo/client";
import { Col, Form } from "react-bootstrap";
import { useMessageDispatch, useMessageState } from "../../context/message";
import uuid from "react-uuid";
import Message from "./Message";

const SEND_MESSAGE = gql`
  mutation sendMessage($uuid: String, $to: String!, $content: String!) {
    sendMessage(uuid: $uuid, to: $to, content: $content) {
      uuid
      from
      to
      content
      createdAt
      hasSeen
      hasSent
    }
  }
`;

const GET_MESSAGES = gql`
  query getMessages($from: String!) {
    getMessages(from: $from) {
      uuid
      from
      to
      content
      createdAt
      hasSeen
    }
  }
`;

export default function Messages() {
  const { users } = useMessageState();
  const dispatch = useMessageDispatch();
  const [content, setContent] = useState("");

  const selectedUser = users?.find((u) => u.selected === true);
  const messages = selectedUser?.messages;

  const [getMessages, { loading: messagesLoading, data: messagesData }] =
    useLazyQuery(GET_MESSAGES, {
      update(cache) {
        cache.readFragment({});
        console.log("reading");
      },
    });

  const [sendMessage] = useMutation(SEND_MESSAGE, {
    update(cache, { data: { sendMessage } }) {
      cache.modify({
        fields: {
          getMessages(existingMsg) {
            console.log(existingMsg);
            const newMsgRef = cache.writeFragment({
              data: sendMessage,
              fragment: gql`
                fragment sendNewMessage on Mutation {
                  uuid
                  to
                  from
                  content
                  hasSeen
                  hasSent
                }
              `,
            });
            return existingMsg.push(newMsgRef);
          },
        },
      });
    },
    onError: (err) => console.log(err),
  });

  useEffect(() => {
    if (selectedUser && !selectedUser.messages) {
      getMessages({ variables: { from: selectedUser.username } });
    }
  }, [selectedUser]);

  useEffect(() => {
    if (messagesData) {
      dispatch({
        type: "SET_USER_MESSAGES",
        payload: {
          username: selectedUser.username,
          messages: messagesData.getMessages,
        },
      });
    }
  }, [messagesData]);

  const submitMessage = (e) => {
    e.preventDefault();
    if (content.trim() === "" || !selectedUser) return;
    let id = uuid();

    sendMessage({
      variables: { uuid: id, to: selectedUser.username, content },
      optimisticResponse: {
        sendMessage: {
          __typename: "Mutation",
          uuid: id,
          from: "User",
          to: selectedUser.username,
          content,
          hasSent: false,
          hasSeen: false,
          createdAt: Date.now(),
        },
      },
    });

    setContent("");
  };

  // Displaying helper text and styling
  let selectedChatMarkup;
  if (!messages && !messagesLoading) {
    selectedChatMarkup = <p className="info-text"> Select a friend</p>;
  } else if (messagesLoading) {
    selectedChatMarkup = <p className="info-text"> Loading..</p>;
  } else if (messages.length > 0) {
    selectedChatMarkup = messages.map((message, index) => (
      <Fragment key={message.uuid}>
        <Message message={message} />
        {index === messages.length - 1 && (
          <div className="invisible">
            <hr className="m-0" />
          </div>
        )}
      </Fragment>
    ));
  } else if (messages.length === 0) {
    selectedChatMarkup = (
      <p className="info-text">
        You are now connected! send your first message!
      </p>
    );
  }

  return (
    <Col xs={10} md={8}>
      <div className="messages-box d-flex flex-column-reverse">
        {selectedChatMarkup}
      </div>
      <div>
        <Form onSubmit={submitMessage}>
          <Form.Group className="d-flex align-items-center">
            <Form.Control
              type="text"
              className="message-input rounded-pill p-4 bg-secondary border-0"
              placeholder="Type a message.."
              value={content}
              onChange={(e) => setContent(e.target.value)}
            />
            <i
              className="fas fa-regular fa-paper-plane fa-2x text-primary ml-2"
              onClick={submitMessage}
              role="button"
            ></i>
          </Form.Group>
        </Form>
      </div>
    </Col>
  );
}

But I'm currently getting this error when I try to send the message

react_devtools_backend.js:3973 Invariant Violation: Could not identify object {"uuid":"4855ffc-6b7b-d7c8-a68-2ae0162f80a","from":"User","to":"Fire","content":"example text","createdAt":1648881891383,"hasSeen":false,"hasSent":false,"__typename":"Mutation"}

Also getting error from the mutation error log

Error: Could not identify object {"__typename":"Message","uuid":"4855ffc-6b7b-d7c8-a68-2ae0162f80a","from":"Alan","to":"Fire","content":"example text","createdAt":"2022-04-02T06:44:51.807Z","hasSeen":false,"hasSent":false}


Solution 1:[1]

Apollo uses by default __typename and id fields to normalise cache. So in your case, Apollo won't recognise your uuid property as a cache identifier. You can change the uuid uuid property, or add keyFields to your InMemoryCache config.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Gijs Lebesque