Search code examples
reactjstypescriptreact-hooksredux-toolkitrtk-query

Having trouble understanding rtk queries


I'm having trouble understanding how to properly use rtk queries in my code. Initially, they're loading as undefined, which is why I'm adding if statements looking for truthiness, but then this leads to the following React error:

Uncaught Error: Rendered more hooks than during the previous render.

It makes sense why I get the error, but I don't know how to fix it. Any ideas?

import { useGetBudgetQuery, useGetExpensesQuery } from "@/state/api";
import { Budget, Expense } from "@/types";
import { Dayjs } from "dayjs";
import React from "react";

interface FinanceHelperProps {
  start: Dayjs | null;
  end: Dayjs | null;
}

const FinanceHelper: React.FC<FinanceHelperProps> = ({ start, end }) => {
  const {
    data: budget,
    isLoading: budgetLoading,
    isError: budgetError,
  } = useGetBudgetQuery();
  const {
    data: expenses,
    isLoading: expensesLoading,
    isError: expensesError,
  } = useGetExpensesQuery();
  let budgetForRange;
  let expensesPerCategory;
  if (start && end && budget) {
    budgetForRange = React.useMemo(() => {
      console.log(budget);
      const diff = end.month() - start.month();
      Object.values(budget).forEach((value) => {
        if (value) return (value *= diff);
      });
      return budget;
    }, [start, end, budget]);
  }
  if (expenses) {
    expensesPerCategory = React.useMemo(() => {
      const newExpenses = expenses.reduce(
        (map, { id, userId, category, subCategory, date, price }) =>
          map.set(category, map.get(category) ?? 0 + price),
        new Map()
      );
      let expenseObj = {};
      newExpenses.forEach((key, value) => {
        expenseObj = { ...expenseObj, [value]: key };
      });
      return expenseObj;
    }, [expenses]);
  }
  if (budgetLoading) return <p>Loading budget...</p>;
  if (budgetError) return <p>budget error...</p>;
  if (expensesLoading) return <p>Loading expenses...</p>;
  if (expensesError) return <p>Expenses error..</p>;
  if (!budget || !expenses) {
    return <p>Couldn't find budget or expenses...</p>;
  }
  const financeMessages = [];
  const length = Object.entries(budget).length;
  if (budgetForRange && expensesPerCategory) {
    let categoryArray: Array<number | null> =
      Object.values(expensesPerCategory);
    let budgetArray: Array<number | null> = Object.values(budgetForRange);

    for (let i = 0; i < length; i++) {
      console.log("category expense:", categoryArray[i]);
      console.log("budget expense:", budgetArray[i]);
    }
  }
  console.log(budgetForRange);
  console.log(expensesPerCategory);

  return <div>FinanceHelper</div>;
};

export default FinanceHelper;

Solution

  • The code is breaking the Rules of Hooks by conditionally calling them.

    Basically move the conditional checks into the hook callback functions as part of computing the memoized values.

    Example:

    const FinanceHelper: React.FC<FinanceHelperProps> = ({ start, end }) => {
      const {
        data: budget = {}, // <-- provide default value
        isLoading: budgetLoading,
        isError: budgetError,
      } = useGetBudgetQuery();
      const {
        data: expenses = [], // <-- provide default value
        isLoading: expensesLoading,
        isError: expensesError,
      } = useGetExpensesQuery();
    
      const budgetForRange = React.useMemo(() => {
        if (start && end) {
          const diff = end.month() - start.month();
          Object.values(budget).forEach((value) => {
            if (value) return (value *= diff);
          });
        }
        return budget;
      }, [start, end, budget]);
    
      const expensesPerCategory = React.useMemo(() => {
        const newExpenses = expenses.reduce(
          (map, { id, userId, category, subCategory, date, price }) =>
            map.set(category, map.get(category) ?? 0 + price),
          new Map()
        );
        const expenseObj = {};
        newExpenses.forEach((key, value) => {
          expenseObj = { ...expenseObj, [value]: key };
        });
        return expenseObj;
      }, [expenses]);
    
      ...
    };