Search code examples
reactjsreact-reduxredux-saga

Redux saga enters in infinite loop state of calling api endpoint


I am using Redux for state management and saga as a middleware. For some reason my app is in some infinite loop state of calling API endpoint.

This is my actions:

export const GET_USERS = "GET_USERS";
export const getUsers = () => ({
  type: GET_USERS,
});

export const GET_USERS_SUCCESS = `${GET_USERS}_SUCCESS`;
export const getUsersSuccess = (data) => ({
  type: GET_USERS_SUCCESS,
  payload: data,
});

export const GET_USERS_FAIL = `${GET_USERS}_FAIL`;
export const getUsersFail = (error) => ({
  type: GET_USERS_FAIL,
  payload: error,
});

This is saga:

export function* getUsers$() {
  try {
    const users = yield getUsersAPI();
    yield put(actions.getUsersSuccess(users.data));
  } catch (error) {
    yield put(actions.getUsersFail(error));
  }
}

export default function* () {
  yield all([takeLatest(actions.getUsers, getUsers$)]);
}

This is a reducer:

export default (state = initialState(), action) => {

  const { type, payload } = action;

  switch (type) {
    case actions.GET_USERS:
      return {
        ...state,
        users: {
          ...state.users,
          inProgress: true,
        },
      };
    case actions.GET_USERS_SUCCESS:
      return {
        ...state,
        users: {
          inProgress: false,
          data: payload,
        },
      };
    case actions.GET_USERS_FAIL:
      return {
        ...state,
        users: {
          ...state.users,
          inProgress: false,
          error: payload,
        },
      };
    default:
      return state;
  }
};

And this is a component connected with redux:

const Home = (props) => {

    useEffect(() => {
        props.getUsers();
        console.log('props', props.data);
    }, []);
   return(
    <h1>Title</h1>
   );
}

const mapStateToProps = ({
    users: {
        users: {
            data
        }
    }
}) => ({data})


export default connect(mapStateToProps, {getUsers})(Home);

Why is this happening?


Solution

  • This is due to the fact that you misused the sagas in your example. As with any other effect creator as the first parameter must pass a pattern, which can be read in more detail in the documentation. The first parameter can also be passed a function, but in a slightly different way. View documentation (block take(pattern)).

    In your case, you are passing a function there that will return an object

    {
      type: 'SOME_TYPE',
      payload: 'some_payload',
    }
    

    Because of this, your worker will react to ALL events that you dispatch. As a result, you receive data from the server, dispatch a new action to save data from the store. And besides the reducer, your getUsers saga will be called for this action too. And so on ad infinitum.

    Solution

    To solve this problem, just use the string constant actions.GET_USERS that you defined in your actions. And your sagas will look like this:

    export function* getUsers$() {
      try {
        const users = yield getUsersAPI();
        yield put(actions.getUsersSuccess(users.data));
      } catch (error) {
        yield put(actions.getUsersFail(error));
      }
    }
    
    export default function* () {
      yield all([takeLatest(actions.GET_USERS, getUsers$)]);
    }
    

    This should fix your problem.