Redux Action Creators for Promises

raw JSON →
3.1.0 verified Thu Apr 23 auth: no javascript

This library provides action creators designed to streamline the creation of synchronous and asynchronous Redux actions, specifically integrating with `redux-promise-middleware`. It is currently stable at version 3.1.0, and while its explicit release cadence is not specified, ongoing build status indicators suggest active maintenance. A key differentiator is its robust first-class TypeScript support, which requires TypeScript 3 or newer for version 3 of the library, and TypeScript 2 for version 1. It simplifies the process of creating async actions by automatically generating `_PENDING`, `_FULFILLED`, and `_REJECTED` actions when a promise is dispatched, thereby eliminating the need for manual action type enums. The library promotes type safety in reducers by allowing action creators to be directly referenced (e.g., `String(myAction)`) and boasts a tiny bundle size, making it a lightweight alternative focused on promise-based Redux flows without external dependencies beyond `redux-promise-middleware` itself.

error Argument of type 'string' is not assignable to parameter of type 'AnyAction'.
cause Attempting to use a plain string literal as an action type in a reducer when `String(actionCreator)` is expected for type safety.
fix
In reducers, always use String(actionCreator) or actionCreator.toString() to obtain the action type string, ensuring type compatibility and leverage TypeScript's type inference.
error TypeError: Cannot read properties of undefined (reading 'pending')
cause Trying to access `.pending`, `.fulfilled`, or `.rejected` properties on an action creator created with `createAction` (synchronous) instead of `createAsyncAction`.
fix
Ensure that action creators meant to handle promises and their lifecycle states are created using createAsyncAction.
error Promise rejected with a non-error: 'Your error message here'. Consider rejecting with an Error object to ensure `action.error` is true.
cause `redux-promise-middleware` by default expects promise rejections to be `Error` objects to set `action.error: true` for `_REJECTED` actions.
fix
Always reject promises with instances of Error (e.g., Promise.reject(new Error('Failed!'))). Alternatively, configure redux-promise-middleware's isError option to handle custom error types.
breaking Version 3.x of `redux-promise-middleware-actions` requires TypeScript 3.x or newer. For projects using TypeScript 2.x, `redux-promise-middleware-actions` version 1.x must be used.
fix Upgrade your project's TypeScript version to 3.0 or higher, or downgrade `redux-promise-middleware-actions` to a 1.x release compatible with TypeScript 2.x.
gotcha This library is not compatible with the `promiseTypeSuffixes` option of `redux-promise-middleware`. It uses its own fixed `_PENDING`, `_FULFILLED`, `_REJECTED` suffixes.
fix Do not configure `promiseTypeSuffixes` when initializing `redux-promise-middleware` if you are using this library. Rely on the default suffixes provided by `redux-promise-middleware-actions`.
gotcha `redux-promise-middleware-actions` requires `redux-promise-middleware` to be installed and applied to the Redux store's middleware chain to correctly handle asynchronous actions.
fix Ensure both `redux-promise-middleware-actions` and `redux-promise-middleware` are installed (`npm install redux-promise-middleware-actions redux-promise-middleware`) and `promiseMiddleware` is applied using `applyMiddleware` when creating your Redux store.
npm install redux-promise-middleware-actions
yarn add redux-promise-middleware-actions
pnpm add redux-promise-middleware-actions

This quickstart demonstrates how to configure a Redux store with `redux-promise-middleware` and use `redux-promise-middleware-actions` to create and dispatch an asynchronous action, showing how the reducer handles `_PENDING`, `_FULFILLED`, and `_REJECTED` states.

import { createStore, applyMiddleware, compose } from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
import { createAsyncAction } from 'redux-promise-middleware-actions';

// 1. Setup Redux store with promiseMiddleware
// Ensure Redux DevTools compatibility if available
const composeEnhancers = (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;

// A simple reducer to handle our async action states
const rootReducer = (state = { data: null, loading: false, error: null }, action) => {
  switch (String(action.type)) { // Use String(action.type) for type matching
    case String(fetchData.pending): // Action.type for pending actions is usually the base type + _PENDING suffix
      return { ...state, loading: true, error: null };
    case String(fetchData.fulfilled): // For fulfilled, payload is the resolved promise value
      return { ...state, loading: false, data: action.payload };
    case String(fetchData.rejected): // For rejected, payload is the error object
      return { ...state, loading: false, error: action.payload };
    default:
      return state;
  }
};

const store = createStore(
  rootReducer,
  composeEnhancers(applyMiddleware(promiseMiddleware))
);

// 2. Create an async action
export const fetchData = createAsyncAction('FETCH_DATA', async (id) => {
  // Simulate an asynchronous operation (e.g., an API call)
  console.log(`Fetching data for ID: ${id}...`);
  const response = await new Promise(resolve => {
    setTimeout(() => {
      if (id === 1) {
        resolve({ id, value: 'sample data successfully fetched' });
      } else {
        throw new Error(`Failed to fetch data for ID: ${id}`);
      }
    }, 1000);
  });
  return response;
});

// 3. Dispatch the async action
console.log('--- Dispatching fetchData(1) (success) ---');
store.dispatch(fetchData(1));

// Observe state changes over time (for demonstration)
const unsubscribe = store.subscribe(() => {
  console.log('Current state:', store.getState());
});

// Dispatch another action after a short delay to demonstrate error handling
setTimeout(() => {
  console.log('\n--- Dispatching fetchData(2) (failure) ---');
  store.dispatch(fetchData(2));
}, 3000);

// Clean up subscription after demonstration
setTimeout(() => {
  unsubscribe();
  console.log('\nDemonstration complete.');
}, 5000);