Type friendly facade for better reducer.
npm install hard-reducer --save
# or
yarn add hard-reducer
- Type safe interface
- Avoid redundant
type
string definitions - Keep reducer interface
(State, Action) => State
to use withredux.combineReducers()
- Handle Flux Standard Action
<Payload>{ type: string, payload: Payload }
Check this code to know detail.
This code is runnable in both flowtype and typescript
/* @flow */
// ^ flow magic comment to activate. It will be ignored in typescript.
import {
buildActionCreator,
createReducer,
type ActionCreator
} from "hard-reducer";
// ^ If you use typescript, Do not use `type` before ActionCreator
const { createAction } = buildActionCreator({ prefix: "counter/" });
// Add type to your payload by ActionCreator
const inc: ActionCreator<number> = createAction("inc");
// or infer by function result
const dec = createAction("dec", (val: number) => val);
inc(1); //=> { type: 'counter/inc', payload: 1 }
// Define state type
type State = { value: number };
const initialState: State = { value: 0 };
const reducer = createReducer(initialState)
// Handle `(State, Payload) => State` in matched context.
.case(inc, (state, payload) => {
return {
value: state.value + payload
};
})
.case(dec, (state, payload) => {
// $ExpectError
const p: string = payload;
return {
value: state.value - payload
};
})
// Take string
.case("other/noop", (state, payload) => {
return state;
})
// Take all uncaught action, not payload!
.else((state, action) => {
console.log("default fallback");
return state;
});
// Use it
const ret0 = reducer(initialState, inc(3));
const ret1 = reducer(ret1, dec(1));
See detail in index.js.flow
or index.d.ts
createAsyncAction(...)
returns { resolved, rejected, started }
and callable method.
(You need to add redux-thunk
in store's middlewares)
/* @flow */
import { createReducer, buildActionCreator } from "hard-reducer";
const { createAsyncAction } = buildActionCreator();
const incAsync = createAsyncAction("inc-async", async (val: number) => {
if (val % 2 === 1) {
throw new Error("error");
}
return {
p: 1
};
});
type Status = "ready" | "started" | "resolved" | "rejected";
type State = { status: Status, payload: ?{ p: number } };
const reducer = createReducer({ status: "ready", payload: null })
.case(incAsync.started, state => {
return { state: "started" };
})
.case(incAsync.resolved, (state, payload) => {
return { state: "resolve", payload };
})
.case(incAsync.rejected, (state, error) => {
return { state: "ready", payload: null };
});
// store
import reduxThunk from "redux-thunk";
import { applyMiddleware, createStore } from "redux";
const store = createStore(reducer, undefined, applyMiddleware(reduxThunk));
store.subscribe((...args) => {
console.log("store", store.getState());
});
// dispatch
store.dispatch(incAsync(1));
createThunkAction(...)
returns { resolved, rejected, started }
and callable method.
(You need to add redux-thunk
in store's middlewares)
import { createReducer, buildActionCreator } from "hard-reducer";
const { createThunkAction, createAction } = buildActionCreator();
const inc = createAction("inc", (val: number) => val);
const thunked = createThunkAction(
"thunked",
async (input, dispatch, getState) => {
dispatch(inc(input.value));
return { ret: true };
}
);
// Handle
createReducer({ status: "ready", payload: null })
.case(thunked.started, state => {
return { state: "started", payload: null };
})
.case(thunked.resolved, (state, payload) => {
return { state: "resolve", payload };
})
.case(thunked.rejected, (state, error) => {
return { state: "ready", payload: null };
});
// dispatch
store.dispatch(thunked({ value: 1 }));
- reduxactions/redux-actions: Flux Standard Action utilities for Redux.
- aikoven/typescript-fsa: Type-safe action creator utilities
- acdlite/flux-standard-action: A human-friendly standard for Flux action objects.
See ChangeLog.md
MIT