I am refactoring some code and turning my class components into function components as a way of learning how to use Hooks and Effects. My code uses Redux for state management and axios for database requests with Thunk as middleware for handling asynchronicity. I'm having an issue in one component that does a get request to retrieve a list of customers on what used to be componentDidMount. No matter what I try, the useEffect function gets into an infinite loop and continues requesting the customer list.

The component in question, CustomersTable, gets a list of customers from the database and displays it in a table. The component is wrapped by a container component that uses Redux's connect to pass in the retrieved list of customers to the CustomersTable as a prop.

useEffect(() => {
    loadCustomers(currentPage, itemsPerPage, sortProp, (ascending ? 'asc' : 'desc'), {});
  }, []);

loadCustomers is a Redux action that uses axios to fetch the customer list. currentPage, itemsPerPage, sortProp and ascending are state variables that are initialized to specific values on 'component mount'

I would expect that because I use the empty array that this would run only once. Instead it runs continuously. I can't figure out why this is happening. My best guess is that when redux gets the list, it returns a new object for state and therefore the props change every time, which then triggers a re-render, which then fetches a new list. Am I using this wrong in that Redux isn't meant to be used with hooks like this?

I ended up getting this working by adding the following:

useEffect(() => {
    if (!list.length) {
      loadCustomers(currentPage, itemsPerPage, sortProp, (ascending ? 'asc' : 'desc'), {});
    }
  }, []);

I'm not sure this is the behavior I truly want though. If the list of customers was truly 0, then the code would continue to fetch the list. If the list were truly empty, then I would want it to fetch only once and then stop. Edit: Turns out this definitely doesn't work. It works for the initial load, but breaks the code for any delete or edit.

OK, providing more context here. The container component that wraps the CustomersTable is:

import { connect } from 'react-redux';
import loadCustomers from './actions/customersActions';
import { deleteCustomer } from './actions/customerActions';
import CustomersTable from './CustomersTableHooks';

function mapStateToProps(state) {
  return {
    customers: state.customers,
    customer: state.customer
  };
}

export default connect(mapStateToProps, { loadCustomers, deleteCustomer })(CustomersTable);

The action, loadCustomers is:

export default function loadCustomers(page = 1, itemsPerPage = 50, sortProp = 'id', sortOrder = 'asc', search = {}) {
  return (dispatch) => {
    dispatch(loadCustomersBegin());
    return loadCustomersApi(page, itemsPerPage, sortProp, sortOrder, search)
      .then(data => dispatch(loadCustomersSuccess(data)))
      .catch(() => dispatch(loadCustomersFailure()));
  };
}

the reducer for customers is:

export default function customersReducer(state = initialState, action) {
  switch (action.type) {
    case types.LOAD_CUSTOMERS_BEGIN:
      return Object.assign({}, state, { isLoading: true, list: [], totalItems: 0 });
    case types.LOAD_CUSTOMERS_SUCCESS:
      return Object.assign({}, state, { isLoading: false, list: action.customers || [], totalItems: action.totalItems });
    case types.LOAD_CUSTOMERS_FAILURE:
      return Object.assign({}, state, { isLoading: false, list: [], totalItems: 0 });
    default:
      return state;
  }
}

I unfortunately can't post much of the CustomersTable itself because things are named in a way that would tell you what company I'm working for.

1 Answers

0
dee zg On

So, if i understand your code correctly, you are dispatching the loadCustomers action in child component within useEffect but you read actual data in parents mapStateToProps.

That would, of course, create infinite loop as:

  1. parent reads customers from store (or anything from the store, for that matter)
  2. renders children
  3. child fetches customers in useEffect
  4. properties on parent change and cause re-render
  5. whole story goes on forever

Moral of the story: don't dispatch from presentational components. or, in other words, dispatch an action from the same component you read those same properties from store.