Search code examples
reactjscachinggraphqlapollo-clientpostgraphile

Apollo-Client - Error: Could not identify object


Client - React, Apollo-Client, codegen

Server - Postgraphile

The app view list of file objects.

Users can add new file.

I wrote a Query for getting the file objects from the server.

And a Mutation for adding new file.

they both works.

what Im trying to achieve is updating the cached files with the new file, and re-render the component that contain the FilesQuery hook.

I followed this doc: https://www.apollographql.com/docs/react/data/mutations/#the-update-function

but calling cache.writeFragment keep throwing the error:

Error: Could not identify object {"id":"de5b015b-28a8-4066-a5ed-44dcead659ed","createdAt":"2022-10-14T14:51:14.874548645","updatedAt":"2022-10-14T14:51:14.874550045","originalFileName":"MY_FILE.xlsx",...}

Files Query

query FileUploads(
  $offset: Int = 0
  $first: Int = 10
  $orderBy: [FileUploadsOrderBy!] = [CREATED_AT_DESC]
  $filter: FileUploadFilter
) {
  fileUploads(
    first: $first
    offset: $offset
    orderBy: $orderBy
    filter: $filter
  ) {
    totalCount
    nodes {
      id
      uploaderOrgId
      uploadFileType
      counterPartiesCount
      parsedPointsCount
      effectiveDate
      originalFileName
      configFileName
      createdAt
      status
      partiesIds
      parsedFileUrl
      uploadingOrg {
        id
        shortName
      }
      entitiesAndCps {
        entities {
          id
          shortName
        }
        cps {
          id
          shortName
        }
      }
    }
  }
}

Table Compoent (Calls FileUploadsQuery)

const FileUploadsTable = () => {
  const { data, loading, error } = useFileUploadsQuery();
...
}

Mutation - Update

update(cache, { data }) {
      cache.modify({
        fields: {
          fileUploads(existingFileUploads = {}) {
            const newFileUplodsRefs = (
              data?.uploadToService?.result as any[]
            ).map((file) => {
              return cache.writeFragment({
                data: file,
                fragment: gql`
                  fragment NewFileUpload on FileUpload {
                    id
                    uploaderOrgId
                    uploadFileType
                    counterPartiesCount
                    parsedPointsCount
                    effectiveDate
                    originalFileName
                    configFileName
                    createdAt
                    status
                    uploadingOrg {
                      id
                      shortName
                    }
                    entitiesAndCps {
                      entities {
                        id
                        shortName
                      }
                      cps {
                        id
                        shortName
                      }
                    }
                  }
                `,
              });
            });
            return [
              ...(newFileUplodsRefs as any[]),
              ...(existingFileUploads?.nodes || []),
            ];
          },
        },
      });
    },

EDIT 1

i managed to pass the error by passing:

cache.writeFragment({
                id: `FileUpload:${file.id}`, <--
                data: file,
                fragment: gql`
                  fragment NewFileUpload on FileUpload {
                    id
                  }
                `,
              });

also changed the return object to:

return {
              nodes: [
                ...newFileUplodsRefs,
                ...(existingFileUploads?.nodes || []),
              ],
            };

still i don't see the re-render, in the component that display the FileUploads.

(the component is FileUploadsTable)


Solution

  • Solution

    apolloClient

    import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
    import isEmpty from 'lodash/isEmpty';
    
    const cache = new InMemoryCache({
      typePolicies: {
        Query: {
          fields: {
            shells: {
              keyArgs: (args, context) => {
                const {
                  variables: { filter, runFilter } = {},
                  fieldName,
                } = context;
    
                return `${fieldName}${
                  isEmpty(filter) && isEmpty(runFilter)
                    ? ''
                    : ':' + JSON.stringify({ filter, runFilter })
                }`;
              },
              merge(existing = [], incoming, { args, cache, variables }) {
                const { offset = 0 } = args;
                if (Array.isArray(existing)) {
                  return incoming;
                }
    
                const merged = { ...existing, nodes: existing.nodes.slice(0) };
                for (let i = 0; i < incoming.nodes.length; ++i) {
                  merged.nodes[offset + i] = incoming.nodes[i];
                }
                return merged;
              },
            },
          },
        },
      },
    });
    
    export const apolloClient = new ApolloClient({
      cache,
      link: new HttpLink({
        uri: `${
          typeof window === 'undefined' ? 'http://localhost:3009' : window.origin
        }/frm/api/graphql`,
      }),
      defaultOptions: {
        query: {
          fetchPolicy: 'cache-first',
        },
      },
    });
    

    Shell Component

      const loadMoreShells = async (page: number) => {
        setIsPageLoading(true);
        await fetchMore({
          variables: {
            first: SHELLS_PER_PAGE,
            offset: SHELLS_PER_PAGE * (page - 1),
            filter: { ...filter, ...{ pinned: { equalTo: false } } },
            runFilter: runFilter ? { run: runFilter } : undefined,
            orderBy: [
              STATUS_ASC,
              SHELL_RUNS_MAX_CREATED_AT_DESC,
              EXECUTION_FROM_DESC,
            ],
          },
        });
      };