Skip to content

drmats/red-g

Repository files navigation

red-g

Statically typed actions and reducers for redux.

npm version GitHub code size GitHub tag npm license

$ npm i red-g

ok, what's in here and how to use it?

  1. 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",
    }
  2. 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 }),
    });
  3. 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,
    };
  4. 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,
                }),
            ),
    );
  5. 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
    };
  6. 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;
            }),
        ),
);

documentation

API Reference


namespace

static redux

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] }

notes

Go ahead and file an issue or submit a fresh PR if you found a bug 🐞.


support

You can support this project via stellar network:


license

red-g is released under the Apache License, Version 2.0. See the LICENSE for more details.