Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useQuery silently returns null data when cache is missing some field #6375

Open
fromi opened this issue Jun 2, 2020 · 5 comments
Open

useQuery silently returns null data when cache is missing some field #6375

fromi opened this issue Jun 2, 2020 · 5 comments

Comments

@fromi
Copy link

fromi commented Jun 2, 2020

Intended outcome:
I have a local field named "displayedGame":

gql`query GetDisplayedGame { displayedGame @client { id myPlayerId players { id name } } }`

It references a Game in the cache: ROOT_QUERY: displayedGame: {__ref: "Game:6k3ZEe3AouGr"}

I receive a real time notification (not using Subscription because I use a Serverless + Pusher architecture). This notification informs that a player joined the game.
I use client.writeFragment({id: 'Game:' + gameId, fragment: GAME_PLAYERS, data: {players}}) in order to update the list of players in the cache.

Actual outcome:
When I do that, useQuery starts returning null data AND null error:
const {data, error} = useQuery<{ displayedGame: Game }>(GET_DISPLAYED_GAME)

After investigating a lot, it appears that the data is null because the field "player.name" of the new player is undefined in the cache: it is not in my real time notification because it is indeed undefined in my database, but the cache expects it to be null.

I only managed to understand that because I tried to run client.readQuery<{ displayedGame: Game }>({query: GET_DISPLAYED_GAME}) directly on the cache: doing that I had a clear error: MissingFieldError: Can't find field 'name' on object {__typename: Player, id: ...}

Expected behavior

  • useQuery should return the MissingFieldError when reading local state from the cache with missing data
  • Maybe writeFragment could automatically inject null for undefined fields when writing a local state?

How to reproduce the issue:
Use writeFragment to inject partial data in the cache for a client side directive field, and watch useQuery silently return null data with no error.

Versions
@apollo/client 3.0.0-beta.50

@thomassuckow
Copy link

I'm wondering if this is related to an issue I am seeing where if I have two queries that request different fields I get a MissingFieldError because the first one gets cached and the second one then can't use the result in the cache because it is incomplete

benjamn added a commit that referenced this issue Aug 3, 2020
@lorensr
Copy link
Contributor

lorensr commented Oct 26, 2020

I get undefined data also when the root query field is absent from the cache, and when the operation includes a network query that succeeds:

const MIXED_QUERY = gql`
  query {
    remoteField
    localField @client
  }
`

const { loading, data, error } = useQuery(MIXED_QUERY)

console.log(loading, data, error)

Current behavior

true undefined undefined
false undefined undefined

Desired behavior

true undefined undefined
false { remoteField: 'foo', localField: null } undefined

Versions

@apollo/client 3.2

@bignimbus
Copy link
Contributor

Hi @fromi and others in this thread! Thanks for your patience on this. I'm doing some housekeeping and would be eager to learn whether this is still an issue in a more recent version of the client. If so, does anyone have a reproduction available via a codesandbox or sample repo? Thanks much!

@fromi
Copy link
Author

fromi commented Nov 8, 2022

Hi @bignimbus! Sorry, I do not have time nowadays to try to reproduce the issue :/

@bignimbus bignimbus removed the 🏓 awaiting-contributor-response requires input from a contributor label Dec 5, 2022
@chrahman
Copy link

chrahman commented Nov 1, 2023

I was facing the same problem, it was caching the ALL_MATCHES query but it was not caching ALL_GROUPS, I was hitting two different queries with the same variables, like:

  const { loading: groupsLoading, data: groupsTableData } = useQuery(ALL_GROUPS, {
    variables: {
      filter: {
        tournament: 1,
        ...(selectedSegment ? { segment: selectedSegment } : {}),
        ...(selectedCategory ? { tournamentCategory: selectedCategory } : {}),
      },
    }
  }); 

  const { loading: matchesLoading, data: matchesTableData } = useQuery(ALL_MATCHES, {
    variables: {
      filter: {
        tournament: 1,
        ...(selectedSegment ? { segment: selectedSegment } : {}),
        ...(selectedCategory ? { tournamentCategory: selectedCategory } : {}),
      },
    },
  });

And then i added 1 extra variable "queryName" for uniquely saving in the cache because i think Apollo client cache queries based on unique variable fields.

  const { loading: groupsLoading, data: groupsTableData } = useQuery(ALL_GROUPS, {
    variables: {
      filter: {
        tournament: tournamentId,
        ...(selectedSegment ? { segment: selectedSegment } : {}),
        ...(selectedCategory ? { tournamentCategory: selectedCategory } : {}),
      },
      queryName: "allGroups",
    }
  });

  const { loading: matchesLoading, data: matchesTableData } = useQuery(ALL_MATCHES, {
    variables: {
      filter: {
        tournament: tournamentId,
        ...(selectedSegment ? { segment: selectedSegment } : {}),
        ...(selectedCategory ? { tournamentCategory: selectedCategory } : {}),
      },
      queryName: "allGroupMatches",
    },
  });
  
    const [updateGroup] = useMutation(UPDATE_GROUP, {
    update(cache, { data: { updateGroup } }) {
      const {allGroups}: any = cache.readQuery({
        query: ALL_GROUPS,
        variables:{
          filter:{
            tournament: auth.tournamentId,
            ...(selectedCategory ? { tournamentCategory: selectedCategory } : {}),
            ...(selectedSegment ? { segment: selectedSegment } : {}),
          },
          queryName: "allGroups",
        }
      });
      cache.writeQuery({
        query: ALL_GROUPS,
        variables:{
          filter:{
            tournament: auth.tournamentId,
            ...(selectedCategory ? { tournamentCategory: selectedCategory } : {}),
            ...(selectedSegment ? { segment: selectedSegment } : {}),
          },
          queryName: "allGroups",
        },

        data: {
          // updated data
        },
      });
    },
  });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants