apollographql / apollo-client

:rocket:  A fully-featured, production ready caching GraphQL client for every UI framework and GraphQL server.
https://apollographql.com/client
MIT License
19.34k stars 2.66k forks source link

Types `cache.modify` and `writeFragment` lead to TypeScript errors for documented Apollo examples when specifying generic parameter #11865

Open sjdemartini opened 4 months ago

sjdemartini commented 4 months ago

Issue Description

As of https://github.com/apollographql/apollo-client/pull/10895 and https://github.com/apollographql/apollo-client/pull/11206, the types for the field modifiers in cache.modify are explicit and more strict, where each field modifier is expected to return a list of references or objects when you specify the generic with cache.modify<MyObjectType>.

However, Apollo's documented examples lead to code that doesn't satisfy this more strict typing, such as https://www.apollographql.com/docs/react/caching/cache-interaction/#example-updating-the-cache-after-a-mutation:

const [addComment] = useMutation(ADD_COMMENT, {
  update(cache, { data: { addComment } }) {
    cache.modify({
      id: cache.identify(myPost),
      fields: {
        comments(existingCommentRefs = [], { readField }) {
          const newCommentRef = cache.writeFragment({
            data: addComment,
            fragment: gql`
              fragment NewComment on Comment {
                id
                text
              }
            `
          });
          return [...existingCommentRefs, newCommentRef];
        }
      }
    });
  }
});

Specifically, in the above, newCommentRef will be Reference | undefined based on writeFragment's longstanding type https://github.com/apollographql/apollo-client/blob/d502a69654d8ffa31e09467da028304a934a9874/src/cache/core/cache.ts#L312-L318.

As such, if you update the above to use cache.modify<CommentType>, this leads to a TS error since undefined is not an allowed return value from a field Modifier function:

Type '(existingCommentRefs: readonly (Reference | AsStoreObject<{...}>)[]) => (Reference | ... 1 more ... | undefined)[]' is not assignable to type 'Modifier<readonly (Reference | AsStoreObject<{...}>)[]>'.
...
        Type 'undefined' is not assignable to type 'Reference | AsStoreObject<{...}>'.ts(2322)

Two questions based on this:

  1. Is there a recommended way to handle this?
    • Should we simply use const newCommentRef = cache.writeFragment(...)! (i.e. add !) to mark that the writeFragment return value won't be undefined? It's not clear to me based on the docs when writeFragment would return undefined, but I guess if it's possible to be undefined with normal usage, we'd want to be more defensive and not simply override the type with the ! operator.
  2. Can/should the documentation be updated accordingly, and/or perhaps the TS types be adjusted?

Link to Reproduction

Not a runtime bug, just a simple TS error

Reproduction Steps

  1. Use TypeScript
  2. Use cache.modify<YourObjectType> and a field modifier which returns a value from cache.writeFragment
  3. Observe TS error since Reference | undefined is not an allowed field modifier return value, despite documentation suggesting usage of this pattern

@apollo/client version

3.10.4

alessbell commented 4 months ago

Hi @sjdemartini 👋 Thanks for opening this issue - it definitely sounds like the documentation should be updated, but in terms of the TS type of the return value from cache.writeFragment I'll need to dig in a bit more and chat with the team.

We're coming up on a long weekend but I'll get back to you as soon as I can, thanks!