redux-dutiful-thunk
TypeScript icon, indicating that this package has built-in type declarations

1.1.1 • Public • Published

Redux Dutiful Thunk

npm version circleci

This is a Redux middleware almost the same as redux-thunk, but it respects Redux types.

Motivation

In Redux Thunk, you need to pass a thunk function directly to dispatch to invoke it.

// An action creator for Redux Thunk.
function incrementAsync() {
  return dispatch => {
    setTimeout(() => dispatch(increment()), 1000);
  };
}
 
// Dispatch a function.
store.dispatch(incrementAsync());

But this becomes a problem if you want to write code in a type-safe manner because the dispatch function is supposed to accept Redux actions, not functions.

// A Redux action consists of a unique `type` and zero or more extra arguments.
{ type: 'FETCH_USER', id: 30 }

So for example when you use Redux Thunk with TypeScript, you need to tweak Redux type definitions in some way (I don't know about flow much but I guess a similar problem exists). Therefore I could not find a good reason to use a function as a special action. Instead, let's create a normal action to meet the Redux rule and match its type definitions.

import {thunk} from 'redux-dutiful-thunk';
 
function incrementAsync() {
  // Wrap your thunk function by `thunk`.
  return thunk(async dispatch => {
      setTimeout(() => dispatch(increment()), 1000);
  });
}
 
// Now the action creator returns a normal action which contains a function you passed.
console.log(incrementAsync());
//=> { type: '@@redux-dutiful-thunk/THUNK', thunk: f, }
 
// So the `dispatch` function can take an action as usual, instead of a function.
store.dispatch(incrementAsync());

The difference with Redux Thunk is only the thunk wrapping.

 function incrementAsync() {
-  return dispatch => {
+  return thunk(async dispatch => {
       setTimeout(() => dispatch(increment()), 1000);
-  };
+  });
 }

Installation

npm install redux-dutiful-thunk

Usage

To enable Redux Dutiful Thunk, create a middleware and apply it to your store.

import {createStore, applyMiddleware} from 'redux';
import {createThunkMiddleware} from 'redux-dutiful-thunk';
import rootReducer from './reducers/index';
 
const store = createStore(
  rootReducer,
  applyMiddleware(createThunkMiddleware())
);

Custom Argument

Like Redux Thunk, you can inject a custom argument to your thunk actions.

const store = createStore(
  rootReducer,
  applyMiddleware(createThunkMiddleware(api))
);
 
function fetchUser(id) {
  return thunk(async (dispatch, getState, api) => {
    const user = await api.fetchUser(id);
    dispatch({type: 'FETCH_USER_SUCCESS', user});
  });
}

With TypeScript

This library is written in TypeScript so the type definitions are provided.

To make your dispatch function accept any thunk actions, add AnyThunkAction to your action type.

import {AnyThunkAction} from 'redux-dutiful-thunk';
 
type Action = 
  | AnyThunkAction
  | {type: 'FETCH_USER'; id: number}
  | {type: 'DO_SOMETHING'};

To implement your thunk action creators easily, we recommend to define a Thunk type using ThunkAction.

import {thunk, ThunkAction} from 'redux-dutiful-thunk';
import {Action} from './action-type';
import {State} from './state';
import {API} from './api';
import {User} from './models'
 
type Thunk<R = void> = ThunkAction<State, Action, API, R>;
 
// A thunk action creator.
function fetchUser(id: number): Thunk<User> {
  // You can omit argument types.
  return thunk(async (dispatch, getState, api) => {
    return await api.fetchUser(id);
  });
}

Thunk's Return Value

Because the dispatch function returns a given value as is, you can get a return value of thunk function in Redux Thunk.

const user = await dispatch(fetchUser(id));
console.log('got user', user);

But this cannot be done in Redux Dutiful Thunk.

// fetchUser returns an action, not a user data.
const action = dispatch(fetchUser(id));

For this use case, thunk actions have a promise that is resolved to a return value of your thunk function.

const action = dispatch(fetchUser(id));
const user = await action.promise;
 
// So you can write like this
user = await dispatch(fetchUser(id)).promise;

Of course this promise is type safe.

Specifying Thunk Type

All thunk actions have a same action type. If you want to distinguish each thunk action, use thunkAs instead of thunk. This allows you to specify a thunk type.

import {thunkAs} from 'redux-dutiful-thunk';
 
function fetchUser(id: number): Thunk<User> {
  return thunkAs('FETCH_USER', async (dispatch, getState, api) => {
    return await api.fetchUser(id);
  });
}
 
const action = fetchUser(3);
console.log(action.thunkType === 'FETCH_USER'); //=> true

API

Base types

  • Thunk<State, Action, Context, R>

    (dispatch: Dispatch<Action>, getState: () => State, context: Context) => R
  • ThunkAction<State, Action, Context, R, T>

    {
      typestring,
      thunkThunk<State, Action, Context, R>,
      promisePromise<R>,
      thunkTypeT,
    }

thunk

(f: Thunk<S, A, C, R>) => ThunkAction<S, A, C, R, null>

thunkAs

(type: T, f: Thunk<S, A, C, R>) => ThunkAction<S, A, C, R, T>

isThunkAction

(action: AnyAction) => action is AnyThunkAction

createThunkMiddleware

(contxt?: C) => Middleware

See src/index.ts for details.

Readme

Keywords

Package Sidebar

Install

npm i redux-dutiful-thunk

Weekly Downloads

1

Version

1.1.1

License

MIT

Unpacked Size

16.3 kB

Total Files

7

Last publish

Collaborators

  • ryym