apollo-client icon indicating copy to clipboard operation
apollo-client copied to clipboard

Query data and error are undefined

Open giulianok opened this issue 4 years ago • 47 comments

Intended outcome: I'm using useQuery to query and render some data. The loading prop works as expected, the value is true and then changes to false. When loading is false, data and error are both undefined.

I checked the network tab and the data is being received (I see the data prop)

Screen Shot 2021-04-27 at 1 41 51 PM

I also checked what's going on with the Apollo chrome extension and I can see the data

Screen Shot 2021-04-27 at 1 34 24 PM Screen Shot 2021-04-27 at 1 34 10 PM

I also was able to verify the correct result from the BE using the fetch API

Screen Shot 2021-04-27 at 1 34 47 PM

Actual outcome: data and error props from useQuery are undefined.

How to reproduce the issue:

Here's the component that uses useQuery and fetch

const QUERY = gql`
  query AccountStatus {
    accountStatus {
      __typename
      missingCode
      completed
      reason
    }
  }
`

const MissingThings = () => {
  const x = useQuery(QUERY)

  const { loading, data, error } = x

  console.log('--x', x)

  useEffect(() => {
    fetch(`${process.env.REACT_APP_GRAPHQL_URL}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization:
          'Bearer <TOKEN>',
      },
      body: JSON.stringify({
        variables: {},
        query: `
        query AccountStatus {
          accountStatus {
            __typename
            missingCode
            completed
            reason
          }
        }
        
        `,
      }),
    })
      .then((result) => result.json())
      .then((result) => console.log('--result', result))
      .catch(console.error)
  }, [])

  if (loading) {
    return null
  }

  if (error) {
    console.log('--error', error)
    return null
  }

  console.log('--data', data)

  return <div>All good</div>
}

And this is the Apollo Client

const ApolloClientProvider = ({ children }: any) => {
  const { auth, account } = useGlobalProvider()
  const headers =
    auth && account ? { Authorization: `Bearer ${auth?.token}` } : {}

  console.log('--headers', headers)
  console.log('--auth', auth)

  const wsLink = new WebSocketLink({
    uri: process.env.REACT_APP_GRAPHQL_WS_URL as string,
    options: {
      reconnect: true,
      connectionParams: () => ({
        headers,
      }),
    },
  })

  const httpLink = new HttpLink({ uri: process.env.REACT_APP_GRAPHQL_URL })

  const authMiddleware = new ApolloLink((operation, forward) => {
    // add the authorization to the headers
    operation.setContext({
      headers:
        auth && account ? { Authorization: `Bearer ${auth?.token}` } : {},
    })

    return forward(operation)
  })

  const splitLink = split(
    ({ query }) => {
      const definition = getMainDefinition(query)
      return (
        definition.kind === 'OperationDefinition' &&
        definition.operation === 'subscription'
      )
    },
    wsLink,
    httpLink,
  )

  const logoutLink = onError((error) => {
    console.log('APOLLO ERROR!', error)

    if (
      error.networkError?.message.includes('JWTExpired') ||
      error.graphQLErrors?.some(
        ({ extensions, message }) =>
          extensions?.code === 'invalid-jwt' || message.includes('JWTExpired'),
      )
    ) {
      navigate('/logout')
    }
  })

  const client = new ApolloClient({
    cache: new InMemoryCache(),
    link: ApolloLink.from([logoutLink, authMiddleware, splitLink]),
  })

  return <ApolloProvider client={client}>{children}</ApolloProvider>
}

Versions System: OS: macOS 11.2.3 Binaries: Node: 14.8.0 - ~/n/bin/node npm: 6.14.7 - ~/n/bin/npm Browsers: Chrome: 90.0.4430.72 Safari: 14.0.3 npmPackages: @apollo/client: ^3.3.15 => 3.3.15

giulianok avatar Apr 27 '21 17:04 giulianok

I continued testing and I found out that not using the cache works:

const x = useQuery(QUERY, {
    fetchPolicy: 'network-only',
  })

Any idea of how to debug the cache? I see that the data is cached but Apollo is having a hard time retrieving it (that's my conclusion)

Screen Shot 2021-04-27 at 2 03 35 PM

giulianok avatar Apr 27 '21 18:04 giulianok

I'm having the same bug of data being undefined but when backgrounding my app and foregrounding it. Verified via flipper that the data was actually coming in though. Setting fetchPolicy: network-only didnt solve it for my case.

    "@apollo/client": "^3.3.15",
    "react-native": "0.63.2",
    "apollo3-cache-persist": "^0.9.1",

alexwasner avatar Apr 28 '21 18:04 alexwasner

I’ve also run into this situation when implementing defer for Apollo Client. It seems like if the server responds with data which doesn’t match the query, in that it’s missing fields, the result provided by the cache has data and error set to undefined and loading set to false, which should probably never happen. Here’s a quick reproduction.

/*** SCHEMA ***/
import {
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLID,
  GraphQLString,
  GraphQLList,
} from 'graphql';

const PersonType = new GraphQLObjectType({
  name: 'Person',
  fields: {
    id: {type: GraphQLID},
    name: {type: GraphQLString},
  },
});

const peopleData = [
  {id: 1, name: 'John Smith'},
  {id: 2, name: 'Sara Smith'},
  {id: 3, name: 'Budd Deey'},
];

const QueryType = new GraphQLObjectType({
  name: 'Query',
  fields: {
    people: {
      type: new GraphQLList(PersonType),
      resolve: () => peopleData,
    },
  },
});

const schema = new GraphQLSchema({query: QueryType});

/*** LINK ***/
import {graphql, print} from "graphql";
import {ApolloLink, Observable} from "@apollo/client";
function delay(wait) {
  return new Promise(resolve => setTimeout(resolve, wait));
}

const link = new ApolloLink(operation => {
  return new Observable(async observer => {
    const {query, operationName, variables} = operation;
    await delay(300);
    try {
      const result = await graphql(
        schema,
        print(query),
        null,
        null,
        variables,
        operationName,
      );
      // Emulate producing a result which is missing a field from the server.
      delete result.data.people[2].name;
      observer.next(result);
      observer.complete();
    } catch (err) {
      observer.error(err);
    }
  });
});

/*** APP ***/
import React from "react";
import {render} from "react-dom";
import {
  ApolloClient,
  ApolloProvider,
  InMemoryCache,
  gql,
  useQuery,
} from "@apollo/client";
import "./index.css";

const ALL_PEOPLE = gql`
  query AllPeople {
    people {
      id
      name
    }
  }
`;

function App() {
  const {loading, data, error} = useQuery(ALL_PEOPLE);
  if (loading) {
    return <div>Loading...</div>;
  } else if (error) {
    return <div>Error: {error.toString()}</div>;
  } else if (!data) {
    return <div>What the heck</div>;
  }

  return (
    <ul>
      {data.people.map(person => (
        <li key={person.id}>{person.name}</li>
      ))}
    </ul>
  );
}

const client = new ApolloClient({
  cache: new InMemoryCache(),
  link,
});

render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById("root"),
);

brainkim avatar May 12 '21 05:05 brainkim

@benjamn Do you know the original behavior for when a server responds with less data than the query expects? My sense is that this is a regression of some kind.

brainkim avatar May 12 '21 05:05 brainkim

I’m discussing this with the team, and one thing y’all should try is adding returnPartialData to the query options. Still investigating as to what the expected behavior should be.

brainkim avatar May 12 '21 17:05 brainkim

Just noticed this bug today. Can also confirm that setting a fetchPolicy that does not read from cache resolves the issue. On latest version, @apollo/[email protected].

What's really bizarre is that I've only noticed this error recently. Yet, when I downgrade to v3.0.0, the error persists.

Do you know the original behavior for when a server responds with less data than the query expects? My sense is that this is a regression of some kind.

@brainkim I'm working on a new project with my company that involves a very simple query that reproduces this error consistently. I can say safely that the fields my mock server is returning are the exact same as the fields my client is expecting. Sadly, the code is private so I can't give you the repo.

Basically I'm reproducing this error when I run 2 different queries that have the same cache key. I have a

useQuery(GET_USERS)

called earlier, which returns an array of all users including one with ID asdf, and a

useQuery(SELECT_USER, { variables: { id: 'asdf' } })

called later. The second useQuery fails silently and eventually resolves to { loading: false, error: false, data: undefined }.

dlqqq avatar May 17 '21 22:05 dlqqq

I was able to reproduce this issue on a separate repository. See below @brainkim

https://github.com/diracs-delta/apollo-client-8063

dlqqq avatar May 17 '21 23:05 dlqqq

@diracs-delta This is a beautiful example. I’m dropping everything to investigate.

brainkim avatar May 18 '21 03:05 brainkim

@diracs-delta Ah I get it. It’s the same problem as I think everyone in this issue is having. In the mock graphql server you need to put the user object under the key user, or it doesn’t actually match the GraphQL query you sent to the server.

const handlers = [
  graphql.query('getUsers', (_, res, ctx) => {
    return res(ctx.data({ users: userStore }))
  }),
  graphql.query('getSpecificUser', (req, res, ctx) => {
    const user = userStore.find(user => user.id === req.variables.id);

    if (!user) {
      return res(ctx.errors([{ message: `Cannot find user ${user}` }]));
    }

    return res(ctx.data({user}));
  }),
];

brainkim avatar May 18 '21 15:05 brainkim

@brainkim I believe you're right. The useQuery hook is silently failing when the response doesn't match the query exactly, but the response has a corresponding cache entry.

It would be nice to produce some kind of console warning or emit an error when this happens to prevent future confusion. I can take a further look at this later tonight.

dlqqq avatar May 18 '21 16:05 dlqqq

Hello, I'm experiencing this same issue, however I am not mocking anything.

As @brainkim suggested above, adding returnPartialData allows my useQuery hook to work again and properly return data. However, I'm not keen on adding that to all my queries with caching involved?

Is there somewhere in the cache setup where this can be addressed? Or what long term solution could I implement that doesn't involve adding the returnPartialData option to all my useQuery hooks?

jasongaare avatar Jun 08 '21 19:06 jasongaare

@jasongaare

If you are both the provider and consumer of the GraphQL API, this issue is occurring because the field structure of the query and the response do not match, which is usually due to the backend omitting a null field entirely instead of returning null for a null field. Example:

// => request
query {
  id
  name
}

// <= response
data: {
  id: 'asdf'
  // should be name: null
}

Otherwise, if you are only the consumer of the GraphQL API, you're unfortunately out of other options until this issue is resolved.

dlqqq avatar Jun 10 '21 15:06 dlqqq

Where is the requirement to return "null" instead of "undefined" documented? Is that a graphql spec kind of thing? I hit this hard when I tried to change my Maybe type to T | undefined instead of T | null in graphql code-gen. Is that just not going to work on the client b/c the spec requires "null"?

macrael avatar Jun 14 '21 18:06 macrael

@macrael Although I truthfully haven't read the spec top-to-bottom, I believe that to be the case. I think the provider of a GraphQL API should always explicitly return null for a field rather than omitting it in the response, but someone please correct me if I'm wrong.

The reason codegen defines Maybe<T> = T | undefined | null by default is because codegen is intended to be used primarily for client-side development, in which case the type of Maybe is very convenient. This way, in queries with optional parameters, you can just omit the optional parameters instead of explicitly specifying each to be null. This omission is definitely in the GraphQL spec for request variables, but not for response data.

Since keeping the default definition of Maybe<T> is really convenient when you are using query variables, I suggest just leaving the value of Maybe<T> untouched when doing client-side work. But if you're doing backend work, you should define Maybe<T> = null | T instead.

dlqqq avatar Jun 14 '21 21:06 dlqqq

I've also encountered this issue, and setting fetchPolicy: network-only is not fixing it.

The oddest thing is that when I'm on a page where an object is queried for some fields, and then I open a component where the same object is queried for different fields, it is WORKING.

However, when I'm on a page that doesn't contain any graphql query, and I open the same component that queries the object, the issue happens. In the network tab in dev tools the API actually returned the right data, but in the code where I did await client.query I got undefined.

I've tried to add ID to each of the object and subobjects and pass in fetchPolicy: no-cache or fetchPolicy: network-only, but nothing works.

I'm not doing watchQuery so returnPartialData is not an option.

The component is rendered by ReactDOM.render. I've checked appollo dev tool, and the data from this component is not appearing in the dev tool no matter the data shows correctly in UI or not (for both cases mentioned above). In the meanwhile, another graphql call in the page shows in the cache.

DeannaWang avatar Jul 23 '21 04:07 DeannaWang

This should be resolved in @apollo/client@latest - let us know if not.

hwillson avatar Jan 04 '22 11:01 hwillson

Trying to install the package @apollo/client@latest.

Yarn version: 1.22.10

Node version: 14.17.4

Platform: darwin x64

Trace: 
  Invariant Violation: expected manifest
      at invariant (/usr/local/Cellar/yarn/1.22.10/libexec/lib/cli.js:2314:15)
      at PackageResolver.getStrictResolvedPattern (/usr/local/Cellar/yarn/1.22.10/libexec/lib/cli.js:65361:5)
      at add (/usr/local/Cellar/yarn/1.22.10/libexec/lib/cli.js:94632:33)
      at add (/usr/local/Cellar/yarn/1.22.10/libexec/lib/cli.js:94673:9)
      at add (/usr/local/Cellar/yarn/1.22.10/libexec/lib/cli.js:94673:9)
      at PackageHoister.prepass (/usr/local/Cellar/yarn/1.22.10/libexec/lib/cli.js:94706:7)
      at PackageHoister.seed (/usr/local/Cellar/yarn/1.22.10/libexec/lib/cli.js:94098:10)
      at PackageLinker.getFlatHoistedTree (/usr/local/Cellar/yarn/1.22.10/libexec/lib/cli.js:48740:13)
      at PackageLinker.<anonymous> (/usr/local/Cellar/yarn/1.22.10/libexec/lib/cli.js:48755:27)
      at Generator.next (<anonymous>)

alimoli avatar Jan 18 '22 20:01 alimoli

I have the solution just to get the data out . My solution is this one. (it is temporary) :)) I wait for the bug to be solved

const {loading, error, data} = useQuery(queries.GET_CATEGORIES); //at this moment the data is undefined //using the spread operator I create a new object with the data content const prod = {...data}; console.log(prod);

stefandplac avatar Mar 03 '22 11:03 stefandplac

I can confirm that this is still happening at 3.5.10. If one of the fields requested is null (because of an error), both errors and data fields are undefined from useQuery returned object.

Note: I can see in the network tab that request has 200 response code, has data and errors fields set as expected in the response.

ucarbehlul avatar Mar 08 '22 16:03 ucarbehlul

I'm seeing the same thing (@apollo/client 3.5. 10):

useQuery returns undefined for error and data, though I can see that loading is true at some point.

We both consume and provide the GraphQL API, so I have full ability to debug on both ends. The network response is exactly as we'd expect. Notably, there are no errors in the network response; the shape of the response is exactly as we'd expected, with the queried object nested under data.

For now, we've downgraded back to 3.2.5 to resolve this.

mvincent7891 avatar Apr 15 '22 20:04 mvincent7891

I had the same problem, the base url was wrong export const client = new ApolloClient({ uri: process.env.NEXT_PUBLIC_BASE_URL + "/api/graphql", cache, });

martinmolina1988 avatar Apr 27 '22 16:04 martinmolina1988

I just ran into this problem, as well. Fixed it by downgrading to 3.2.5.

Bruce773 avatar Apr 29 '22 18:04 Bruce773

I just ran into this problem too... data and error undefined

fvctr avatar Apr 30 '22 20:04 fvctr

Same problem 🥲

akbarnurullaev avatar May 04 '22 06:05 akbarnurullaev

Same issue happening at 3.6.1.

jcgonzave avatar May 04 '22 20:05 jcgonzave

3.5.10 works for me 🔥

In addition, my snapshot tests using "@apollo/client": "3.5.10" and "@testing-library/react": "^13.1.1" look like:

  it('Renders correctly for a successful call', async () => {
    const { container } = render(
      <MockedProvider
        defaultOptions={{ watchQuery: { fetchPolicy: 'network-only' } }}
        mocks={[mocksNeeded]}
        addTypename={false}
      >
        <AllTheChildren/>
      </MockedProvider>,
    )
    await waitFor(() => new Promise((res) => setTimeout(res, 500)))
    expect(container).toMatchSnapshot()
  })

minnocci-bcgdv avatar May 12 '22 23:05 minnocci-bcgdv

same problem here. fixed by downgrading to 3.2.5

brandonpardede avatar May 13 '22 13:05 brandonpardede

downgrading to 3.2.5 works for me

alex-nguyen90 avatar May 14 '22 10:05 alex-nguyen90

Tried a few suggestions here, I can confirm that both 3.2.5 and 3.5.10 are working for me and decided to stay with 3.5.10.

I'm really curious also as to why no errors are being reported and we end up with a silent fail, this has been very time-consuming to debug.

Maybe this suggests a regression, let us know if there is anything else we can do to test or provide further debug info.

w0o avatar May 15 '22 03:05 w0o

3.5.10 working for me. No joy with 3.6+

bentron2000 avatar May 16 '22 01:05 bentron2000