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

Query data and error are undefined #8063

Open
giulianok opened this issue Apr 27, 2021 · 47 comments
Open

Query data and error are undefined #8063

giulianok opened this issue Apr 27, 2021 · 47 comments

Comments

@giulianok
Copy link

giulianok commented Apr 27, 2021

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
Copy link
Author

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

@alexwasner
Copy link

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",

@brainkim
Copy link
Contributor

brainkim commented May 12, 2021

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
Copy link
Contributor

@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
Copy link
Contributor

brainkim commented May 12, 2021

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.

@dlqqq
Copy link

dlqqq commented May 17, 2021

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
Copy link

dlqqq commented May 17, 2021

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

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

@brainkim
Copy link
Contributor

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

@brainkim
Copy link
Contributor

brainkim commented May 18, 2021

@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}));
  }),
];

@dlqqq
Copy link

dlqqq commented May 18, 2021

@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.

@jasongaare
Copy link

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?

@dlqqq
Copy link

dlqqq commented Jun 10, 2021

@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.

@macrael
Copy link

macrael commented Jun 14, 2021

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"?

@dlqqq
Copy link

dlqqq commented Jun 14, 2021

@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.

@akbarnurullaev
Copy link

Same problem 🥲

@jcgonzave
Copy link

Same issue happening at 3.6.1.

@minnocci-bcgdv
Copy link

minnocci-bcgdv commented May 12, 2022

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()
  })

@brandonpardede
Copy link

same problem here. fixed by downgrading to 3.2.5

@alex-nguyen90
Copy link

downgrading to 3.2.5 works for me

@w0o
Copy link

w0o commented May 15, 2022

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.

@bentron2000
Copy link

3.5.10 working for me. No joy with 3.6+

@minnocci-bcgdv
Copy link

3.6.0 also failing for me, sticking to 3.5.10

@jeffreyyoung
Copy link

3.3.7 was failing for me. Fixed by upgrading to 3.5.10.

-    "@apollo/client": "3.3.7",
+    "@apollo/client": "3.5.10",

@eranimo
Copy link

eranimo commented Jun 8, 2022

Why is this closed?

@ghost
Copy link

ghost commented Jun 13, 2022

Still encountering this problem.

@Maxth
Copy link

Maxth commented Jun 21, 2022

+1

@Pastbin
Copy link

Pastbin commented Jun 29, 2022

it seems I was able to solve the problem!

If your server responds with a status other than 200, then "data" becomes "undefined"
Example:

response.status(403)
return {message: "not authorized"}

data (client) = undefined

response.status(200)
(default 200)
return {message: "not authorized"}

data (client) = data{message: "not authorized"}

I hope this helps someone, good luck!

@brainkim brainkim removed their assignment Jun 29, 2022
@anonymous-coder
Copy link

I'm a little shocked this is still a thing, it only worked on 3.3.7 for me, not even 3.5.10.

@Saad9624
Copy link

Saad9624 commented Jul 2, 2022

getting undefined for data and error

@Alaricus
Copy link

Alaricus commented Jul 6, 2022

Happening on 3.3.1.

@MorganeLecurieux
Copy link

MorganeLecurieux commented Sep 30, 2022

Same issue for me on 3.6.9
I get the error on the first load of the query. Then if I reload the page, the error from the hook is undefined but I can still see it in the network 😞

@bignimbus bignimbus reopened this Nov 7, 2022
@bignimbus bignimbus added 🐞 bug and removed 🛬 fixed-in-prerelease 🚧 in-triage Issue currently being triaged labels Nov 7, 2022
@jpwexperience
Copy link

Running into this problem on 3.7.1

First query gives undefined for data and error. Response of 200 with expected data can be seen in the network.

@jimmygizmo
Copy link

jimmygizmo commented Jan 29, 2023

I've tried every suggestion in this post and many other sources and most of the apollo client versions that people said worked and the problem is present 100% of the time. My schema and query are as simple as possible. The data shape matches perfectly. ** The correct data is clearly being returned during the fetch. ** I've tried everything possible. The client is getting good data back from the server. I tried the no-cache setting and the partial data setting and saw no improvement.
The Loading feature seems to work fine, but I get the UNDEFINED error and UNDEFINED data at the end. I was never able to get Apollo to work and tried 4 or 5 fresh passes at all code from server to client, schema to model. It's obviously something going sideways in the client at the final stage of handing the data to the caller. Regarding the cache, I can only say the setting had no effect and I would not have had cached data in all cases of the failure, which for me was 100%.

@hwillson
Copy link
Member

@jimmygizmo can you provide a small runnable reproduction that shows this happening?

@Sabplpz
Copy link

Sabplpz commented Aug 6, 2023

Happening at 3.7.17, tried downgrading to 3.5.10 to test and because I'm using React 18.2 it wouldn't let me downgrade. Not sure what to do, any suggestions?

@Eric-Fray
Copy link

Confirmed the same on version 3.80 and 3.8.1. Downgrading is also not an option for me.

@Joshrogan
Copy link

Upgrading to 3.9.11 fixed this for me.

@WildDanDan
Copy link

For anyone using Hasura, I solved this issue (in 3.11.8) by setting the environment variable HASURA_GRAPHQL_EXPERIMENTAL_FEATURES=no_null_unbound_variable_default. Look for similar settings in your GraphQL engine. Hope this helps someone.

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