TypeScript FSA Utilities for Redux Thunk

2.10.2 · active · verified Sun Apr 19

typescript-fsa-redux-thunk is a utility library that bridges the gap between `typescript-fsa`'s strongly typed Flux Standard Action (FSA) creators and `redux-thunk` for handling asynchronous operations in Redux applications. It provides an `asyncFactory` to easily define typed async action creators, streamlining the process of dispatching `started`, `done`, and `failed` actions that conform to the FSA specification. The current stable version is 2.10.2. While the release cadence isn't explicitly stated, the project appears actively maintained with recent updates, though the latest npm publish was 5 years ago for 2.10.2. A key differentiator is its tight integration with `typescript-fsa`, allowing developers to leverage type safety throughout their async Redux action flows, including custom error types. It also includes `thunkToAction` for easier integration with `bindActionCreators`.

Common errors

Warnings

Install

Imports

Quickstart

This example demonstrates how to create a type-safe asynchronous login action using `asyncFactory`, set up a Redux store with `redux-thunk`, and handle the action's lifecycle (started, failed, done) in a reducer.

import 'isomorphic-fetch'; // For Node.js environments
import { createStore, applyMiddleware, AnyAction } from 'redux';
import thunkMiddleware, { ThunkMiddleware } from 'redux-thunk';
import { reducerWithInitialState } from 'typescript-fsa-reducers';
import actionCreatorFactory from 'typescript-fsa';
import { asyncFactory } from 'typescript-fsa-redux-thunk';

interface LoginParams {
  email: string;
  password: string;
}
interface UserToken {
  token: string;
}
class CustomError extends Error {}

interface State {
  title: string;
  userToken: UserToken;
  loggingIn?: boolean;
  error?: CustomError;
}

const create = actionCreatorFactory('examples');
const createAsync = asyncFactory<State>(create);

const changeTitle = create<string>('Change the title');

const login = createAsync<LoginParams, UserToken, CustomError>(
  'Login',
  async (params, dispatch) => {
    const url = `https://reqres.in/api/login`;
    const options: RequestInit = {
      method: 'POST',
      body: JSON.stringify(params),
      headers: {
        'Content-Type': 'application/json; charset=utf-8',
      },
    };
    const res = await fetch(url, options);
    if (!res.ok) {
      throw new CustomError(`Error ${res.status}: ${res.statusText}`);
    }

    dispatch(changeTitle('You are logged-in'));

    return res.json();
  },
);

const initial: State = {
  title: 'Please login',
  userToken: {
    token: '',
  },
};

const reducer = reducerWithInitialState(initial)
  .case(changeTitle, (state, title) => ({
    ...state,
    title,
  }))
  .case(login.async.started, (state) => ({
    ...state,
    loggingIn: true,
    error: undefined,
  }))
  .case(login.async.failed, (state, { error }) => ({
    ...state,
    loggingIn: false,
    error,
  }))
  .case(login.async.done, (state, { result: userToken }) => ({
    ...state,
    userToken,
    loggingIn: false,
    error: undefined,
  }));

(async () => {
  const thunk: ThunkMiddleware<State, AnyAction> = thunkMiddleware;
  const store = createStore(reducer, applyMiddleware(thunk));
  console.log('Initial state:', store.getState().title);

  try {
    await store.dispatch(login({ email: 'eve.holt@reqres.in', password: 'cityslicka' }));
    const { title, userToken } = store.getState();
    console.log('Logged in state:', title, userToken);
  } catch (err) {
    console.error('Login failed:', err);
  }
})();

view raw JSON →