简体   繁体   中英

React component is re-rendering infinitely

First, I am really new to react; so, apologies, for beginner questions.

I have a React app with Redux and Redux Saga.

One of the components looks like this:

import { TableContainer, TableHead, TableRow } from '@material-ui/core';
import Paper from '@material-ui/core/Paper';
import makeStyles from '@material-ui/core/styles/makeStyles';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { ProgramCategory } from '../model/program-category';
import { ProgramCategoryItemRow } from '../ProgramGategoryItemRow/ProgramCategoryItemRow';
import { ProgramCategoryActions } from '../store/program-category.actions';
import { ProgramCategorySelectors } from '../store/program-category.selectors';

const useStyles = makeStyles({
  table: {
    width: '100%',
  },
  tableHeadCell: {
    fontWeight: 'bold',
  },
});

export interface ProgramCategoriesTableProps {
  isLoaded: boolean;
  categories: ProgramCategory[];
  fetchAllCategories: () => void;
}

export const PureProgramCategoriesTable: React.FC<ProgramCategoriesTableProps> = ({
  isLoaded,
  categories,
  fetchAllCategories,
}) => {
  useEffect(() => {
    console.error('in useEffect');
    fetchAllCategories();
  });

  const styles = useStyles();

  return (
    <TableContainer component={Paper}>
      // the rest
      <TableBody>
          {categories.map(c => (
            <ProgramCategoryItemRow category={c} />
          ))}
      </TableBody>
    </TableContainer>
  );
};

const mapStateToProps = createSelector(
  [ProgramCategorySelectors.isLoaded, ProgramCategorySelectors.getAll],
  (isLoaded, categories) => ({ isLoaded, categories }),
);

const mapDispatchToProps = {
  fetchAllCategories: ProgramCategoryActions.fetchAll.start,
};

export const ProgramCategoriesTable = connect(mapStateToProps, mapDispatchToProps)(PureProgramCategoriesTable);

The sagas that process ProgramCategoryActions.fetchAll.start is as follows:

import { call, put, takeLatest } from 'redux-saga/effects';
import { ProgramCategoryApi } from '../services/program-category.api';
import { ProgramCategoryActions } from './program-category.actions';

function* handleFetchAll() {
  try {
    const categories = yield call(ProgramCategoryApi.fetchAll);
    yield put(ProgramCategoryActions.fetchAll.success(categories));
  } catch (e) {
    yield put(ProgramCategoryActions.fetchAll.failure(e));
  }
}

export function* programCategorySagas() {
  yield takeLatest(ProgramCategoryActions.fetchAll.start.type, handleFetchAll);
}

Everything make sense, but what happens my action code is executed over and over again. Digging into it a bit more, it appears that the effect hook is also executed over and over again. If I understand it correctly, it happens because the data in state is changing, the component is getting re-rendered again. But, it leads to infinite loop. What am I doing wrong? What is the correct way to setup this kind of component?

One of the options that I found is to change the saga to:

function* handleFetchAll() {
  try {
    const alreadyLoaded = select(ProgramCategorySelectors.isLoaded);
    if (!alreadyLoaded) {
      const categories = yield call(ProgramCategoryApi.fetchAll);
      yield put(ProgramCategoryActions.fetchAll.success(categories));
    }
  } catch (e) {
    yield put(ProgramCategoryActions.fetchAll.failure(e));
  }
}

So, it only calls the api once; and it seem to work fine this way. But, is it the correct solution?

As suggested in the comments, I tried adding dependency to the effect:

useEffect(() => {
    fetchAllCategories();
  }, []);

Now, I am getting an error:

./src/program/ProgramCategoriesTable/ProgramCategoriesTable.tsx Line 37:6: React Hook useEffect has a missing dependency: 'fetchAllCategories'. Either include it or remove the dependency array. If 'fetchAllCategories' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps

The issue is here,

useEffect(() => {
  console.error('in useEffect');
  fetchAllCategories();
});

From react docs: Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this.) Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.

https://reactjs.org/docs/hooks-effect.html

You have to pass and array of dependecies at the end.

useEffect(() => {
  console.error('in useEffect');
  fetchAllCategories();
}, []);

Hope this helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM