TypeScript FSA utilities for redux-saga
This package provides utility functions to seamlessly integrate asynchronous action creators from `typescript-fsa` with `redux-saga` worker sagas. Its primary function, `bindAsyncAction`, simplifies handling the lifecycle of async operations by automatically dispatching `started`, `done`, and `failed` actions around a saga's execution. This ensures strong type safety throughout the asynchronous flow when using `typescript-fsa`'s action patterns. The current stable version is 2.0.0, which notably requires `typescript-fsa` version 3.x.x. The project actively maintains compatibility with recent `redux-saga` releases, providing a reliable bridge between these two popular libraries for typed state management and side-effect handling.
Common errors
-
TypeError: (0 , typescript_fsa_1.actionCreatorFactory) is not a function
cause Mismatched major versions between `typescript-fsa-redux-saga` and `typescript-fsa`. `typescript-fsa-redux-saga@2.x` requires `typescript-fsa@3.x`.fixEnsure `typescript-fsa` is installed at version `^3.0.0` or later (`npm install typescript-fsa@^3`). -
Error: Could not find type definition for 'redux-saga'
cause `redux-saga` or its type definitions (`@types/redux-saga`) are not installed, or there's a version mismatch.fixInstall `redux-saga` and its type definitions: `npm install redux-saga @types/redux-saga`.
Warnings
- breaking Version 2.0.0 upgraded the `typescript-fsa` dependency to `^3.0.0`. Users on `typescript-fsa` v2.x.x must upgrade their `typescript-fsa` package to version 3 or higher, or remain on `typescript-fsa-redux-saga` v1.x.x.
- gotcha Prior to v1.0.3, generator transpilation had inconsistencies across CommonJS and ES6 builds, sometimes using Babel. From v1.0.3 onwards, Babel was removed, and native TypeScript transpilation is used for generators. This might affect projects with highly customized Babel configurations or those relying on previous specific generator output.
- gotcha The `skipStartedAction` option (introduced in v1.1.0) changes the behavior of `bindAsyncAction` significantly. When `true`, the `started` action is *not* dispatched automatically but is expected to be manually dispatched as the trigger for the saga, with the saga then dispatching `done`/`failed`. Incorrect usage can lead to missing `started` actions or unexpected saga triggers.
Install
-
npm install typescript-fsa-redux-saga -
yarn add typescript-fsa-redux-saga -
pnpm add typescript-fsa-redux-saga
Imports
- bindAsyncAction
import bindAsyncAction from 'typescript-fsa-redux-saga';
import { bindAsyncAction } from 'typescript-fsa-redux-saga'; - actionCreatorFactory
import { actionCreatorFactory } from 'typescript-fsa';import actionCreatorFactory from 'typescript-fsa';
- SagaIterator
import SagaIterator from 'redux-saga';
import { SagaIterator } from 'redux-saga';
Quickstart
import actionCreatorFactory from 'typescript-fsa';
import { SagaIterator } from 'redux-saga';
import { call } from 'redux-saga/effects';
import { bindAsyncAction } from 'typescript-fsa-redux-saga';
// actions.ts (excerpt)
const actionCreator = actionCreatorFactory('MY_APP');
export const doSomething =
actionCreator.async<{foo: string}, // parameter type
{bar: number} // result type
>('DO_SOMETHING');
// A mock API call function
async function fetchSomething(param: string): Promise<number> {
console.log(`Simulating API call with: ${param}`);
return new Promise(resolve => setTimeout(() => resolve(param.length * 10), 500));
}
// The worker saga wrapped by bindAsyncAction
const doSomethingWorker = bindAsyncAction(doSomething)(
function* (params): SagaIterator<{bar: number}> {
// `params` type is `{foo: string}`
console.log(`Worker saga started with params:`, params);
const result = yield call(fetchSomething, params.foo);
console.log(`API call returned:`, result);
return {bar: result};
},
);
// A root saga that demonstrates calling the async worker
function* myRootSaga(): SagaIterator {
console.log('Initiating doSomething action...');
// Calling the worker directly. bindAsyncAction internally dispatches
// doSomething.started, doSomething.done (or doSomething.failed if an error occurs).
const result = yield call(doSomethingWorker, {foo: 'example_data'});
console.log('doSomethingWorker completed with result:', result);
try {
console.log('Initiating another doSomething action (simulating potential error path)...');
yield call(doSomethingWorker, {foo: 'error_case'}); // For demo, assuming this could lead to error
} catch (e) {
console.error('Caught an error in saga outside worker:', e); // Errors from worker are re-thrown by bindAsyncAction
}
}
// To run this in a Redux Saga setup, you would typically pass `myRootSaga` to `sagaMiddleware.run`.
// Example (for conceptual understanding, not directly executable without Redux setup):
// import createSagaMiddleware from 'redux-saga';
// import { createStore, applyMiddleware } from 'redux';
// const sagaMiddleware = createSagaMiddleware();
// const store = createStore(() => ({}), applyMiddleware(sagaMiddleware));
// sagaMiddleware.run(myRootSaga);