Skip to content

mizchi/hard-reducer

Repository files navigation

hard-reducer

CircleCI Greenkeeper badge

Type friendly facade for better reducer.

npm install hard-reducer --save
# or
yarn add hard-reducer

Concepts

  • Type safe interface
  • Avoid redundant type string definitions
  • Keep reducer interface (State, Action) => State to use with redux.combineReducers()
  • Handle Flux Standard Action <Payload>{ type: string, payload: Payload }

Check this code to know detail.

Flow playground

Examples

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

Handle async action: createAsyncAction

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));

Handle thunk action: createThunkAction

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 }));

Related projects

ChangeLog

See ChangeLog.md

LICENSE

MIT