typescriptreact-tsxtrpc

tRPC api conditional calls invoke Error: Rendered more hooks than during the previous render


I'm trying to learn how typescript, react and tRPC behaves together, I've this page questions.tsx:

import { NextPage } from 'next';
import { useRouter } from 'next/router';
import { QuestionCard } from '~/components/questionCard';
import { api } from '~/utils/api';

const QuestionFlow: NextPage = () => {
  const router = useRouter();

  const { subjectId, chapterId } = router.query;

  const subId = typeof subjectId === 'string' ? parseInt(subjectId) : 0;
  const chapId = typeof chapterId === 'string' ? parseInt(chapterId) : 0;

  //Get All questions from Chapter
  const { data: allChapterQuestions } = api.chapterQuestions.getAllChapterQuestionsFromSubjectChapter.useQuery({ subjectId: subId, chapterId: chapId });

  if (allChapterQuestions && allChapterQuestions.length > 0) {
    const { data: questionInfo } = api.chapterQuestions.getById.useQuery({ chapterQuestionId: allChapterQuestions[0]!.id });
    const { data: questionOptions } = api.questionOptions.getAllOptionsFromQuestionId.useQuery({ questionId: allChapterQuestions[0]!.id });

    // Check if both questionInfo and questionOptions are available before rendering the QuestionCard
    const isDataAvailable = questionInfo && questionOptions;

    return (
      <>
        <p>Question Flow</p>
        <p>subjectId: {subId}</p>
        <p>chapterId: {chapId}</p>
        {isDataAvailable ?
          <QuestionCard questionInfo={questionInfo} questionOptions={questionOptions} /> :
          <p>ERROR WITH SOME DATA</p>
        }
      </>
    );
  } else {
    return (
      <>
        <p>Question Flow</p>
        <p>subjectId: {subId}</p>
        <p>chapterId: {chapId}</p>
        <p>ERROR WITH THE DATA</p>
      </>
    );
  }
};

export default QuestionFlow;

As you may guess this renders first the "else" return, to then throw the error:

Unhandled Runtime Error
Error: Rendered more hooks than during the previous render.

I understand that this is caused to call my api. inside a conditional that will change on the next re-frame if the conditional change.

I know that one work around will be to put all those 3 api calls inside just one that retrieves the info at once, BUT:

There are a lot of cases that I won't be able to do that, and I don't know how to work around it when the moment arrives, there are a lot of cases that I want to check the response from the api and make another api call depending on that.

How should I tackle this issue?


Solution

  • As it was recommended in the comment, you cannot use hook with conditions, it's a general React rule, not trpc.

    What you can do here is roughly this:

    1. Move hooks out of the if-else block
    2. Add enabled option to the hooks
    3. Only keep rendering logic under your condition
    const hasChapters = allChapterQuestions && allChapterQuestions.length > 0
    
    const { data: questionInfo } = api
          .chapterQuestions
          .getById
          // add `enabled` option
          .useQuery({ chapterQuestionId: allChapterQuestions[0]!.id }, { enabled: hasChapters});
    
    // and now you can have your if clause here:
    if (questionInfo) {
      // ...
    } else {
      // ...
    }