useQuery 返回未定义,但在 gql 操场上返回数据

Posted

技术标签:

【中文标题】useQuery 返回未定义,但在 gql 操场上返回数据【英文标题】:useQuery returns undefined, But returns data on gql playground 【发布时间】:2020-05-12 05:16:50 【问题描述】:
"@apollo/react-hooks": "^3.1.3",
"apollo-client": "^2.6.8",

Apollo 客户端在 react 应用程序上返回 undefined 但在 gql 操场上返回数据,我不明白为什么它不能在客户端工作但在 graphql 操场上工作。

架构

我已经为用户查询定义了联合以进行错误处理。

type Query 
  user(id: ID!): UserReturn!


union UserReturn = User | Error

type User 
  id: ID!
  username: String!
  email: String
  profileUrl: String
  createdAt: Date
  ads: [Doc!]!



type Error 
  message: String
  code: ID

查询解析器

 async user(_,  id ,  User ) 
    console.log('query - User')
    try 
      await delay(1000 * 3)
      const user = await User.findById(id).populate('userData')
      console.log(user)
      if (!user) return 
        __typename: 'Error',
        message: 'User not found.',
        code: id
      

      const  _id: id, username, email, createdAt, userData:  profileUrl   = user

      console.log(username)
      return 
        __typename: 'User',
        id,
        username,
        email,
        createdAt,
        profileUrl
      
     catch (err) 
      console.log(err)
      return 
        __typename: 'Error',
        message: 'Something went wrong while getting user.',
        code: LogBack(err, `query/user?id=$id`, __filename)
      
    
  

在 gql Playground 上查询时

在 graphql 操场上,查询有效。

在客户端

 const  data  = useQuery(
    gql`query user($id: ID!) 
      user(id: $id) 
        __typename
        ... on User 
          id
          username
          email
          profileUrl
          createdAt
          # ads
        
        ... on Error 
          message
          code
        
      
    
    `,
    
      variables: 
        id: userId
      
    
  );

  console.log(data) // undefined

useQuery 运行但返回 undefiend。

【问题讨论】:

@xadm 在哪里?在解析器上? 在组件中 - 数据在获取之前可以是未定义的 不,我只发布了usequery,我也尝试过onComplete,但我仍然不确定。 这种方式不起作用,请阅读文档:apollographql.com/docs/react/data/queries ... 和 react hooks docs, f.e. useEffect 【参考方案1】:

请耐心等待,因为这个答案很长

我也遇到了这个问题。似乎在使用片段(在本例中为内联)和接口时会出现问题。我设法通过将正确的内省数据传递给 Apollo 的启发式片段匹配器来解决这个问题(参见第 3 步)。

这里有一个详细的分步指南,说明如何解决它:

1 - 验证控制台警告。

验证您的控制台中是否存在警告(这是发生在我身上的一个示例)。这些是与默认启发式片段匹配器冲突的字段:

阅读 Apollo 文档,我发现了以下内容:

默认情况下,Apollo Client 的缓存将使用启发式片段 matcher,如果包含结果,则假定片段匹配 其选择集中的所有字段,并且任何字段都不匹配 失踪。这在大多数情况下都有效,但这也意味着阿波罗 客户端无法为您检查服务器响应,它无法告诉您 当您手动将无效数据写入存储时,您使用 update、updateQuery、writeQuery 等 还有启发式片段 当使用带有联合的片段或匹配器时,匹配器将无法准确工作 接口。 Apollo Client 将通过控制台让您知道这一点 警告(开发中),如果它尝试使用默认启发式 带有联合/接口的片段匹配器。这 IntrospectionFragmentMatcher 是使用的解决方案 unions/interfaces,下面有更详细的解释。

这里有更多关于 v2 的信息: https://www.apollographql.com/docs/react/v2.6/data/fragments/#fragments-on-unions-and-interfaces

这里有更多关于 v3 的信息: https://www.apollographql.com/docs/react/data/fragments/#using-fragments-with-unions-and-interfaces

要解决这个问题,我们需要将 IntrospectionResultData 传递给 Apollo 客户端(参见步骤 3)。但在此之前,我们需要生成文件或数据。

