15

I'm trying to understand ngrx/effects. I have built a simple function that increments number by 1 with each click. But it's going in an infinite loop when clicked, not sure whats going on. I'm sure im making some stupid mistake.

monitor.effects.ts

@Injectable()
export class MonitorEffects {
    @Effect()
    compute$: Observable<Action> = this.actions$
        .ofType(monitor.ActionTypes.INCREMENT)
        .map((action: monitor.IncrementAction) => action.payload)
        .switchMap(payload => {
            return this.http.get('https://jsonplaceholder.typicode.com/users')
             .map(data => new monitor.IncrementAction(payload+1))
             .catch(err => of(new monitor.InitialAction(0)))
        });

    constructor(private actions$: Actions, private http:Http ) {};
}

monitor.component.ts

ngOnInit() {
    this.storeSubsciber = this
      .store
      .select('monitor')
      .subscribe((data: IMonitor.State) => {
        this.value = data.value;
        this.errorMsg = data.error;
        this.currentState = data.currentState;
      });
  }

  increment(value: number) {
    this.store.dispatch(new monitorActions.IncrementAction(value));
  }

monitor.reducer.ts

export const monitorReducer: ActionReducer<IMonitor.State> = (state = initialState, action: Actions) => {
  switch (action.type) {
    case ActionTypes.INCREMENT:
      return Object.assign({}, { value: action.payload, currentState: ActionTypes.INCREMENT, error: null });
...
...
...

    default:
      return Object.assign({}, { value: 0, currentState: ActionTypes.INITIAL, error: null });
  }
}
Sudhakar
  • 2,904
  • 8
  • 33
  • 47
  • if you watch actions `.ofType(monitor.ActionTypes.INCREMENT)` and from your effect you then dispatch an action `monitor.ActionTypes.INCREMENT` (from this part I assume : `monitor.IncrementAction(payload+1)`), then the effect is triggered again, and again, and again, ... – maxime1992 Dec 02 '16 at 09:50
  • Yes, thats was the problem. Thank you very much! – Sudhakar Dec 02 '16 at 22:58
  • I made a proper answer in case someone's having a similar problem and don't understand from your example. Glad it's working on your project ! You're welcome :) – maxime1992 Dec 03 '16 at 13:23

4 Answers4

17

An Effect allows you watch a given action type and react to that action every time it's been dispatched.

So if you watch actions X in some effect and dispatch from that effect another action X, you'll end up in an infinite loop.

In your case : Your action is of type .ofType(monitor.ActionTypes.INCREMENT) and from your effect you then dispatch an action monitor.ActionTypes.INCREMENT (from this part I assume : monitor.IncrementAction(payload+1)), then the effect is triggered again, and again, and again, ...

maxime1992
  • 22,502
  • 10
  • 80
  • 121
  • 1
    For my case, the infinite loop happened because my `.ofType` Action had the same ActionType (first param of `createAction`) as my trigger Action. – Danimt Jul 24 '20 at 15:29
11

I had this same issue, and the answer was that my effect didn't return a completely different action from the one that the current effect was keying on.

For my affect, I only did a .do(), and didn't switchMap it to a new action. That was my issue. Because I didn't need to fire a new action, I made a NoopAction that get returns. It is implying that no one should ever use the NoopAction in a reducer.

frosty
  • 21,036
  • 7
  • 52
  • 74
  • I am not sure why not returning a new action caused the endless loop. But returning the NoopAction prevented it. – frosty Jul 02 '18 at 23:43
  • 30
    If you were not returning a new action, and you were using the `do` (now called `tap` in rxjs 6) operator, you would get an infinite loop, because `do` returns the observable it receives. If you add `dispatch: false` to the Effect declaration like `@Effect({dispatch: false})`, the effect wouldn't return the observable it received. – lderrickable Jan 28 '19 at 13:17
  • @lderrickable you should make that an answer, that's so easy to trip up on. – BlackICE Feb 05 '19 at 20:43
6

I was having this issue and the answers here didn't help. For me, I had a "success" action that had a tap() to navigate. My browser would lock up and I didn't understand why until I put a log in my tap function and noticed it was getting hit infinitely.

My issue was that the effect was calling itself when tap returned the same observable. What I missed, and my solution... was putting dispatch: false on my effect to prevent it from dispatching itself.

@Effect({dispatch: false})
Smern
  • 18,746
  • 21
  • 72
  • 90
  • I got almost the same issue as Dallas described. Just used `map()` operator solve the problem for me. – bastien enjalbert Dec 20 '21 at 17:02
  • thank you very much! This solves my problem that I had for hours! Thanks! – julianpoemp Jan 07 '22 at 15:40
  • I'm still not 100% sure why this worked... The part I'm unsure about is `My issue was that the effect was calling itself when tap returned the same observable`. Why was the effect calling itself again? I assume this wasn't something you were explicitly doing. – WBuck May 27 '23 at 21:58
  • 1
    OK, after reading @Iderrickable's comment in frosty's answer I understand what was happening now. – WBuck May 27 '23 at 22:04
3

We already have a correct answer from Maxime

I came here for the same issue but nothing worked, in my case it was copy-paste issue

export const LoadCustomers = createAction(
    '[Customers] Load Customers' 
)
export const LoadCustomers_Success = createAction(
    '[Customers] Load Customers',
    props<{customers: Array<ICustomer>}>()
)
export const LoadCustomers_Failure = createAction(
    '[Customers] Load Customers',
    props<{error: string}>(),
)

My mistake was the type '[Customers] Load Customers', I change the action name but forgot to change the type.

Ali Adravi
  • 21,707
  • 9
  • 87
  • 85