NgRx Effects: A Case for Returning a No-Op Action

by | May 20, 2019

When you write an NgRx effect, it is important to keep in mind this principle: You must always return an action. This article doesn’t apply to effects that specify @Effect({dispatch:false}). In that case, you will not return an action at all.

You might be wondering what happens if you don’t return an action from the effect. You will see the following error in the console:

ERROR TypeError: You provided ‘undefined’ where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

The nasty thing about this error is that it will be the last time your effect gets fired as it completes the observable for it. Also, catchError and finalize will not be called so you can’t even code a graceful recovery. In other words, you never want this to happen in your code.

 

The Case for a No Operation Action

In most cases, your effect will simply call an API and return a Success action or if the catchError is hit, it will return a Failure action. This is the pattern laid down by Todd Motto in his great NgRx training class. But what if you have a scenario where if the payload has a certain condition, you return an action, otherwise you don’t want to return an action because it is unnecessary? There wasn’t a failure and no action was required to update the store. What do we do here? If we do nothing and do not return anything, we’ll get the error above and have a bad day programming.

I propose that this is a good scenario to have a generic Noop action that simply is defined as an action but does nothing – doesn’t update the store in the reducer.

Example:

@Effect()
  effectReturnTest$ = this.actions$.pipe(
    ofType<EffectReturnTest>(AppActionTypes.EffectReturnTest),
    switchMap(action => {
      switch (action.payload) {
        case 0:
          return of(new ActionToReturn("Some string"));

        case 2:
          return of(new NoopAction());
      }
    }),
    catchError(error => {
      // For case 1 above, this isn't caught, we would of course never want to catch an error here.
      console.log("Error!", error);
      return of(new NoopAction());
    }),
    finalize(() => console.log("Should never get to finalize!"))
  );

 

Note the following:

We have a “switch” block that is doing something based on the payload.

  • Case 0 is the success case that returns a normal action
  • Case 1 is the “don’t handle” case that will throw an error
  • Note that for this case, neither the catchError nor the finalize are called, even though the observable stream is ended
  • Case 2 is the Noop case that will return a no operation (Noop) action

Essentially, Case 2 in the code is the fix for Case 1. We can’t just avoid handling a case and let it pass through so that an action isn’t returned. I know I could have used a “default” case here and returned a NoopAction but this was just for this contrived example.

You might ask the question: “Why not use ignoreElements() after the switchMap in the code above so that it gets ignored.” Good question but the short answer is that it still throws the error mentioned above. Probably because the effect still is expecting an action to be returned.

 

Conclusion

The documentation for NgRx doesn’t really talk about returning a NoopAction, however, I believe there are cases like the one outlined in this article where it can come in handy. Especially to avoid the dreaded “You provided ‘undefined’ where a stream was expected” error.

The code for this example is available here.