Infinite loop with ngrx/effects
Asked Answered
T

5

17

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 });
  }
}
Timoteo answered 2/12, 2016 at 2:23 Comment(3)
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, ...Gwenora
Yes, thats was the problem. Thank you very much!Timoteo
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 :)Gwenora
G
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, ...

Gwenora answered 3/12, 2016 at 13:20 Comment(1)
For my case, the infinite loop happened because my .ofType Action had the same ActionType (first param of createAction) as my trigger Action.Sax
A
12

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.

Ani answered 2/7, 2018 at 23:42 Comment(3)
I am not sure why not returning a new action caused the endless loop. But returning the NoopAction prevented it.Ani
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.Turkish
@Turkish you should make that an answer, that's so easy to trip up on.Submicroscopic
S
9

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})
Scientism answered 15/10, 2021 at 20:38 Comment(5)
I got almost the same issue as Dallas described. Just used map() operator solve the problem for me.Kobold
thank you very much! This solves my problem that I had for hours! Thanks!Nodule
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.Crashing
OK, after reading @Iderrickable's comment in frosty's answer I understand what was happening now.Crashing
see this docs for more context about dispatch: false config ngrx.io/guide/effects/operators#concatlatestfromUnbridled
X
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.

Xmas answered 23/5, 2022 at 0:58 Comment(0)
P
0

In my case I just changed switchMap to mergeMap and it worked.

Reference: https://www.learnrxjs.io/learn-rxjs/operators/transformation/mergemap

Planar answered 7/8 at 6:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.