juliano.net
juliano.net

Reputation: 8177

useEffect infinite loop even though I've provided a dependency list

I'm trying to create a query page using redux, axios and an API. In useEffect(), if the variable from the useSelector is empty and the isLoading state is false, then I do the API request. However even without any changes to these variables (already in the dependency list), the useEffect keeps firing the API request indefinitely).

And while inspecting the state, I see integrationStatusLoading changing from true to false all the time. It seems that the useEffect is fired several times even before having completed the previous run.

My reducer:

import * as spreadsheetActions from './spreadsheetActions';

export const INITIAL_STATE = {
  messagesLog: [],
  loading: false,
  modalIsOpen: false,
  integration: {
    rows: [],
    loading: false
  }
};

export default (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case spreadsheetActions.SET_INTEGRATION_STATUS:
      return { ...state, integration: {
        ...state.integration,
        rows: action.payload
      }};
    case spreadsheetActions.SET_LOADING_INTEGRATION_STATUS:
      return { ...state, integration: {
        ...state.integration,
        loading: action.payload
      }};
    default:
      return state;
  }
};

The jsx:

  const IntegrationStatusContainer = function() {
  const classes = useStyles();
  const dispatch = useDispatch();
  const integrationStatusData = useSelector(state => state.spreadsheet.integration.rows);
  const integrationStatusLoading = useSelector(state => state.spreadsheet.integration.loading);

  useEffect(() => {
    if (isEmpty(integrationStatusData) && !integrationStatusLoading) {
      dispatch(spreadsheetOperations.getIntegrationStatus());
    }
  }, [dispatch, integrationStatusData, integrationStatusLoading]);

  return (
    <IntegrationStatusTable items={integrationStatusData} isLoading={integrationStatusLoading} />
  );
};

export default IntegrationStatusContainer;

spreadsheetOperations.js

export const getIntegrationStatus = () => async dispatch => {
  dispatch(spreadsheetActions.setLoadingIntegrationStatus(true));

  let response = await spreadsheetManager.getIntegrationStatus();

  batch(() => {
    dispatch(spreadsheetActions.setLoadingIntegrationStatus(false));
    dispatch(spreadsheetActions.setIntegrationStatus(response));
  });
};

spreadsheetManager.js

getIntegrationStatus = async () => {
    const getIntegrationStatusResult = await spreadsheetService.getIntegrationStatus();
    return getIntegrationStatusResult;
  };

spreadsheetService.js

getIntegrationStatus = async () => {
    const integrationStatusResponse = await axios.get(
      `${BASE_URL}/api/integration_status`
    );
    return integrationStatusResponse.data;
  };
}

spreadsheetActions.js

export const setIntegrationStatus = rows => ({
  type: SET_INTEGRATION_STATUS,
  payload: rows
});

export const setLoadingIntegrationStatus = status => ({
  type: SET_LOADING_INTEGRATION_STATUS,
  payload: status
});

What is wrong with my code? How can I stop the infinite loop?

Upvotes: 0

Views: 251

Answers (3)

juliano.net
juliano.net

Reputation: 8177

Problem solved by creating a new fetchStatus state with three possible values (pending, fulfilled and rejected), then in my if I've added a condition checking for fetchStatus === 'pending'.

Upvotes: 1

aaronrhodes
aaronrhodes

Reputation: 616

What is the expected functionality of this component? That the API call only fires once?

Your store updates to the isLoading flag will cause a render every time it changes. At the moment your cycle looks like it would be:

  • Initial render - fire API call
  • Render from flag changing to true
  • Render from flag changing to false - fire API call
  • Repeat step 2 and 3 indefinitely

Have you console logged the variables inside the effect as well as the redux state? If the Redux state is changing then it will fire a render on the component.

I think the simplest solution is to remove the isLoading flag from the dependancies, unless you have a retry scenario you're trying to achieve but I'd be looking at redux-saga for a solution to that.

Upvotes: 0

lukyluke154
lukyluke154

Reputation: 19

I see you are replying on isEmpty to check whether API should be called. Can you check if isEmpty is working as expected or the API is always returning empty response?

Upvotes: 0

Related Questions