您有 3 个选项。手动或自动(远程或本地)进行。

2 - 生成自省文件

从以下选项中选择一个(所有选项最终都相同)。在选择一个之前阅读所有这些。

2.1 - 选项 A - 手动生成文件。

使用以下架构使其适应您自己的架构。 请注意,以下是 TypeScript 代码。如果您使用的是纯 JS,请删除 type

请注意,在我的 .gql 文件中,我的联合类型如下:

   # 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;
    

完成此操作后,请继续执行第 3 步。

2.2 - 选项 B - 自动远程接近。

如果您的架构在远程服务器中并且您想获取它。这是直接从 Apollo Docs 中提取的脚本。对于自动方法,您可以按照 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!');
      
    );
  );

这将生成一个带有__schema 和适当类型的json 文件。完成此操作后,请继续执行第 3 步。

2.3 - 选项 C - 自动局部方法

上述选项对我来说很困难,因为我的架构位于身份验证墙后面。幸运的是,我确实可以直接在本地访问 .gql 文件,并且能够生成自省文件。继续阅读:

我们使用graphql-code-generator为我们生成自省文件。

转到您的后端代码,或者您的 graphql.gql 文件所在的任何位置,然后执行以下操作:

    安装GraphQL Code Generator:
yarn add graphql

yarn add -D @graphql-codegen/cli
    运行初始化向导:
yarn graphql-codegen init
    填写详细信息(适应自己) 就我而言,我选择了:
后端 - API 或服务器,使用 React 构建的应用程序 您的架构在哪里? ./appsync/appSync.gql 选择插件:Fragment Matcher(也可以随意选择其他插件...这很重要!) 文档:./appsync/generated/introspection.ts(这是您希望输出文件的位置)

这将生成一个codegen.yml,其中将包含要运行的graphql-code-generator 的插件和配置。

这是我的:

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"

我已经评论了对我们的使命不重要的部分。

然后(非常重要!)运行:

yarn install

因为向导会将包添加到我们的package.json

然后,生成代码:

yarn generate

这将输出需要包含在 Apollo 中才能继续的 introspection.ts 文件。

3 - 将自省文件注入 ApolloClient

现在,在您的前端代码中,将 introspection.ts 文件复制到您的存储库(如果它还没有的话),并包含它:

注意:我已将文件重命名为 fragmentTypes.ts 并将其包含在 apollo 文件夹中:

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,
);

在此之后,您的控制台警告应该消失,查询和突变应该正常执行。

【讨论】:

我没有在这个查询中使用片段【参考方案2】:

可能有帮助的东西,你知道你在哪里调用 data 也可以查找error和console.log('Error:',error)

查看 apollo 客户端查询文档

类似这样的东西,看看错误信息,它应该有帮助!

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>;

【讨论】:

【参考方案3】:

这可能与您的情况无关,但我的情况是命名问题。

  const  isLoading, error, blogs  = useQuery(BLOGS);

从钩子返回的对象是data 而不是blogs

【讨论】:

【参考方案4】:

这是一个迟到的答案,但我遇到了同样的问题,我的 playGround 返回了正确的响应,但没有返回 useQuery 挂钩。

我的问题是提供给查询的变量(在您的情况下为“id”)的类型是字符串而不是数字。

【讨论】:

【参考方案5】:

当数据完全显示在 Playground 中,但在实际应用中为空时,我遇到了类似的问题。我检查了开发工具,看到所有数据都在浏览器的响应中,但它只是没有显示在 apollo 缓存中。然后我发现原因是我将我的 apollo 客户端缓存选项设置为 addTypename: false。删除后,它按预期工作。

【讨论】:

以上是关于useQuery 返回未定义,但在 gql 操场上返回数据的主要内容,如果未能解决你的问题,请参考以下文章

使用 useQuery 进行批处理反应钩子返回未定义

在api请求准备好之前无法弄清楚如何跳过查询,因此它不会在带有useSWR的apollo useQuery中返回未定义的变量

apollo usequery 导致未定义的错误

调用useEffect挂钩时useQuery数据未定义?

如何在 GraphQL 中正确链接 useQuery 和 useMutation?

GQL 查询未返回 StringProperty 查询平等性测试的结果