React Hook useQuery回调调用报错,附Next.js代码寻求解决方案
解决React Hook规则错误:在回调中调用useQuery
错误原因
React Hook的核心规则要求所有Hook必须在函数组件的顶层作用域调用,不能在循环、条件判断、嵌套函数(比如map回调)中执行。你在points.map()里调用useQuery,会导致Hook的调用顺序在每次渲染时可能变化,破坏React对Hook状态的跟踪机制,因此触发react-hooks/rules-of-hooks规则报错。
两种可行解决方案
方案1:使用Apollo Client的useQueries钩子(快速适配)
Apollo Client提供了useQueries钩子,专门用于批量执行多个查询,完美符合Hook规则。它接收一个查询配置数组,返回对应结果的数组,和你之前的逻辑兼容。
修改后的代码:
'use client'; import React from 'react'; // @ts-ignore import CanvasJSReact from '@canvasjs/react-charts'; import { useQueries } from '@apollo/client'; // 替换useQuery为useQueries import { OVERALL_PERCENTAGE_SECONDARY } from '@/app/graphql/queries/student-score/overallPercentageSecondaryQueries'; const CanvasJSChart = CanvasJSReact.CanvasJSChart; const ScoreChart = ({ school, stfn }: { school: string; stfn: string }) => { // 替换any为明确类型 const points = [5, 4, 3, 2, 1]; // 用useQueries批量执行查询,顶层调用符合Hook规则 const queries = useQueries( points.map(point => ({ query: OVERALL_PERCENTAGE_SECONDARY, variables: { school, stfn, points: point }, })) ); const loading = queries.some(query => query.loading); const error = queries.find(query => query.error)?.error; if (loading) return <p>Loading...</p>; if (error) return <p>Error: {error.message}</p>; const overallPercentages = queries.map(query => query.data?.overallPercentageByPoints || { numof_question: 0, question: [] }); const optionsChart = { animationEnabled: true, exportEnabled: false, theme: "light1", data: [{ type: "pie", indexLabel: "{label}: {y}%", startAngle: -90, dataPoints: overallPercentages.map((percentage, index) => ({ y: percentage.overall, label: ["SA", "A", "N", "D", "SD"][index] })), }] }; return ( <div> <p className='text-sm text-center font-bold'>Score Chart</p> <CanvasJSChart options={optionsChart} containerProps={{ width: '100%', height: '170px' }} /> </div> ); }; export default ScoreChart;
方案2:修改GraphQL查询为批量查询(性能更优)
如果后端支持,建议修改GraphQL查询,一次性获取所有points对应的数据,减少网络请求次数,提升性能。
- 新增批量查询定义:
# overallPercentageSecondaryQueries.ts export const OVERALL_PERCENTAGE_SECONDARY_BATCH = gql` query OverallPercentageSecondaryBatch($school: String!, $stfn: String!, $points: [Int!]!) { overallPercentageByPointsBatch(school: $school, stfn: $stfn, points: $points) { points overall numof_question question } } `;
- 组件中使用单个
useQuery:
'use client'; import React from 'react'; // @ts-ignore import CanvasJSReact from '@canvasjs/react-charts'; import { useQuery } from '@apollo/client'; import { OVERALL_PERCENTAGE_SECONDARY_BATCH } from '@/app/graphql/queries/student-score/overallPercentageSecondaryQueries'; const CanvasJSChart = CanvasJSReact.CanvasJSChart; const ScoreChart = ({ school, stfn }: { school: string; stfn: string }) => { const points = [5, 4, 3, 2, 1]; const { loading, error, data } = useQuery(OVERALL_PERCENTAGE_SECONDARY_BATCH, { variables: { school, stfn, points }, }); if (loading) return <p>Loading...</p>; if (error) return <p>Error: {error.message}</p>; // 按points的原始顺序整理数据 const resultMap = new Map(data?.overallPercentageByPointsBatch.map(item => [item.points, item])); const overallPercentages = points.map(point => resultMap.get(point) || { numof_question: 0, question: [], overall: 0 }); const optionsChart = { animationEnabled: true, exportEnabled: false, theme: "light1", data: [{ type: "pie", indexLabel: "{label}: {y}%", startAngle: -90, dataPoints: overallPercentages.map((percentage, index) => ({ y: percentage.overall, label: ["SA", "A", "N", "D", "SD"][index] })), }] }; return ( <div> <p className='text-sm text-center font-bold'>Score Chart</p> <CanvasJSChart options={optionsChart} containerProps={{ width: '100%', height: '170px' }} /> </div> ); }; export default ScoreChart;
方案对比
- 方案1:无需修改后端,直接适配现有查询,适合快速修复问题。
- 方案2:减少网络请求次数,性能更优,是长期优化的首选方案,需要后端配合实现批量查询接口。
内容的提问来源于stack exchange,提问作者erwin
相关产品推荐
相关产品推荐

