Home > Enterprise >  How to solve a situation when a component calls setState inside useEffect but the dependencies chang
How to solve a situation when a component calls setState inside useEffect but the dependencies chang

Time:10-31

I have this component:

const updateUrl = (url: string) => history.replaceState(null, '', url);

// TODO: Rename this one to account transactions ATT: @dmuneras
const AccountStatement: FC = () => {
  const location = useLocation();
  const navigate = useNavigate();
  const { virtual_account_number: accountNumber, '*': transactionPath } =
    useParams();

  const [pagination, setPagination] = useState<PaginatorProps>();
  const [goingToInvidualTransaction, setGoingToInvidualTransaction] =
    useState<boolean>(false);
  const SINGLE_TRANSACTION_PATH_PREFIX = 'transactions/';

  // TODO: This one feels fragile, just respecting what I found, but, we could
  // investigate if we can jsut rely on the normal routing. ATT. @dmuneras
  const transactionId = transactionPath?.replace(
    SINGLE_TRANSACTION_PATH_PREFIX,
    ''
  );

  const isFirst = useIsFirstRender();

  useEffect(() => {
    setGoingToInvidualTransaction(!!transactionId);
  }, [isFirst]);

  const {
    state,
    queryParams,
    dispatch,
    reset,
    setCursorAfter,
    setCursorBefore
  } = useLocalState({
    cursorAfter: transactionId,
    includeCursor: !!transactionId
  });

  const {
    filters,
    queryParams: globalQueryParams,
    setDateRange
  } = useGlobalFilters();

  useUpdateEffect(() => {
    updateUrl(
      `${location.pathname}?${prepareSearchParams(location.search, {
        ...queryParams,
        ...globalQueryParams
      }).toString()}`
    );
  }, [transactionId, queryParams]);

  useUpdateEffect(() => dispatch(reset()), [globalQueryParams]);

  const account_number = accountNumber;
  const requestParams = accountsStateToParams({
    account_number,
    ...state,
    ...filters
  });

  const { data, isFetching, error, isSuccess } =
    useFetchAccountStatementQuery(requestParams);

  const virtualAccountTransactions = data && data.data ? data.data : [];

  const nextPage = () => {
    dispatch(setCursorAfter(data.meta.cursor_next));
  };

  const prevPage = () => {
    dispatch(setCursorBefore(data.meta.cursor_prev));
  };
  const onRowClick = (_event: React.MouseEvent<HTMLElement>, rowData: any) => {
    if (rowData.reference) {
      if (rowData.id == transactionId) {
        navigate('.');
      } else {
        const queryParams = prepareSearchParams('', {
          reference: rowData.reference,
          type: rowData.entry_type,
          ...globalQueryParams
        });

        navigate(
          `${SINGLE_TRANSACTION_PATH_PREFIX}${rowData.id}?${queryParams}`
        );
      }
    }
  };

  const checkIfDisabled = (rowData: TransactionData): boolean => {
    return !rowData.reference;
  };

  useEffect(() => {
    if (data?.meta) {
      setPagination({
        showPrev: data.meta.has_previous_page,
        showNext: data.meta.has_next_page
      });
    }
  }, [data?.meta]);

  const showTransactionsTable: boolean =
    Array.isArray(virtualAccountTransactions) && isSuccess && data?.data;

  const onTransactionSourceLoaded = (
    transactionSourceData: PayoutDetailData
  ) => {
    const isIncludedInPage: boolean = virtualAccountTransactions.some(
      (transaction: TransactionData) => {
        if (transactionId) {
          return transaction.id === parseInt(transactionId, 10);
        }

        return false;
      }
    );

    if (!goingToInvidualTransaction || isIncludedInPage) {
      return;
    }

    const fromDate = dayjs(transactionSourceData.timestamp);
    const toDate = fromDate.clone().add(30, 'day');

    setDateRange({
      type: 'custom',
      to: toDate.format(dateFormat),
      from: fromDate.format(dateFormat)
    });

    setGoingToInvidualTransaction(false);
  };

  const fromDate = requestParams.created_after || dayjs().format('YYYY-MM-DD');
  const toDate = requestParams.created_before || dayjs().format('YYYY-MM-DD');

  const routes = [
    {
      index: true,
      element: (
        <BalanceWidget
          virtualAccountNumber={account_number}
          fromDate={fromDate}
          toDate={toDate}
        />
      )
    },
    {
      path: `${SINGLE_TRANSACTION_PATH_PREFIX}:transaction_id`,
      element: (
        <TransactionDetails
          onTransactionSourceLoaded={onTransactionSourceLoaded}
        />
      )
    }
  ];

  return (........

I get this error: Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

The useEffect where the issue is, it is this one:

  useEffect(() => {
    if (data?.meta) {
      setPagination({
        showPrev: data.meta.has_previous_page,
        showNext: data.meta.has_next_page
      });
    }
  }, [data?.meta]);

Considering previous answers, would the solution be to make sure I return a new object each time? But I am not sure what would be the best approach. Any clues ?

CodePudding user response:

did you want the useEffect to start every changes of 'data?.meta' ?

CodePudding user response:

Without reading all the code, I believe the data.meta object changes on every render. There is a way to change the useEffect to narrow done its execution conditions:

  useEffect(() => {
    if (data?.meta) {
      setPagination({
        showPrev: data.meta.has_previous_page,
        showNext: data.meta.has_next_page
      });
    }
  }, [!data?.meta, data?.meta?.has_previous_page, data?.meta?.has_next_page]);

Please note the ! before data.?.meta which makes the hook test only for presence or absence of the object, since your code doesn't need more than that information.

  • Related