You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+tRPC条件调用hooks报错:渲染的hooks数量不一致

解决依赖前接口响应的tRPC查询调用问题

针对你遇到的hooks调用顺序不一致导致的错误,这里有几个实用的处理方式,不用合并接口也能搞定:

1. 用enabled参数控制后续查询的执行

tRPC的useQuery支持enabled选项,你可以把所有查询都放在组件顶层,然后用前一个查询的状态来决定后续查询是否执行。这样既遵守了hooks必须顶层调用的规则,又能实现依赖前接口响应的逻辑。

举个例子:

import { api } from '../utils/api';

const QuestionsPage = () => {
  // 第一个查询:获取基础数据
  const { data: firstData, isSuccess: firstSuccess } = api.firstQuery.useQuery();
  
  // 第二个查询:依赖firstData,只有第一个查询成功且有数据时才执行
  const { data: secondData } = api.secondQuery.useQuery(
    { id: firstData?.id },
    { enabled: firstSuccess && !!firstData }
  );

  // 第三个查询:依赖secondData,同理控制enabled
  const { data: thirdData } = api.thirdQuery.useQuery(
    { key: secondData?.key },
    { enabled: !!secondData }
  );

  // 渲染逻辑
  if (!firstData) return <div>加载中...</div>;
  if (!secondData) return <div>加载第二步...</div>;
  
  return (
    <div>
      {/* 渲染数据 */}
    </div>
  );
};

所有hooks都在顶层,每次渲染调用顺序一致,enabled参数会自动跳过未满足条件的查询,不会触发不必要的请求,也不会出现hooks数量不一致的错误。

2. 用useEffect结合tRPC客户端手动调用

如果依赖逻辑特别复杂(比如多条件分支、动态决定调用接口),可以用useEffect直接调用tRPC客户端方法,自己管理数据、加载、错误状态。这种方式灵活性更高,完全避开hooks顺序问题。

例子:

import { useState, useEffect } from 'react';
import { api } from '../utils/api';

const QuestionsPage = () => {
  const [firstData, setFirstData] = useState<FirstData | null>(null);
  const [secondData, setSecondData] = useState<SecondData | null>(null);
  const [thirdData, setThirdData] = useState<ThirdData | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  // 获取第一个数据
  useEffect(() => {
    const fetchData = async () => {
      try {
        const res = await api.firstQuery.query();
        setFirstData(res);
      } catch (err) {
        setError('获取第一个数据失败');
      } finally {
        setIsLoading(false);
      }
    };
    fetchData();
  }, []);

  // 依赖firstData获取第二个数据
  useEffect(() => {
    if (!firstData) return;
    
    const fetchSecond = async () => {
      try {
        const res = await api.secondQuery.query({ id: firstData.id });
        setSecondData(res);
      } catch (err) {
        setError('获取第二个数据失败');
      }
    };
    fetchSecond();
  }, [firstData]);

  // 依赖secondData获取第三个数据
  useEffect(() => {
    if (!secondData) return;
    
    const fetchThird = async () => {
      try {
        const res = await api.thirdQuery.query({ key: secondData.key });
        setThirdData(res);
      } catch (err) {
        setError('获取第三个数据失败');
      }
    };
    fetchThird();
  }, [secondData]);

  if (isLoading) return <div>加载中...</div>;
  if (error) return <div>{error}</div>;
  if (!firstData || !secondData || !thirdData) return <div>数据不完整</div>;

  return (
    <div>
      {/* 渲染内容 */}
    </div>
  );
};

这种方式适合复杂业务场景,但需要自己维护状态,不像useQuery那样自动缓存、重试,常规依赖查询优先用第一种方法。

3. 封装成自定义Hook

如果多个组件都有类似的依赖查询逻辑,可以把查询逻辑封装成自定义Hook,内部用enabled参数控制,对外暴露统一的数据和状态,保持组件代码整洁。

例子:

import { api } from '../utils/api';

const useChainedQueries = () => {
  const { data: firstData, isSuccess: firstSuccess } = api.firstQuery.useQuery();
  
  const { data: secondData } = api.secondQuery.useQuery(
    { id: firstData?.id },
    { enabled: firstSuccess && !!firstData }
  );

  const { data: thirdData } = api.thirdQuery.useQuery(
    { key: secondData?.key },
    { enabled: !!secondData }
  );

  return {
    firstData,
    secondData,
    thirdData,
    isLoading: !firstSuccess || !secondData || !thirdData,
  };
};

// 在组件中使用
const QuestionsPage = () => {
  const { firstData, secondData, thirdData, isLoading } = useChainedQueries();

  if (isLoading) return <div>加载中...</div>;

  return (
    <div>
      {/* 渲染数据 */}
    </div>
  );
};

自定义Hook同样遵守hooks规则,把复杂查询逻辑抽离,组件只需要关心数据使用。


内容的提问来源于stack exchange,提问作者Lotan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 07:04:52