useQuery returns undefined, But returns data on gql playground

前端 未结 3 571
北荒
北荒 2021-01-13 05:11
\"@apollo/react-hooks\": \"^3.1.3\",
\"apollo-client\": \"^2.6.8\",

Apollo client return undefined on react app but return the data on gql playgrou

相关标签:
3条回答
  • 2021-01-13 05:40

    Please, bear with me as this answer is long.

    I ran into this issue as well. It seems the problem happens when using fragments (in this case, inline) and interfaces. I managed to solve it by passing the correct introspection data to Apollo's Heuristic Fragment Matcher (See Step 3).

    Here's a detailed step by step guide on how to solve it:

    1 - Verify Console Warnings.

    Verify that there are warnings in your console (Here's an example that happened to me). These are the fields colliding with the default heuristic fragment matcher:

    Reading the Apollo docs, I found out the following:

    By default, Apollo Client's cache will use a heuristic fragment matcher, which assumes that a fragment matched if the result included all the fields in its selection set, and didn't match when any field was missing. This works in most cases, but it also means that Apollo Client cannot check the server response for you, and it cannot tell you when you're manually writing invalid data into the store using update, updateQuery, writeQuery, etc. Also, the heuristic fragment matcher will not work accurately when using fragments with unions or interfaces. Apollo Client will let you know this with a console warning (in development), if it attempts to use the default heuristic fragment matcher with unions/interfaces. The IntrospectionFragmentMatcher is the solution for working with unions/interfaces, and is explained in more detail below.

    More info for v2 here: https://www.apollographql.com/docs/react/v2.6/data/fragments/#fragments-on-unions-and-interfaces

    More info for v3 here: https://www.apollographql.com/docs/react/data/fragments/#using-fragments-with-unions-and-interfaces

    To fix this issue, we need to pass the IntrospectionResultData to the Apollo Client (See Step 3). But before that, we need to generate the file or data.

    You have 3 options. Either do it manually or automatically (remote or local).

    2 - Generate the introspection file

    Pick one of the options below (all of them end up being the same). Read all of them before choosing one.

    2.1 - Option A - Generating the file manually.

    Use the following schema to adapt it to your own. Note, the following is TypeScript code. Remove the type if you're using plain JS.

    Please see that in my case I had in my .gql file a union type of the following fashion:

       # GraphQL code omitted. 
      union PlanningResult = Planning | PlanningTechnical
    
    // For Apollo V 2.x
    export interface IntrospectionResultData {
      __schema: {
        types: {
          kind: string;
          name: string;
          possibleTypes: {
            name: string;
          }[];
        }[];
      };
    }
    
    const result: IntrospectionResultData = {
      __schema: {
        types: [
          {
            kind: 'UNION',
            name: 'PlanningResult',
            possibleTypes: [
              {
                name: 'Planning',
              },
              {
                name: 'PlanningTechnical',
              },
            ],
          },
        ],
      },
    };
    export default result;
    
    
    // For Apollo V3:
    
          export interface PossibleTypesResultData {
            possibleTypes: {
              [key: string]: string[]
            }
          }
          const result: PossibleTypesResultData = {
      "possibleTypes": {
        "PlanningResult": [
          "Planning",
          "PlanningTechnical"
        ]
      }
    };
          export default result;
        
    

    Once you've done this, proceed to step 3.

    2.2 - Option B - Automatic Remote Approach.

    This is if you have your schema in a remote server and you'd like to fetch it. This is a script extracted directly from the Apollo Docs. For the automatic approach, you can fetch the schema directly as stated in the Apollo Docs:

    // This is for V2 only, for V3 use the link down below (They're not the same!).
    
    // For V2: https://www.apollographql.com/docs/react/v2.6/data/fragments/#fragments-on-unions-and-interfaces
    // For V3 please, go to https://www.apollographql.com/docs/react/data/fragments/#generating-possibletypes-automatically
    
    const fetch = require('node-fetch');
    const fs = require('fs');
    
    fetch(`${YOUR_API_HOST}/graphql`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        variables: {},
        query: `
          {
            __schema {
              types {
                kind
                name
                possibleTypes {
                  name
                }
              }
            }
          }
        `,
      }),
    })
      .then(result => result.json())
      .then(result => {
        // here we're filtering out any type information unrelated to unions or interfaces
        const filteredData = result.data.__schema.types.filter(
          type => type.possibleTypes !== null,
        );
        result.data.__schema.types = filteredData;
        fs.writeFile('./fragmentTypes.json', JSON.stringify(result.data), err => {
          if (err) {
            console.error('Error writing fragmentTypes file', err);
          } else {
            console.log('Fragment types successfully extracted!');
          }
        });
      });
    

    This will generate a json file with the __schema and appropriate types. Once you've done this, proceed to step 3.

    2.3 - Option C - Automatic Local Approach

    The options above were difficult for me as I had my schema behind an auth wall. Fortunately, I did have direct local access to the .gql file and was able to generate the introspection file. Read on:

    We use graphql-code-generator to generate the introspection file for us.

    Go to your back-end code, or wherever your graphql.gql file lies, and do:

    1. Install GraphQL Code Generator:
    yarn add graphql
    
    yarn add -D @graphql-codegen/cli
    
    1. Run the initialization wizard:
    yarn graphql-codegen init
    
    1. Fill in the details (adapt to your own) In my case, I selected:
    • Backend - API or server, Application built with React
    • Where is your schema? ./appsync/appSync.gql
    • Pick plugins: Fragment Matcher (Feel free to select other plugins as well... this is the important one!)
    • document: ./appsync/generated/introspection.ts (This is where you want the file to be outputted)

    This will generate a codegen.yml that will include the plugins and the configuration for graphql-code-generator to run.

    This is mine:

    overwrite: true
    schema: "./appsync/appSync.gql"
    # documents: "./appsync/**/*.gql"
    generates:
      ./appsync/generated/introspection.ts:
        plugins:
          # - "typescript"
          # - "typescript-operations"
          # - "typescript-resolvers"
          # - "typescript-react-apollo"
          - "fragment-matcher"
        config:
        # NOTE: Remember to specify the CORRECT Apollo Client Version
          apolloClientVersion: 2.6
      ./graphql.schema.json:
        plugins:
          - "introspection"
    
    

    I've commented on the parts that are not critical for our mission.

    Then (very important!) Run:

    yarn install
    

    Because the wizard adds packages to our package.json.

    Then, generate the code:

    yarn generate
    

    This will output the introspection.ts file which needs to be included in Apollo to continue.

    3 - Inject the introspection file to the ApolloClient

    Now, in your front-end code, copy the introspection.ts file to your repo (if it's not already in there), and include it:

    Note: I've renamed my file to fragmentTypes.ts and included it inside the apollo folder:

    For V2:
    import ApolloClient from 'apollo-client/ApolloClient';
    import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
    import { InMemoryCache } from 'apollo-cache-inmemory/lib/inMemoryCache';
    // The file we just generated. If it's a .json file 
    // remember to include the .json extension
    import introspectionQueryResultData from './apollo/fragmentTypes';
    
    const fragmentMatcher = new IntrospectionFragmentMatcher({
      introspectionQueryResultData,
    });
    
    export const globalClient = new ApolloClient({
      link,
      cache: new InMemoryCache({ fragmentMatcher }),
    });
    
    
    For V3:
    import { InMemoryCache, ApolloClient } from '@apollo/client';
    // In case you used graphql-code-generator
    // import introspectionQueryResultData from './apollo/fragmentTypes';
    // The file we just generated. If it's a .json file 
    // remember to include the .json extension
    import possibleTypes from './path/to/possibleTypes.json';
    
    const cache = new InMemoryCache({
      possibleTypes,
    });
    const client = new ApolloClient({
      // ...other arguments...
      cache,
    });
    

    After this, your console warnings should go away and the queries and mutations should perform as normal.

    0 讨论(0)
  • 2021-01-13 05:46

    It's kind of a late answer but I had the same issue where my playGround returned a correct response but not the useQuery hook.

    My problem was that the variable given to the query, in your case 'id', was of type String instead of Number.

    0 讨论(0)
  • 2021-01-13 05:57

    something that might help, you know where you call {data} you can also look for error and console.log('Error:',error)

    check the apollo client query docs

    something like this , and look at the error message, it should help !

    import { useQuery } from '@apollo/react-hooks';
    import gql from 'graphql-tag';
    
    const GET_GREETING = gql`
      query getGreeting($language: String!) {
        greeting(language: $language) {
          message
        }
      }
    `;
    
    function Hello() {
        const { loading, error, data } = useQuery(GET_GREETING, {
        variables: { language: 'english' },
      });
      if (loading) return 'Loading...';
      if (error) return `Error! ${error.message}`;
      return <h1>Hello {data.greeting.message}!</h1>;
    }
    
    0 讨论(0)
提交回复
热议问题