Statically typed actions and reducers for redux.
$ npm i red-g
-
actions as non-const enums (example
app/action_type.ts
file):export enum AppActionType { RESET = "App/RESET", READY = "App/READY", NOT_READY = "App/NOT_READY", DELAYED = "App/DELAYED", CLEAR_ERROR = "App/CLEAR_ERROR", }
-
easy action creators - define only those carrying some payload, empty ones are defined automatically (example
app/action.ts
file):import { actionCreators } from "red-g"; import { AppActionType } from "./action_type"; export default actionCreators(AppActionType, { READY: (error?: string) => ({ error }), DELAYED: (condition: boolean) => ({ condition }), });
-
slice of state (
app/state.ts
):export default { // usual stuff ready: false, delayed: false, // last application error - example use // of "type predicate" matcher // (matching actions by payload content) error: null as string | null, // how many actions of this type // has been spawned? - somewhat artificial // example of "boolean" matcher usage // (matching actions using string // operations on their type) actionCount: 0, };
-
fantastic slice reducers (example
app/reducer.ts
file) with matchers:import type { Action } from "red-g"; import { isWithPayload, sliceReducer, } from "red-g"; import initState from "./state"; import app from "./action"; export default sliceReducer(initState) ( (slice) => slice .handle(app.RESET, () => initState) // this action can carry `error` payload // but we're not interested in it (for now) .handle(app.READY, (state) => ({ ...state, ready: true, }) // this action is not carrying any payload // and was automatically defined by // call to `actionCreators()` .handle(app.NOT_READY, (state) => ({ ...state, ready: false, }) // this action is carrying simple payload and we can // destructure it immediately .handle(app.DELAYED, (state, { condition }) => ({ ...state, delayed: condition, }) // same situation as with `app.NOT_READY` // (shown here for completness of the example) .handle(app.CLEAR_ERROR, (state) => ({ ...state, error: null, }) // match all actions that carry payload with // `error` key ("type predicate" matcher) .match( (action): action is Action<{ error: string }> => isWithPayload(action) && action.payload.error, (state, { error }) => ({ ...state, error, }), ) // match all actions whose `type` field starts with // `App/` prefix ("boolean" matcher) .match( (action) => action.type.startsWith("App/"), (state) => ({ ...state, actionCount: state.actionCount + 1, }), ), );
-
now, you can combine all of your slice reducers (and actions and thunks...) into nice trees (as you do usually) - example
state_logic.ts
file:import appAction from "./app/action"; import appReducer from "./app/reducer"; export const action = { app: appAction, // ... and others }; export const reducer = { app: appReducer, // ... and others };
-
slice reducers are compatible with regular redux flow:
import { createStore, combineReducers, } from "redux"; import { bindActionCreatorsTree, } from "red-g"; import { action, reducer, } from "./state_logic"; const rootReducer = combineReducers(reducer); // redux store creation const store = createStore( rootReducer, // ... and other usual stuff ); // bound actions tree const act = bindActionCreatorsTree( action, store.dispatch, );
There's more to be happy about if you use IntelliSense.
can I use immer?
Sure! Curried producers are supported out of the box:
import type { Action } from "red-g";
import {
isWithPayload,
sliceReducer,
} from "red-g";
import produce from "immer";
import initState from "./state";
import app from "./action";
export default sliceReducer(initState) (
(slice) => slice
.handle(app.RESET, () => initState)
.handle(app.READY, produce((draft) => {
draft.ready = true;
})
.handle(app.DELAYED, produce((draft, { condition }) => {
draft.delayed = condition;
})
.match(
(action): action is Action<{ error: string }> =>
isWithPayload(action) && action.payload.error,
produce((draft, payload) => {
draft.error = payload.error;
}),
),
);
redux
{ actionCreators: [Function: actionCreators], bindActionCreator: [Function: bindActionCreator], bindActionCreators: [Function: bindActionCreators], bindActionCreatorsTree: [Function: bindActionCreatorsTree], createReducer: [Function: createReducer], defineActionCreator: [Function: defineActionCreator], emptyActionCreators: [Function: emptyActionCreators], isWithPayload: [Function: isWithPayload], isWithTypeField: [Function: isWithTypeField], payloadActionCreators: [Function: payloadActionCreators], sliceReducer: [Function: sliceReducer] }
Go ahead and file an issue or submit a fresh PR if you found a bug 🐞.
You can support this project via stellar network:
- Payment address: xcmats*keybase.io
- Stellar account ID:
GBYUN4PMACWBJ2CXVX2KID3WQOONPKZX2UL4J6ODMIRFCYOB3Z3C44UZ
red-g is released under the Apache License, Version 2.0. See the LICENSE for more details.