redux-dutiful-thunk
v1.1.1
Published
Use thunk action without breaking Redux types
Downloads
2
Readme
Redux Dutiful Thunk
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>
{ type: string, thunk: Thunk<State, Action, Context, R>, promise: Promise<R>, thunkType: T, }
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.