effect

The effect operator is an advanced RxJS utility for creating side effects in your observable pipeline. It provides enhanced functionality compared to standard operators like mergeMap, allowing for automatic handling of success and failure actions, additional actions, and more.


Importing

To use the effect operator, import it from @blue-functor/remodel:

import { effect } from '@blue-functor/remodel';

Parameters

ParameterTypeRequiredDescription
actionCreatorEffectActionCreator<Payload, SuccessPayload, FailurePayload>YesThe action creator for the effect. Generates .succeeded and .failed actions automatically.
effectCreator(data: D | Payload) => Promise<SuccessPayload>YesA function that performs the side effect, typically an API call or asynchronous operation.
optionsEffectOptions<SuccessPayload, FailurePayload>NoAn object for configuring additional success and failure actions and error handling.
options.additionalSuccessActions(data: SuccessPayload) => Action<any>[]NoA function that returns additional actions to dispatch upon successful execution.
options.additionalFailureActions(error: FailurePayload) => Action<any>[]NoA function that returns additional actions to dispatch upon failure.
options.onError(error: Error, actionType: string) => voidNoCustom error handler callback. Called when the effect fails.
options.suppressErrorLoggingbooleanNoSet to true to disable automatic error logging. Defaults to false.

Returns

An OperatorFunction that emits:

  • The .succeeded or .failed action created by the actionCreator, depending on the success or failure of the effect.
  • Any additional actions returned by options.additionalSuccessActions or options.additionalFailureActions.

Example Usage

Basic Example: API Call

This example demonstrates how to use effect for making an API call and handling success and failure actions.

src/models/ui/epics.ts
import { ofType, effect } from '@blue-functor/remodel';
import { myEffectAction } from '../actions';
import { myAPICall } from '../services';
 
const myEpic: Epic = (action$) =>
  action$.pipe(
    ofType(myEffectAction),
    effect(myEffectAction, myAPICall),
  );

In this example:

  • The myAPICall function is called when myEffectAction is dispatched.
  • On success, myEffectAction.succeeded is dispatched with the response data.
  • On failure, myEffectAction.failed is dispatched with the error.

Advanced Example: Additional Actions

This example demonstrates how to dispatch additional actions based on the result of an effect.

src/models/ui/epics.ts
import { ofType, effect } from '@blue-functor/remodel';
import { showNotification } from '../notifications';
import { myEffectAction } from './actions';
import { uploadPhoto } from './services';
 
const myEpic: Epic = (action$) =>
  action$.pipe(
    ofType(myEffectAction),
    effect(myEffectAction, uploadPhoto, {
      additionalSuccessActions: () => [showNotification({ message: 'Photo uploaded!', type: 'SUCCESS' })],
      additionalFailureActions: () => [showNotification({ message: 'Something went wrong!', type: 'FAILURE' })],
    }),
  );

In this example:

  • A success notification is dispatched alongside the .succeeded action on success.
  • A failure notification is dispatched alongside the .failed action on failure.


Example: Custom Error Handling

src/models/analytics/epics.ts
import { ofType, effect } from '@blue-functor/remodel';
import { trackEvent } from './actions';
import { sendAnalytics } from './services';
import { logToSentry } from '@/services/monitoring';
 
const analyticsEpic: Epic = (action$) =>
  action$.pipe(
    ofType(trackEvent),
    effect(trackEvent, sendAnalytics, {
      onError: (error, actionType) => {
        // Custom error handling - send to monitoring service
        logToSentry(error, { actionType });
      },
      suppressErrorLogging: true, // Disable console.error
    }),
  );

Example: Using Original Payload in Success Handler

The meta field in success/failed actions contains the original request payload, allowing you to correlate responses with requests.

src/models/posts/epics.ts
import { ofType, effect } from '@blue-functor/remodel';
import { Epic } from 'redux-observable';
import { createPost } from './actions';
import { map } from 'rxjs/operators';
 
const handlePostCreatedEpic: Epic = (action$) =>
  action$.pipe(
    ofType(createPost.succeeded),
    map((action) => {
      // action.payload contains the server response
      // action.meta contains the original request data
      console.log('Created post:', action.payload.id);
      console.log('Original title:', action.meta.title);
      return showNotification({ message: `Post "${action.meta.title}" created!` });
    }),
  );

Features

  1. Automatic Action Handling:

    • Automatically generates .succeeded and .failed actions for the provided actionCreator.
    • These actions are dispatched based on the result of the effectCreator.
    • The original request payload is included in the meta field for both success and failure actions.
  2. Additional Actions:

    • Allows dispatching extra actions based on the result of the effect through additionalSuccessActions and additionalFailureActions.
  3. Enhanced Error Handling:

    • Provides custom error handling via onError callback.
    • Automatic error logging in development (can be suppressed with suppressErrorLogging).
    • Includes catchError operator to prevent stream termination on errors.
    • Logs errors only in development mode by default.
  4. Flexible Input:

    • Handles both Action payloads and direct data inputs seamlessly, making it adaptable for various scenarios.
  5. Stream Resilience:

    • Uses catchError to prevent the observable stream from terminating on errors.
    • Failed effects dispatch .failed actions instead of breaking the epic pipeline.

Best Practices

Do

// Use custom error handling for production
effect(myAction, myService, {
  onError: (error, actionType) => {
    sendToLoggingService({ error, actionType });
  },
});
 
// Leverage meta field for request-response correlation
ofType(fetchUser.succeeded).pipe(
  map(action => {
    const userId = action.meta; // Original request ID
    const userData = action.payload; // Server response
    return updateCache(userId, userData);
  }),
);

Don’t

// Don't throw errors in effectCreator without handling
const badService = async (id) => {
  throw new Error('Unhandled'); // Will be caught by effect operator
};
 
// Don't ignore the meta field - it's useful!
ofType(action.succeeded).pipe(
  map(action => action.payload), // Missing correlation with original request
);

Notes

  • Integration with EffectActionCreator: The effect operator is designed to work specifically with actions created using createEffectAction, ensuring a streamlined workflow for managing asynchronous operations.

  • Usage in Modular Applications: Perfect for modular applications where epics are scoped to specific models or domains.

  • Error Handling: The operator automatically catches errors and prevents stream termination. Use onError for custom error handling and logging.

  • Meta Field: Success and failure actions include the original request payload in the meta field, enabling better request-response correlation and debugging.

  • Production-Ready: Error logging only happens in development mode unless you provide a custom onError handler, making it safe for production use.

The effect operator simplifies complex side-effect management, enabling clear, concise, and maintainable code for handling asynchronous workflows in Redux-Observable pipelines.