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
| Parameter | Type | Required | Description |
|---|---|---|---|
actionCreator | EffectActionCreator<Payload, SuccessPayload, FailurePayload> | Yes | The action creator for the effect. Generates .succeeded and .failed actions automatically. |
effectCreator | (data: D | Payload) => Promise<SuccessPayload> | Yes | A function that performs the side effect, typically an API call or asynchronous operation. |
options | EffectOptions<SuccessPayload, FailurePayload> | No | An object for configuring additional success and failure actions and error handling. |
options.additionalSuccessActions | (data: SuccessPayload) => Action<any>[] | No | A function that returns additional actions to dispatch upon successful execution. |
options.additionalFailureActions | (error: FailurePayload) => Action<any>[] | No | A function that returns additional actions to dispatch upon failure. |
options.onError | (error: Error, actionType: string) => void | No | Custom error handler callback. Called when the effect fails. |
options.suppressErrorLogging | boolean | No | Set to true to disable automatic error logging. Defaults to false. |
Returns
An OperatorFunction that emits:
- The
.succeededor.failedaction created by theactionCreator, depending on the success or failure of the effect. - Any additional actions returned by
options.additionalSuccessActionsoroptions.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.
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
myAPICallfunction is called whenmyEffectActionis dispatched. - On success,
myEffectAction.succeededis dispatched with the response data. - On failure,
myEffectAction.failedis dispatched with the error.
Advanced Example: Additional Actions
This example demonstrates how to dispatch additional actions based on the result of an effect.
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
.succeededaction on success. - A failure notification is dispatched alongside the
.failedaction on failure.
Example: Custom Error Handling
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.
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
-
Automatic Action Handling:
- Automatically generates
.succeededand.failedactions for the providedactionCreator. - These actions are dispatched based on the result of the
effectCreator. - The original request payload is included in the
metafield for both success and failure actions.
- Automatically generates
-
Additional Actions:
- Allows dispatching extra actions based on the result of the effect through
additionalSuccessActionsandadditionalFailureActions.
- Allows dispatching extra actions based on the result of the effect through
-
Enhanced Error Handling:
- Provides custom error handling via
onErrorcallback. - Automatic error logging in development (can be suppressed with
suppressErrorLogging). - Includes
catchErroroperator to prevent stream termination on errors. - Logs errors only in development mode by default.
- Provides custom error handling via
-
Flexible Input:
- Handles both
Actionpayloads and direct data inputs seamlessly, making it adaptable for various scenarios.
- Handles both
-
Stream Resilience:
- Uses
catchErrorto prevent the observable stream from terminating on errors. - Failed effects dispatch
.failedactions instead of breaking the epic pipeline.
- Uses
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: Theeffectoperator is designed to work specifically with actions created usingcreateEffectAction, 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
onErrorfor custom error handling and logging. -
Meta Field: Success and failure actions include the original request payload in the
metafield, enabling better request-response correlation and debugging. -
Production-Ready: Error logging only happens in development mode unless you provide a custom
onErrorhandler, 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.